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写法。
代码中有问题的欢迎评论区留言哈!