IO 多路复用

简介: IO 多路复用

IO 多路复用作用于数据准备阶段。

io 多路复用作用阶段

1、select

1.1、数据结构

fdset 是一个 fd 的位图,采用数组的形式来存储。每个 bit 位代表一个 fd,0表示该 fd 的事件未就绪,1表示该 fd 事件的已就绪。

// fd_set 里面文件描述符的数量,可以使用 ulimit -n 进行查看,默认1024
 #define FD_SETSIZE __FD_SETSIZE
 // fd_set 的成员是一个长整型的结构体
 typedef long int __fd_mask;
 // fd_set 记录要监听的 fd 集合,及其对应状态
 typedef struct {
     // fds_bits是long类型数组,用来实现位图,长度: 1024/32=32
     // 共1024个bit位,每个bit位代表一个fd,0表示未就绪,1表示就绪
     __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
 } fd_set;
 // fd_set 集合的相关操作
 void FD_ZERO(fd_set *fdset); // 将所有fd清零
 void FD_SET(int fd, fd_set *fdset); // 增加一个fd
 void FD_CLR(int fd, fd_set *fdset); // 删除一个fd 
 int FD_ISSET(int fd, fd_set *fdset); // 判断一个fd是否有设置

1.2、select 函数

#include <sys/select.h> 
 #include <sys/time.h> 
 /* 返回值:返回就绪(可读、可写、异常)文件描述符的总数,若没有返回 0 */
 int select(int nfds, // 要监听 fd_set 的最大 fd+1(监听总数),fd 从0开始计数
            fd_set *readset, // 内核读操作的 fd 集合 
            fd_set *writeset, // 内核写操作的 fd 集合 
            fd_set *exceptionset, // 内核异常操作的 fd 集合 
            struct timeval * timeout // 超时时间,NULL-永不超时,0-不阻塞等待。
 );

1.3、selcet 使用

  • 创建集合(集合的元素:文件描述符),把要等待的fd放入集合(监听)
  • 创建fd_set集合
  • FD_ZERO初始化集合(将位图所有位置0)
  • FD_SET把要监听文件描述符FD加入集合(将位图所对应的位置为1)


  • 用 select 系统调用,进程阻塞,当集合中任意一个FD准备就绪,解除阻塞
  • FD_INSSET 检查 fd 是否就绪,就绪就解除阻塞

1.4、select 问题

select 的问题

  • 监听的 fd 的数量有限制,默认是1024
  • 每次 select 都需要把监听的 fd 从用户态拷贝到内核态,返回后从内核态拷贝数据到用户态
  • 轮询遍历所有的 fd 来判断就绪状态,效率低

1.5、例:即时聊天

// ./姓名 pipe1 pipe2
 // 双方都需要读键盘、读管道
 //女神进程
 #include <func.h>
 int main(int argc, char *argv[]) {
     ARGS_CHECK(argc,3);
     int fdr = open(argv[1], O_RDONLY);
     int fdw = open(argv[2], O_WRONLY);
     puts("阿珍:");
     char buf[4096] = {0};
     fd_set rdset;
     while(1){
         FD_ZERO(&rdset);
         FD_SET(fdr,&rdset);
         FD_SET(STDIN_FILENO, &rdset);
         //超时机制的实现
         struct timeval timeout;
         timeout.tv_sec = 60;
         int nret = select(fdr+1,&rdset,NULL,NULL,&timeout);
         if(nret == 0){
             puts("timeout");
             break;
         }
         //读端:读对方的数据
         if(FD_ISSET(fdr,&rdset)){
             puts("备胎1024");
             memset(buf,0,sizeof(buf));
             int ret = read(fdr,buf,sizeof(buf));
             //对方写端关闭,防止读端阻塞
             if(ret == 0){
                 puts("byebye");
                 break;
             }
             puts(buf);
         }
         //写端:读入键盘数据,发送给对方
         if(FD_ISSET(STDIN_FILENO, &rdset)){
             memset(buf,0,sizeof(buf));
             int ret = read(STDIN_FILENO,buf,sizeof(buf));
             //键盘关闭,ctrl+d
             if(ret == 0){
                 puts("I quit");
                 write(fdw,"你是好人",13);
                 break;
             }
             struct stat statbuf;
             stat(argv[2],&statbuf);
             char * curtime = ctime(&statbuf.st_mtime);
             strcat(curtime, " ");
             strcat(curtime, buf);
             write(fdw, curtime, strlen(curtime));
         }
     }
     return 0;
 }
 //舔狗进程
 #include <func.h>
 int main(int argc, char *argv[]) {
     ARGS_CHECK(argc,3);
     int fdw = open(argv[1], O_WRONLY);
     int fdr = open(argv[2], O_RDONLY);
     puts("阿强:");
     char buf[4096] = {0};
     fd_set rdset;
     while(1){
         FD_ZERO(&rdset);
         FD_SET(fdr,&rdset);
         FD_SET(STDIN_FILENO, &rdset);
         struct timeval timeout;
         timeout.tv_sec = 60;
         int nret = select(fdr+1,&rdset,NULL,NULL,&timeout);
         if(nret == 0){
             puts("timeout");
             break;
         }
         if(FD_ISSET(fdr,&rdset)){
             puts("女神");
             memset(buf,0,sizeof(buf));
             int ret = read(fdr,buf,sizeof(buf));
             if(ret == 0){
                 puts("byebye");
                 break;
             }
             puts(buf);
         }
         if(FD_ISSET(STDIN_FILENO, &rdset)){
             memset(buf,0,sizeof(buf));
             int ret = read(STDIN_FILENO,buf,sizeof(buf));
             if(ret == 0){
                 puts("I quit");
                 write(fdw,"多喝热水",13);
                 break;
             }
             struct stat statbuf;
             stat(argv[1],&statbuf);
             char * curtime = ctime(&statbuf.st_mtime);
             strcat(curtime, " ");
             strcat(curtime, buf);
             write(fdw, curtime, strlen(curtime));
         }
     }
     return 0;
 }

2、poll

2.1、数据结构

// pollfd 的事件类型
 #define POLLIN
 #define POLLOUT
 #define POLLERR
 #define POLLNVAL
 // pollfd 结构
 struct pollfd {
     int fd;             // 要监听的fd
     short int events;    // 要监听的事件类型
     short int revents;   // 实际发生的事件类型
 }

2.2、poll 函数

#include <poll.h>
 int poll(struct pollfd *fds, nfds_t nfds, int timeout);

2.3、poll 使用

  • 创建 pollfd 数组,向其中添加关注的 fd 信息,数组大小自定义
  • 调用 poll 函数,将 pollfd 数组拷贝到内核空间,转链表存储,无上限
  • 内核遍历 fd,判断是否就绪
  • 数据就绪或超时后,拷贝 pollfd 数组到用户空间,返回就绪 fd 数量 n
  • 用户进程判断 n 是否大于0,大于0则遍历 pollfd 数组,找到就绪的 fd

4.2、poll 问题

poll 利用链表解决了 select 中监听 fd 上限的问题,但仍需要遍历所有的 fd

3、epoll

3.1、数据结构

struct eventpoll {
     // ...
     struct rb_root rbr; // 红黑树,记录要监听的fd
     struct list_head rdllist; // 双向链表,记录已就绪的fd epoll_wait
     // ...
 };
 struct epitem {
     // ...
     struct rb_node rbn; // 红⿊树节点
     struct list_head rdllist; // 双向链表节点
     struct epoll_filefd ffd; // 事件句柄信息
     struct eventpoll *ep; // 指向所属的eventpoll对象
     struct epoll_event event; // 注册的事件类型
     // ...
 };
 struct epoll_event { 
     __uint32_t events; // epoll事件,每1位对应1个epol事件类型
     epoll_data_t data; // 用户数据 
 };
 typedef union epoll_data { 
     void *ptr;  // 指定与 fd 相关的用户数据 
     int fd;  // 指定事件从属的文件描述符,常用 
     uint32_t u32; 
     uint64_t u64; 
 } epoll_data_t;  //联合体,fd 与 ptr 只能使用其中1个

3.2、epoll 函数

epoll_create

创建 epoll 对象

int epoll_create1(int size)
 /*
 返回值:成功返回对应的 epfd ( eventpoll 结构体) ,失败返回 -1。
 参数 size:现无意义,必须大于0,一般填1,告知内核事件表有多大。
 */

epoll_ctl

将一个 fd 添加到 epoll 的红黑树中,并设置 ep_poll_callback,callback 触发时,把对应 fd 加入到rdlist 就绪列表中。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
 /*
 返回值:成功返回0,失败返回-1。
 参数:
 - 参数1 epfd:epoll对象
 - 参数2 op:指定操作类型。
   EPOLL_CTL_ADD 增加 
   EPOLL_CTL_MOD 修改 
   EPOLL_CTL_DEL 删除 
 - 参数3 fd:要监听的 fd
 - 参数4 event: 要监听的事件类型,红黑树键值对kv:fd-event
   event.events:
   EPOLLIN       可读
   EPOLLOUT      可写
   EPOLLET       边缘触发,默认⽔平触发LT
 */

epoll_wait

检测 rdlist 列表是否为空,不为空则返回就绪的 fd 的数量

// 收集 epoll 监控的事件中已经发生的事件,如果 epoll 中没有任何⼀个事件发⽣,则最多等待 timeout 毫秒后返回。
 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
 /*
 返回值:成功返回实际就绪的 fd 数量,失败返回-1
 参数:
 - 参数1 epfd:epoll 对象。
 - 参数2 events:用户态创建的evnet数组,内核拷贝就绪的 fd 到该数组中。
     events[i].events:
     EPOLLIN         触发读事件
     EPOLLOUT        触发写事件
     EPOLLERR        连接发生错误
     EPOLLRDHUP      连接读端关闭
     EPOLLHUP        连接双端关闭
 - 参数3 maxevents:可以返回的最⼤事件数目,一般设置为event数组的⻓度
 - 参数4 timeout:超时时间 ms。-1(一直等待),0(不等待),>0(等待时间)。断开与服务器没有交互的客户端
 */

3.3、epoll 原理

epoll_原理图


注:调用 epoll_create 会创建一个 epoll 对象。调用 epoll_ctl 添加到 epoll 中的事件都会与网卡驱动程序建立回调关系,相应事件触发时会调用回调函数(ep_poll_callback),将触发的事件拷贝到 rdlist 双向链表中。调用epoll_wait 将会把 rdlist 中就绪事件拷贝到用户态中;

调用 epoll_create 函数:返回一个epfd,同时内核会创建 eventpoll 结构体,该结构体的成员由红黑树和双向链表组成。红黑树:保存需要监听的描述符。双向链表:保存就绪的文件描述符。

调用 epoll_ctl 函数:对红黑树进行增添,修改、删除。 添加 fd 到红黑树上,从用户空间拷贝到内核空间。一次注册 fd,永久生效。内核会给每一个节点注册一个回调函数,当该 fd 就绪时,会主动调用自己的回调函数,将其加入到双向链表当中。

调用 epoll_wait 函数:内核监控双向链表,如果双向链表里面有数据,从内核空间拷贝数据到用户空间;如果没有数据,就阻塞。

3.4、epoll 事件

3.4.1、连接建立

接收连接

accept
 (epoll_event)ev.events |= EPOLLIN;
 epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);

主动连接

connect = -1 & errno = EINPROGRESS;
 (epoll_event)ev.events |= EPOLLOUT;
 epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

3.4.2、连接断开

客户端断开

1、客户端发送 FIN 包,客户端写端关闭
 2、服务端收到 FIN 包
     read = 0  
     ev.events & EPOLLRDHUB
 3、支持半关闭,关闭服务端读端
 4、不支持半关闭,close(fd)

服务端断开

1、shutdown(SHUT_WR),发送 FIN 包给客户端
 2、write = -1 && errno = EPIPE,写端关闭,但是可能收到数据,读端可能未关闭

3.4.3、消息到达

  • read = 0,收到客户端关闭的 FIN 包
  • read > 0,正常,处理相应的业务逻辑
  • read = -1
  • errno = EWOULDBLOCK,非阻塞 IO,读缓冲为空
  • errno = EINTER,系统调用被中断打断,重试
  • errno = ETIMEOUT,tcp 探活超时


3.4.4、消息发送完毕

  • write > 0,正常,处理相应的业务逻辑
  • write = -1
  • errno = EWOULDBLOCK,非阻塞 IO,写缓冲不够,注册写事件,等待下次发送
  • errno = EINTER,系统调用被中断打断,重试
  • errno = EPIPE,写端关闭


4、IO 多路复用总结

select总结

  • select 的底层实现:位图
  • 监控的 fd 数量有限,默认是1024
  • 每次调用 select 时,会发生用户空间到内核空间的数据拷贝,select返回时,又要从内核空间拷贝数据到用户空间
  • select 每次轮询检查就绪的描述符,即需要遍历所有的文件描述符
  • select 适用于并发量低,活动连接多的情况

epoll总结

  • epoll 的底层实现:红黑树 + 双向链表
  • 监控的 fd 数量没有限制,仅与内存大小有关 cat /proc/sys/fs/file-max
  • epoll 把需要监听的描述符加入红黑树,一次加入永久生效。内核会给每一个加入到红黑树上的文件描述符注册一个回调函数,当内核检测到就绪的文件描述符时,触发回调函数,将其加入到双向链表中。
  • epoll 每次只检查就绪链表,不需要遍历所有文件描述符,效率高
  • epoll_wait 适用于并发量高,活动连接少的情况

5、事件通知机制

5.1、LT 和 ET

当 fd 有数据可读时,调用 epoll_wait(select、poll)可以得到通知。事件通知的模式有两种:

  • 水平触发 LT:当内核读缓冲区非空,写缓冲区不满,则一直触发,直至数据处理完成。
  • 边缘触发 ET:当 IO 状态发生改变,触发一次。每次触发需要一次性把事件处理完。

LT 和 ET 的特性,决定了对于数据的读操作不同

  • LT + 一次性读
  • ET + 循环读
// lt + 一次性读,小数据
 ret = read(fd, buf, sizeof(buf));
 // et + 循环读,大数据
 while(true) {
     ret = read(fd, buf, sizeof(buf);
     // 此时,说明读缓冲区已经空了
     if (ret == EAGAIN || ret == EWOULDBLOCK) break;
 }

5.2、ET 的优势

  • ET 模式避免了 LT 模式可能出现的惊群现象(如:一个 listenfd 被多个 epoll 监听,一个调用accept 接受连接,其他 accept 阻塞)
  • ET 模式减少了 EPOLL 事件被重复触发的次数,效率高。

5.3、ET 的使用

ET 的使用:ET+ 非阻塞 IO + 循环读

循环读:若数据不能一次性处理完,只能等到下次数据到达网卡后才触发读事件。

非阻塞 IO:fcntl 函数可以将 fd 设置为非阻塞。

//修改(获取)文件描述符属性
 int fcntl(int fd, int cmd, ... /* arg */ );
 /*
 返回值:失败返回-1
 参数
 - 参数1:需要修改的文件描述符,
 - 参数2:修改(获取)文件描述符的操作
 */
 int flag = fcntl(fd, F_GETFL, 0);
 fcntl(fd, F_SETFL, O_NONBLOCK);

例如:将 recv 函数设置为非阻塞的两种方式

  • recv 函数的属性设置为MSG_DONWAIT
    ret = recv(newFd, buf, sizeof(buf)-1, MSG_DONTWAIT);
  • fcntl 函数将文件描述符设置为非阻塞性的。
    void setNoblock(int fd) {
    //1、获取原有套接字状态的信息
    int status = fcntl(fd, F_GETFL);
    //2、将非阻塞的标志与原有的标志信息做或操作
    status |= O_NONBLOCK;
    //3、将标志位信息写回到socket中
    fcntl(fd, F_SETFL, status);
    }

6、实例

6.1、TCP聊天

//server.c
 #include <func.h>
 int main(int argc,char*argv[]) {
     int sfd = socket(AF_INET, SOCK_STREAM, 0);
     ERROR_CHECK(sfd, -1, "socket");
     //保存本地的IP地址和端口号
     struct sockaddr_in serAddr;
     memset(&serAddr, 0, sizeof(serAddr));
     serAddr.sin_family = AF_INET;
     serAddr.sin_addr.s_addr = inet_addr(argv[1]);
     serAddr.sin_port = htons(atoi(argv[2]));
     //设置地址可重用
     int ret = 0;
     int reuse = 1;
     ret = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
     //绑定本机的IP和端口号到sfd上
     ret = bind(sfd, (struct sockaddr*)&serAddr, sizeof(serAddr));
     ERROR_CHECK(ret, -1, "bind");
     //监听
     ret = listen(sfd, 10);
     ERROR_CHECK(ret, -1, "listen");
     //创建epoll
     int epfd = epoll_create(1);
     ERROR_CHECK(epfd, -1, "epoll_create");
     struct epoll_event events, evs[3];
     memset(&events, 0, sizeof(events));
     //监听键盘
     events.events = EPOLLIN;
     events.data.fd = STDIN_FILENO;
     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &events);
     ERROR_CHECK(ret, -1, "epoll_ctl1");
     //监听客户端
     events.events = EPOLLIN;
     events.data.fd = sfd;
     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &events);
     ERROR_CHECK(ret, -1, "epoll_ctl2");
     char buffer[512] = { 0 };
     int readyNum = 0;
     int newFd = 0;
     while(1) {
         readyNum = epoll_wait(epfd, evs, 3, -1);
         ERROR_CHECK(readyNum, -1, "epoll_wait");
         for(int i = 0; i < readyNum; ++i) {
             //客户端请求建立连接
             if (evs[i].data.fd == sfd) {
                 //建立连接
                 newFd = accept(sfd, NULL, NULL);
                 ERROR_CHECK(newFd, -1, "accept");
                 puts("client connect\n");
                 //监听newFd,即客服
                 events.events = EPOLLIN;
                 events.data.fd = newFd;
                 ret = epoll_ctl(epfd, EPOLL_CTL_ADD, newFd, &events);
                 ERROR_CHECK(ret, -1, "epoll_ctl3");
             }
            //向客户端发送数据
             if (evs[i].data.fd == STDIN_FILENO) {
                 memset(buffer, 0, sizeof(buffer));
                 ret = read(STDIN_FILENO, buffer, sizeof(buffer));
                 ERROR_CHECK(ret, -1, "read");
                 ret = send(newFd, buffer, strlen(buffer)-1, 0);
                 ERROR_CHECK(ret, -1, "send");
             }
             //从客户端读取数据
             if (evs[i].data.fd == newFd) {
                 memset(buffer, 0, sizeof(buffer));
                 ret = recv(newFd, buffer, sizeof(buffer), 0);
                 ERROR_CHECK(ret, -1, "recv");
                 //当客户端发送完数据后,关闭连接
                 if (0 == ret) {
                     //关闭newFd后,newFd成为整型数值
                     //再次调用recv会报参数错误,返回-1
                     close(newFd);
                     continue;
                 }
                 //newFd关闭后,直接跳出循环,防止其他fd饥饿
                 if (-1 == ret) {
                     break;
                 }
                 printf("客户端: %s\n", buffer);
             }
         }
     }  
     close(sfd);
     close(newFd);
     return 0;
 }
 //client.c
 #include <func.h>
 int main(int argc,char*argv[]) {
     int sfd = socket(AF_INET, SOCK_STREAM, 0);
     ERROR_CHECK(sfd, -1, "socket");
     //保存本地的IP地址和端口号
     struct sockaddr_in serAddr;
     memset(&serAddr, 0, sizeof(serAddr));
     serAddr.sin_family = AF_INET;
     serAddr.sin_addr.s_addr = inet_addr(argv[1]);
     serAddr.sin_port = htons(atoi(argv[2]));
     //连接到服务器
     int ret = connect(sfd, (struct sockaddr*)&serAddr, sizeof(serAddr)); 
     ERROR_CHECK(ret, -1, "newFd");
     //创建epoll
     int epfd = epoll_create(1);
     ERROR_CHECK(epfd, -1, "epoll_create");
     struct epoll_event events, evs[2];
     memset(&events, 0, sizeof(events));
     //监听键盘
     events.events = EPOLLIN;
     events.data.fd = STDIN_FILENO;
     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &events);
     ERROR_CHECK(ret, -1, "epoll_ctl1");
     //监听服务器端
     events.events = EPOLLIN;
     events.data.fd = sfd;
     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &events);
     ERROR_CHECK(ret, -1, "epoll_ctl2");
     char buffer[512] = { 0 };
     int readyNum = 0;
     while(1) {
         readyNum = epoll_wait(epfd,evs,2,-1);
         ERROR_CHECK(readyNum, -1, "epoll_wait");    
         for(int i = 0; i < readyNum; ++i) {
             //向服务器端发送数据
             if(evs[i].data.fd == STDIN_FILENO) {
                 memset(buffer, 0, sizeof(buffer));
                 read(STDIN_FILENO, buffer, sizeof(buffer));
                 send(sfd, buffer, strlen(buffer)-1, 0);
                 ERROR_CHECK(ret, -1, "send");
             }
             //接收服务器端发来的数据
             if(evs[i].data.fd == sfd) {
                 memset(buffer, 0, sizeof(buffer));
                 ret = recv(sfd, buffer, sizeof(buffer), 0);
                 ERROR_CHECK(ret, -1, "recv");
                 if(ret == 0) {
                     continue;
                 }
                 printf("服务器: %s\n", buffer);
             }
         }
     } 
     close(sfd);
     return 0;
 }

6.2、UDP聊天

服务器端:socket - bind - while(1) {- recvfrom - sendto -} - close

//server.c  
 #include <func.h>
 int main(int argc,char*argv[]) {
     int sfd = socket(AF_INET, SOCK_DGRAM, 0);
     ERROR_CHECK(sfd, -1, "socket");
     //保存服务器的IP地址和端口号
     struct sockaddr_in serAddr;
     memset(&serAddr, 0, sizeof(serAddr));
     serAddr.sin_family = AF_INET;
     serAddr.sin_addr.s_addr = inet_addr(argv[1]);
     serAddr.sin_port = htons(atoi(argv[2]));
     //绑定本机的IP和端口号到sfd上
     int ret = bind(sfd, (struct sockaddr*)&serAddr, sizeof(serAddr));
     ERROR_CHECK(ret, -1, "bind");
     //创建epoll
     int epfd = epoll_create(1);
     ERROR_CHECK(epfd, -1, "epoll_create");
     struct epoll_event events, evs[3];
     memset(&events, 0, sizeof(events));
     //监听键盘
     events.events = EPOLLIN;
     events.data.fd = STDIN_FILENO;
     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &events);
     ERROR_CHECK(ret, -1, "epoll_ctl1");
     //监听客户端
     events.events = EPOLLIN;
     events.data.fd = sfd;
     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &events);
     ERROR_CHECK(ret, -1, "epoll_ctl2");
     char buffer[512] = { 0 };
     int readyNum = 0;
     //创建接收客户端IP和端口的结构体,才能知道客户的位置
     struct sockaddr_in cliAddr;
     socklen_t len = sizeof(cliAddr); 
     memset(&cliAddr, 0, sizeof(len));
     while(1) {
         readyNum = epoll_wait(epfd, evs, 2, -1);
         ERROR_CHECK(readyNum, -1, "epoll_wait");
         for(int i = 0; i < readyNum; ++i) {
             //收到来自客户端的数据
             if (evs[i].data.fd == sfd) {
                 puts("client connect\n");
                 memset(buffer, 0, sizeof(buffer));
                 ret = recvfrom(sfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&serAddr, &len); 
                 ERROR_CHECK(ret, -1, "recvfrom");
                 printf("客户端:%s\n", buffer);
             }
            //发送服务器端的数据
             if (evs[i].data.fd == STDIN_FILENO) {
                 memset(buffer, 0, sizeof(buffer));
                 ret = read(STDIN_FILENO, buffer, sizeof(buffer));
                 ERROR_CHECK(ret, -1, "read");
                 ret = sendto(sfd, buffer, strlen(buffer)-1, 0, (struct sockaddr*)&serAddr, len);
                 ERROR_CHECK(ret, -1, "sendto");
             }
         }
     }  
     close(sfd);
     return 0;
 }

客户端:socket - sendto - recvfrom - close

//client.c
 #include <func.h>
 int main(int argc,char*argv[]) {
     int ret = 0;
     int sfd = socket(AF_INET, SOCK_DGRAM, 0);
     ERROR_CHECK(sfd, -1, "socket");
     //保存服务器的IP地址和端口号
     struct sockaddr_in serAddr;
     memset(&serAddr, 0, sizeof(serAddr));
     socklen_t len = sizeof(serAddr);
     serAddr.sin_family = AF_INET;
     serAddr.sin_addr.s_addr = inet_addr(argv[1]);
     serAddr.sin_port = htons(atoi(argv[2]));
     //创建epoll
     int epfd = epoll_create(1);
     ERROR_CHECK(epfd, -1, "epoll_create");
     struct epoll_event events, evs[2];
     memset(&events, 0, sizeof(events));
     //监听键盘
     events.events = EPOLLIN;
     events.data.fd = STDIN_FILENO;
     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &events);
     ERROR_CHECK(ret, -1, "epoll_ctl1");
     //监听服务器端
     events.events = EPOLLIN;
     events.data.fd = sfd;
     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &events);
     ERROR_CHECK(ret, -1, "epoll_ctl2");
     char buffer[512] = { 0 };
     int readyNum = 0;
     while(1) {
         readyNum = epoll_wait(epfd, evs, 2, -1);
         ERROR_CHECK(readyNum, -1, "epoll_wait");    
         for(int i = 0; i < readyNum; ++i) {
             //先执行,将键盘输入的数据发送给服务器
             if(evs[i].data.fd == STDIN_FILENO) {
                 memset(buffer, 0, sizeof(buffer));
                 read(STDIN_FILENO, buffer, sizeof(buffer));
                 sendto(sfd, buffer, strlen(buffer)-1, 0, (struct sockaddr*)&serAddr, len);
                 ERROR_CHECK(ret, -1, "sendto");
             }
             //收到来自服务器的数据
             if(evs[i].data.fd == sfd) {
                 memset(buffer, 0, sizeof(buffer));
                 ret = recvfrom(sfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&serAddr, &len);
                 ERROR_CHECK(ret, -1, "recvfrom");
                 printf("服务器: %s\n", buffer);
             }
         }
     }   
     close(sfd);
     return 0;
 }
相关文章
|
2月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
95 0
|
7月前
|
存储 监控 Linux
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
1133 0
|
2月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
96 1
Linux C/C++之IO多路复用(aio)
|
2月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
33 0
Linux C/C++之IO多路复用(poll,epoll)
|
2月前
|
Java Linux
【网络】高并发场景处理:线程池和IO多路复用
【网络】高并发场景处理:线程池和IO多路复用
50 2
|
2月前
|
监控 网络协议 Java
IO 多路复用? 什么是 IO 多路复用? 简单示例(日常生活)来解释 IO 多路复用 一看就懂! 大白话,可爱式(傻瓜式)教学! 保你懂!
本文通过日常生活中的简单示例解释了IO多路复用的概念,即一个线程通过监控多个socket来处理多个客户端请求,提高了效率,同时介绍了Linux系统中的select、poll和epoll三种IO多路复用的API。
145 2
|
3月前
|
消息中间件 NoSQL Java
面试官:谈谈你对IO多路复用的理解?
面试官:谈谈你对IO多路复用的理解?
52 0
面试官:谈谈你对IO多路复用的理解?
|
3月前
|
网络协议 Java Linux
高并发编程必备知识IO多路复用技术select,poll讲解
高并发编程必备知识IO多路复用技术select,poll讲解
|
5月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
249 2
|
5月前
|
存储 Java Unix
(八)Java网络编程之IO模型篇-内核Select、Poll、Epoll多路复用函数源码深度历险!
select/poll、epoll这些词汇相信诸位都不陌生,因为在Redis/Nginx/Netty等一些高性能技术栈的底层原理中,大家应该都见过它们的身影,接下来重点讲解这块内容。