Linux高并发学习----一请求一线程/select/poll/epoll基本使用

1、概述

作为服务端,同时支持多用户连接是必然要求,在刚开始学习网络编程时,咱们所想到的几种常见用法如下:
1、一个请求对应一个线程:即给每一个新连接用户分配一个新线程,在该线程处理业务,这种情况显然只适用于很小规模连接的场景,毕竟线程资源是有限的,一般的pc能开到几百个线程就不错了。
2、select:能够将大量事件存入对应集合中,只需要遍历集合,处理不同类型事件即可,但是支持的socket连接有限,一般是1024。
3、poll:类似于select,但是处理事件时更加方便,不需要设置多个事件集合,且支持的socket连接较select也大幅提升。
4、epoll:让Linux成为服务器端开发主战场的得力功臣,支持单机百万并发连接,相较于select/poll,不需要对事件集合一一遍历(有些连接处于就绪态,没有事件触发),效率很高。

2 、一请求一线程

在这种情况下,每当accept一个连接,则创建一个新线程,在线程中处理用户事件即可,下面的代码省略了socket从创建到listen的阶段。

    for(;;)
    {
         connfd = accept(listenfd, (struct sockaddr*)&client_adr, &client_adr_lenth);
         pthread_t threadId;
         pthread_create(&threadId, NULL, threadFunc, (void*)&connfd);
    }
    close(listenfd);

对应的线程入口函数如下:

void *threadFunc(void *args)
{
     int cntFd = *(int *)args;
     if(cntFd > 0)
          printf("recv a connection! clientfd = %d\n", cntFd);
     else
     {
          printf("accept error!\n");
          exit(1);
     }
     while (1)
     {
          int n = recv(cntFd, buf, 256, 0);
          if(n > 0)
          {
               printf("recv : %s\n", buf);
               write(cntFd, str, strlen(str));
          }
          else
          {
               printf("对端fd = %d 断开连接!\n", cntFd);
               close(cntFd);
               break;
          }   
     }   
}

3、select

同样省略socket从创建到listen的阶段,给出select示例代码如下:

fd_set rfds, rset, wfds, wset;
	FD_ZERO(&rfds);
	FD_SET(listenfd, &rfds);
	FD_ZERO(&wfds);
	int max_fd = listenfd;
	while (1) 
     {
		rset = rfds;
		wset = wfds;
          // rset是读事件集合,wset是写事件集合,后续事件处理从对应集合中读取即可
		int nready = select(max_fd+1, &rset, &wset, NULL, NULL);  
		if (FD_ISSET(listenfd, &rset))  // 首先处理完成三次握手的连接
          { 
			struct sockaddr_in client;
		     socklen_t len = sizeof(client);
               if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) 
               {
                    printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                    return 0;
               }
			FD_SET(connfd, &rfds);
			if (connfd > max_fd) max_fd = connfd;
			if (--nready == 0) continue;
		}

		int i = 0;
		for (i = listenfd + 1;i <= max_fd;i ++) 
          {
               if (FD_ISSET(i, &rset)) 
               { 
                    n = recv(i, buff, MAXLNE, 0);
                    if (n > 0) 
                    {
                         buff[n] = '\0';
                         printf("recv msg from client fd = %d: %s\n", i, buff);
                         FD_SET(i, &wfds);
                         send(i, buff, n, 0);
                    } 
                    else if (n == 0) 
                    { 
                         FD_CLR(i, &rfds);
                         printf("对端断开连接!fd = %d\n", i);
                         close(i);				
                    }
                    if (--nready == 0)  break;
               }
		}
	}

4、poll

	 struct pollfd pollfds[MAX_POLL_SIZE]; // 存放发生的事件集合
     pollfds[0].fd = listenfd;
     pollfds[0].events = POLLIN;
     pollfds[0].revents = 0;
     int maxfd = listenfd;

     for(int i = 1; i < MAX_POLL_SIZE; ++i)
          pollfds[i].fd = -1;

     while(1)
     {
          int nready = poll(pollfds, maxfd+1, -1);
          if(nready < 0)
          {
               if(errno == EINTR)  continue;
               break;
          }
          else if(nready == 0) // 超时
               continue;

          if(pollfds[0].revents & POLLIN)   // 完成三次握手的连接
          {
               struct sockaddr_in clientAddr;
               socklen_t len = sizeof(clientAddr);
               connfd = accept(listenfd, (struct sockaddr*)&clientAddr, &len);
               if(fcntl(connfd, F_SETFL, O_NONBLOCK) == -1)
               {
                    printf("fcntl() fail!\n");
                    return -1;
               }
               pollfds[connfd].fd = connfd;
               pollfds[connfd].events = POLLIN;
               pollfds[connfd].revents = 0;
               if(connfd > maxfd)  maxfd = connfd;
               
               printf("new connnection : %d accepted!\n", connfd);
               if(--nready == 0)   continue;
          }

          for(int i = listenfd; i <= maxfd; ++i)
          {
               if(pollfds[i].revents & POLLIN)
               {
                    // 有数据到达
                    int n = recv(i, buf, 256, 0);
                    if(n == 0)
                    {
                         printf("对端 %d 断开连接!\n", i);
                         pollfds[i].fd = -1;
                         close(i);
                    }
                    else if(n < 0)
                    {
                         if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
                         {
                              printf("异常错误! errno = %d:%s\n", errno, strerror(errno));
                              return -1;
                         }
                    }
                    else
                    {
                         buf[n] = '\0';
                         printf("recv msg: %s\n", buf);
                         send(i, buf, n, 0);
                    }
                    if(--nready == 0) break;
               }
          }
     }

5、epoll

     int epollfd = epoll_create(1);  
     struct epoll_event events[512], ev;
     ev.events = EPOLLIN;
     ev.data.fd = listenfd;
     epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);
     while(1)
     {
          int nready = epoll_wait(epollfd, events, 512, -1);
          if(nready == -1)
               continue;    
          for(int i = 0; i < nready; ++i)
          {
               int clientfd = events[i].data.fd;
               if(clientfd == listenfd)  // 完成三次握手的连接
               {
                    struct sockaddr_in clientAdr;
                    socklen_t len = sizeof(clientAdr);
                    connfd =  accept(clientfd, (struct sockaddr*)&clientAdr, &len);
                    printf("new connection: %d\n", connfd);
                    ev.data.fd = connfd;
                    ev.events = EPOLLIN;
                    epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev);
               }
               else
               {
                    if(events[i].events & EPOLLIN) // 读事件
                    {
                         printf("recv:\n");
                         int n = recv(clientfd, buf, 256, 0);
                         if(n == 0)
                         {
                              printf("client fd = %d closed!\n", clientfd);
                              ev.events = EPOLLIN;
                              ev.data.fd = clientfd;
                              epoll_ctl(epollfd, EPOLL_CTL_DEL, clientfd, &ev);
                              close(clientfd);
                         }
                         else
                         {
                              buf[n] = '\0';
                              printf("%s\n", buf);
                              send(clientfd, buf, n, 0);
                         }
                    }
               }
          }
     }

6、后续

本篇文章使用了几种最基本的支持多连接的情况,下一篇文章将介绍使用epoll的reactor写法。
代码中有问题的欢迎评论区留言哈!

上一篇:sellect 、poll 、epoll


下一篇:io