新手入门socket编程——io多路复用


这是一篇服务器socket通信的文章

socket是什么

socket的定义

socket,也就是套接字,它是一种计算机之间进行通信的约定或方式
在我的理解里,它是一个封装了TCP/IP以及其他网络协议族中许多函数的接口,例如:listen,accept,recv,send等函数。它通过绑定一个端口和IP地址来进行网络通信。
本文我们以在服务端生成一个回声网络通信的代码实现来了解socket网络通信。

io多路复用

基本概念

在多并发的情况下,io多路复用是通过同时监视多个文件描述符来高效管理。相比于传统的io模型,它提高了系统对于多并发问题的处理能力。

常用的io多路复用机制

select

最早的io多路复用机制之一,通过select函数传递文件描述符集合给内核,从而实现对多个文件描述符状态进行监视。select在可监视的文件数量和效率上性能不足。

poll

poll是在select的基础上产生的,将select函数中的三个参数:read,write,error改为一个结构体数组参数,使传递更加方便。poll 使用 pollfd 结构数组来保存需要监视的文件描述符以及事件,并将数组传递给内核。它没有文件描述符数量的限制,但在文件描述符较多时,效率会比较低。

epoll

epoll 是在 select 和 poll 基础上的进一步改进。它提供更高的性能和可伸缩性,适用于大量文件描述符的高并发场景。使用 epoll_create 创建一个 epoll 实例,通过 epoll_ctl 向其中添加或删除文件描述符,最后通过 epoll_wait 获取就绪的文件描述符。

server端io多路复用代码

main函数中

       int sockfd = socket(AF_INET, SOCK_STREAM, 0);

创建一个套接字,并分配一个文件描述符,参数分别表示为使用IPv4地址,使用流式套接字,自动选择合适协议。

       struct sockaddr_in serveraddr;
       memset(&serveraddr, 0, sizeof(struct sockaddr_in));

定义一个服务器地址的结构体变量并进行初始化。

       serveraddr.sin_family = AF_INET;
       serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
       serveraddr.sin_port = htons(2047);

对该服务器的参数进行设置,网络地址类型为IPv4,接受任意接口的连接,端口号为2047.

       if (bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr == -1))) {
           perror("bind error");
           return -1;
       }
       listen(sockfd, 10);

将套接字的文字描述符与服务器地址绑定,然后开始监听,最多允许10个连接处理。

select函数

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select函数

      fd_set rfd,rset;
      FD_ZERO(&rfd);
      FD_SET(sockfd,&rfd);
      int maxfd=sockfd;

设置两个文件描述符集合,将其中的rfd集合初始化,并将之前监听的sockfd加入,设置集合最大文件描述符为sockfd。

      while(1){
rset=rfd;
int nREADY=select(maxfd+1,&rset,NULL,NULL,NULL);
if(FD_ISSET(sockfd, &rset)){
          struct sockaddr_in clientaddr;
           socklen_t len =sizeof(clientaddr);
          int clientfd=accept(sockfd(structsockaddr*)&clientaddr,&len);
           printf("clientfd:%d\n",clientfd);
           FD_SET(clientfd,&rfd);
            maxfd=clientfd;
   }

进入循环,将rfd复制给rset。select每次循环会将rset集合从用户空间复制到内核空间进行选择(这会改变rset集合),然后将接收到的clientfd记录到rfd中,之后在下次循环又将新的rfd集合复制给rset。每次接收一个clientfd就将其写到rfd中,也就变成了对多个文件描述符形成的集合进行监听。

     int i=0;
     for(i=sockfd+1;i<=maxfd;i++){
    printf("maxfd\n");
     if(FD_ISSET(i, &rset)){
     char buffer[128]={0};
      int count=recv(clientfd,buffer,128,0);
       if(count==0){
        break;}
       send(i,buffer,count,0);
       printf("clientfd: %d,count:%d,buffer:%s\n",i,,count,buffer);
 }
}

这个循环用于对clientfd,也就是客户端发过来的数据进行接收和回声发送。

poll函数

int poll(struct pollfd fds[], nfds_t nfds, int timeout);

将select中的中间三个参数读、写、异常装在poll的结构体数组中

    struct pollfd fds[1024]={0};
    fds[sockfd].fd=sockfd;
    fds[sockfd].events=POLLIN;
    int maxfd=sockfd;

创建结构体数组,存储文件描述符和事件

   while(1)
   {
       int nready=poll(fds,maxfd+1,-1);
     if(fds[sockfd].revents & POLLIN){
        struct sockaddr_in clientaddr;
        socklen_t len =sizeof(clientaddr);
        int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
        printf("clientfd:%d\n",clientfd);
        fds[sockfd].fd=clientfd;
         fds[sockfd].events=POLLIN;
         maxfd=clientfd;
             }

进入无限循环,使用poll函数监视文件描述符事件,返回就绪状态的文件描述符数量。如果是就绪状态,获得请求后创建clientfd并更新fds和maxfd。

        int i=0;
        for(i=sockfd+1;i<=maxfd;i++){
        if(fds[i].revents & POLLIN){
                char buffer[128]={0};
                int count=recv(i,buffer,128,0);
                if(count==0){
                printf("disconnect\n");
                fds[i].fd= -1;
                fds[i].events=0;
                close(i);
                continue;}
                send(i,buffer,count,0);
                printf("clientfd: %d,count:%d,buffer:%s\n",i,count,buffer);
             }
         }
    }

遍历在就绪集合中的文件描述符,完成接收和发送工作。并在接受完数据后,将结构体数组中的变量去除,避免重复检测、收发。

epoll

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epoll有三个函数,分别是创建epoll实例,对epoll实例中的文件描述符里的事件进行操作,等待事件以及返回就绪事件。

int epfd=epoll_create(1);
 struct epoll_event ev;
 ev.events=EPOLLIN;
 ev.data.fd=sockfd;
 epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);
 struct epoll_event events[1024]={0};

创建epoll实例epfd,时间结构体ev,设置监听sockfd并加入到epfd中。

 while(1){
      int nready= epoll_wait(epfd,events,1024,-1);

进入循环,获取就绪事件数量

      int i=0;
      for(i=0;i<nready;i++){
       int connfd=events[i].data.fd;
       if(sockfd==connfd){
        struct sockaddr_in clientaddr;
        socklen_t len =sizeof(clientaddr);
        int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
        ev.events=EPOLLIN |EPOLLET;
        ev.data.fd=clientfd;
        epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
        printf("clientfd:%d\n",clientfd);
           }

遍历就绪事件,将i的文件描述符设为connfd,将请求的客户端文件描述符clientfd中的事件状态设为可读,并将clientfd加入到epfd中

else if(events[i].events & EPOLLIN){
           char buffer[10]={0};
            int count=recv(connfd,buffer,10,0);
            if(count==0){
            printf("disconnect\n");
            epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
            close(i);
            continue;}
            send(connfd,buffer,count,0);
            printf("clientfd: %d,count:%d,buffer:%s\n",connfd,count,buffer);
           }
        }
 }

如果显示这是这是一个就绪事件并且是可读的,则接收数据并在epfd中将connfd删除,最后输出数据。

  • 43
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值