TCP 并发服务器构建
单循环服务器与并发服务器
单循环服务器:服务端同一时刻只能处理一个客户端的任务。
并发服务器:服务端同一时刻可以处理多个客户端的任务。
TCP 并发服务器构建
1. TCP 建立连接:一对一 TCP 服务端并发模型
1.1 多进程
进程资源开销大;
安全性高。
1.2 多线程
线程相对于进程资源开销小;
在相同资源环境下,并发量比进程大。
1.3 线程池
目的:解决多线程或多进程模型中,**频繁创建和销毁线程(进程)**带来的时间消耗问题;
基于 生产者—消费者模型 和 任务队列 实现的一套多线程框架。
1.4 IO 多路复用
IO:文件描述符(fd);
特点:在不创建新的进程和线程的前提下,使用一个进程实现对多个文件读写的同时监测。
阻塞 IO 模式
fgets(stdin);
recv(connfd);
多个任务之间表现为同步的效果。
常见实现方式
select
poll
epoll
2. select 实现 IO 多路复用
2.1 实现步骤
创建文件描述符集合:
fd_set
添加关注的文件描述符到集合:
FD_SET()
使用
select
将集合表传递给内核,内核开始监测事件当内核监测到事件时,应用层
select
将解除阻塞,并获得相关的事件结果根据
select
返回的结果做不同的任务处理
2.2 常用操作函数
void FD_CLR(int fd, fd_set *set); // 从集合中移除文件描述符
int FD_ISSET(int fd, fd_set *set); // 判断文件描述符是否在集合中
void FD_SET(int fd, fd_set *set); // 将文件描述符加入集合
void FD_ZERO(fd_set *set); // 清空集合
2.3 select 函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
功能:
传递文件描述符集合表给内核,并等待获取事件结果。
参数:
nfds
:关注的最大文件描述符 + 1;
readfds
:读事件的文件描述符集合;
writefds
:写事件的文件描述符集合;
exceptfds
:其他事件的文件描述符集合;
timeout
:设置 select
监测的超时时间
NULL
:不设置超时时间(select
一直阻塞等待)。
返回值:
成功:返回内核监测到的事件个数;
失败:返回 -1
;
返回 0
:超时时间到达,但没有事件发生。
#include <stdio.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>#define SER_PORT 50000
#define SER_IP "192.168.0.179"int init_tcp_ser()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket error");return -1;}struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(SER_PORT);seraddr.sin_addr.s_addr = inet_addr(SER_IP);int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if (ret < 0){perror("bind error");return -1;}ret = listen(sockfd, 100);if (ret < 0){perror("listen error");return -1;}return sockfd;
}int main(int argc, const char *argv[])
{char buff[1024] = {0};struct sockaddr_in cliaddr;socklen_t clilen = sizeof(cliaddr);int sockfd = init_tcp_ser();if (sockfd < 0){return -1;}//1. 创建文件描述符集合fd_set rdfds;fd_set rdfdstmp;FD_ZERO(&rdfds);//2. 添加关注的文件描述符到集合FD_SET(sockfd, &rdfds);int maxfd = sockfd;while (1){rdfdstmp = rdfds;//3. 传递集合到内核,并等待返回监测结果int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);if (cnt < 0){perror("select error");return -1;}//4. 是否有监听套接字事件到达 ----》三次握手已完成,可以acceptif (FD_ISSET(sockfd, &rdfdstmp)){int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);if (connfd < 0){perror("accept error");return -1;}FD_SET(connfd, &rdfds);maxfd = maxfd > connfd ? maxfd : connfd;}//5. 是否有通讯套接字事件到达for (int i = sockfd+1; i <= maxfd; i++){if (FD_ISSET(i, &rdfdstmp)){memset(buff, 0, sizeof(buff));ssize_t cnt = recv(i, buff, sizeof(buff), 0);if (cnt < 0){perror("recv error");FD_CLR(i, &rdfds);close(i);continue;}else if (0 == cnt){FD_CLR(i, &rdfds);close(i);continue;}printf("%s\n", buff);strcat(buff, "--->ok");cnt = send(i, buff, strlen(buff), 0);if (cnt < 0){perror("send error");FD_CLR(i, &rdfds);close(i);continue;}}}}close(sockfd);return 0;
}