Linux c++(socket网络通信 & select)

优点

  • 跨平台
  • 缺点
    • 文件描述符1024的限制,由于FD_SETSIZE的限制,只是返回变化的文件描述符的个数,具体哪个变化需要遍历,每次都需要将需要监听的文件描述集合由应用层拷贝到内核
  • 总结: 若大量并发,少量活跃,select效率低

提问

  • 假设现在4-1023个文件描述符需要监听,但是5-1000这些文件描述关闭了?
  • 假设现在4-1023个文件描述符需要监听,但是只有5,1002发来消息? 无解

IO多路转接([select] | [poll] | [epoll])

  • int select (
    int nfds, //要检测的文件描述符中最大的fd+1 [1024]
    fd_set* readfds, //读集合
    fd_set* writefds, // 写集合
    fd_set* exceptfds, //异常集合 [NULL]
    struct timeval* timeout // [1] NULL永久阻塞 [2]当检测到fd变化的时候返回
    );
  • 返回变换文件描述符的个数
  • 全部清空
    • void FD_ZERO(fd_set* set);
  • 从集合中删除某一项
    • void FD_CLR(int fd,fd_set* set);
  • 将某个文件描述符添加到集合
    • void FD_SET(int fd,fd_set* set);
  • 判断某个文件描述符是否在集合中
    • int FD_ISSET(int fd,fd_set* set);

使用select函数的优缺点

  • 优点
    • 跨平台
  • 缺点
    • 每次调用select, 都需要把fd集合从用户态考备到内核态,这个开销在fd很多时候会很大
    • 同时每次调用select都需要在哪和便利传递进来的所有fd,这个开销在fd很多时候也很大
    • select支持的文件描述符数量太小了,默认时1024
#include <stdio.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
       
int main(int argc,char *argv[])
{          
    if(argc< 2)
    {       
        printf("eg: ./app port");
        exit(1);
    }     
    struct sockaddr_in serv_addr;
    socklen_t serv_len = sizeof(serv_addr);
    int port = atoi(argv[1]);
    //创建套接字
    int lfd = socket(AF_INET,SOCK_STREAM,0);
    //初始化服务器
    memset(&serv_addr,0,serv_len);
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(port);
    //绑定IP和端口
    bind(lfd,(struct sockaddr*)&serv_addr,serv_len);
    //设置同时监听的最大个数
    listen(lfd,36);
    printf("Start accept ......\n");
       
    struct sockaddr_in client_addr;
    socklen_t cli_len = sizeof(client_addr);
    // 最大的文件描述符
    int maxfd = lfd;
    // 文件描述符读集合
    fd_set reads,temp; 
    // init    
    FD_ZERO(&reads);
    FD_SET(lfd,&reads);
    while(1)
    {    
       //委托内核IO检测                                                                                  
       temp = reads;
       int  ret = select(maxfd+1,&temp,NULL,NULL,NULL);
       if(ret == -1) 
       {   
            perror("select error");
            break;
       } else if(ret == 0){
          continue;
      }
       // 有客户端发起新连接
       if (FD_ISSET(lfd,&temp)) {
           //接收连接请求 accept不阻塞
           int cfd = accept(lfd,(struct sockaddr*)&client_addr,&cli_len);
           if(cfd == -1) 
           {   
               perror("accept error");
               exit(1);
           }   
           char ip[64];
           printf("new client IP: %s,Port: %d\n",
                  inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip,sizeof(ip)),
                  ntohs(client_addr.sin_port));
           // 将cfd加入到带检测的读集合中 下一次就可以检测到了
           FD_SET(cfd,&reads);
           //更新最大的文件描述符
           maxfd = maxfd < cfd?cfd:maxfd;
        // 如果只有lfd变化
          if(--ret == 0)
                continue;
       }   
       //已经连接的客户端给我发送数据
       for (int i =lfd+1; i <= maxfd; ++i) {                                                                                               
           if(FD_ISSET(i,&temp))
           {   
               char buf[1024] = {0};
               int len = recv(i,buf,sizeof(buf),0);
               if(len < 0) 
               {   
                   perror("recv error");
                   close(i);
                    FD_CLR(i,&reads);
               }else if(len == 0)
               {   
                   printf("客户端已经断开连接\n");
                   close(i);
                   //从读集合中删除
                   FD_CLR(i,&reads);
               }else{
                    printf("recv buf: %s\n",buf);
                    send(i,buf,len,0);
               }   
           }   
       }   
    }   
    close(lfd);
    return 0;
}

超时处理

if (wait_seconds > 0)
	{
		fd_set accept_fdset;
		struct timeval timeout;
		FD_ZERO(&accept_fdset);
		FD_SET(m_lfd, &accept_fdset);
		timeout.tv_sec = wait_seconds;
		timeout.tv_usec = 0;
		do
		{
			// 检测读集合
			ret = select(m_lfd + 1, &accept_fdset, NULL, NULL, &timeout);
		} while (ret < 0 && errno == EINTR);	// 被信号中断, 再次进入循环
		if (ret <= 0)
		{
			return NULL;
	}
}

// 一但检测出 有select事件发生,表示对等方完成了三次握手,客户端有新连接建立

posted on 2021-05-09 17:42  lodger47  阅读(589)  评论(0)    收藏  举报

导航