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