TCP并发服务器构建
一、服务器
单循环服务器:服务端同一时刻只能处理一个客户端的任务(TCP)
并发服务器:服务端同一时刻可以处理多个客户端的任务(UDP)
二、TCP服务端并发模型

1、多进程
进程资源开销大,安全性高。
以下是一个实现多进程TCP服务端代码:
cs
#define SER_PORT 62300
#define SER_IP "192.168.0.165"
int init_tcp_ser()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket error");
return 0;
}
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)
{
printf("bind error");
return -1;
}
int cnt = listen(sockfd, 100);
if(cnt < 0)
{
perror("listen error");
return -1;
}
return sockfd;
}
void wait_handler(int signo)
{
wait(NULL);
}
int main(int argc, char *argv[])
{
signal(SIGCHLD, wait_handler);
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int sockfd = init_tcp_ser();
if(sockfd < 0)
{
perror("init_tcp_ser error");
return -1;
}
while(1)
{
int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
if(connfd < 0)
{
perror("accept error");
return -1;
}
pid_t pid = fork();
if(pid > 0)
{
}
else if(pid == 0)
{
char buff[1024] = {0};
while(1)
{
memset(buff, 0, sizeof(buff));
ssize_t ret = recv(connfd, buff, sizeof(buff), 0);
if(ret < 0)
{
perror("send error");
return -1;
}
else if(ret == 0)
{
printf("[%s : %d]: offline!\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
break;
}
printf("[%s : %d]: %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);
strcat(buff, "----OK");
ssize_t cnt = send(connfd, buff, strlen(buff), 0);
if(cnt < 0)
{
perror("send error");
break;
}
}
close(connfd);
}
}
close(sockfd);
return 0;
}
2、多线程
线程相对与进程资源开销小,相同资源环境下,并发量比进程大。
以下是一个实现多线程TCP服务端代码:
cs
#include "head.h"
#define SER_PORT 62300
#define SER_IP "192.168.0.165"
int init_tcp_ser()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket error");
return 0;
}
//允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:
int optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
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)
{
printf("bind error");
return -1;
}
int cnt = listen(sockfd, 100);
if(cnt < 0)
{
perror("listen error");
return -1;
}
return sockfd;
}
void *task(void *sug)
{
int connfd = *(int *)sug;
char buff[1024] = {0};
while(1)
{
memset(buff, 0, sizeof(buff));
ssize_t ret = recv(connfd, buff, sizeof(buff), 0);
if(ret < 0)
{
perror("send error");
break;
}
else if(ret == 0)
{
printf("[%s : %d]: offline!\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
break;
}
printf("[%s : %d]: %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);
strcat(buff, "----OK");
ssize_t cnt = send(connfd, buff, strlen(buff), 0);
if(cnt < 0)
{
perror("send error");
break;
}
}
close(connfd);
}
int main(int argc, char *argv[])
{
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int sockfd = init_tcp_ser();
if(sockfd < 0)
{
perror("init_tcp_ser error");
return -1;
}
while(1)
{
int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
if(connfd < 0)
{
perror("accept error");
return -1;
}
pthread_t tid;
int fd = pthread_create(&tid, NULL, task, &connfd);
if(fd < 0)
{
perror("thread create error");
return -1;
}
pthread_detach(tid);
}
close(sockfd);
return 0;
}
3、线程池
为了解决多线程或者多进程模型,在服务器运行过程,频繁创建和销毁线程(进程)带来的时间消耗问题。
基于生产者和消费者编程模型,以及任务队列等,实现的一套多线程框架。

4. IO多路复用
1)概念
对多个文件描述符的读写可以复用一个进程。
在不创建新的进程和线程的前提下,使用一个进程实现对多个文件读写的同时监测。
阻塞IO模式:
2)方式
实现方式有 select、poll、epoll 三种。
5、select 实现IO多路复用
1)实现步骤
(1)创建文件描述符集合 fd_set
(2)添加关注的文件描述符到集合 FD_SET
(3)使用 select 传递集合表给内核,内核开始监测事件 select()
(4)当内核监测到事件时,应用层select将解除阻塞,并获得相关的事件结果
(5)根据 select 返回的结果做不同的任务处理
2)select 的宏
将集合清0:void FD_ZERO(fd_set *set);
向集合中添加文件描述符:void FD_SET(int fd, fd_set *set);
从集合中移除文件描述符:void FD_CLR(int fd, fd_set *set);
检查文件描述符是否在集合中:int FD_ISSET(int fd, fd_set *set);
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 : 超时时间到达,但没有事件发生,则返回0 |
4)应用示例
(1)通过select函数构建多路复用,实现管道和终端的读
管道向服务端写的代码:
cs
int main(void)
{
mkfifo("./myfifo", 0664);
int fd = open("./myfifo", O_WRONLY);
if(fd < 0)
{
perror("open error");
return -1;
}
while(1)
{
write(fd, "hello world", 11);
sleep(1);
}
close(fd);
return 0;
}
读的代码:
cs
int main(void)
{
char buff[1024] = {0};
mkfifo("./myfifo", 0664);
int fifofd = open("./myfifo", O_RDONLY);
if(fifofd < 0)
{
perror("open error");
return -1;
}
//1、创建文件描述符集合
fd_set rdfds;
fd_set rdfdstmp;
//2、清空文件描述符集合表
FD_ZERO(&rdfds);//初始化...集
//3、添加关注的文件描述符到集合中
FD_SET(0, &rdfds);//添加
int maxfd = 0;
FD_SET(fifofd, &rdfds);
maxfd = maxfd > fifofd ? maxfd : fifofd;
while(1)
{
rdfdstmp = rdfds;
//4、传递集合表给内核并等待返回到达事件的结果
int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);
if(cnt < 0)
{
perror("select error");
return -1;
}
if(FD_ISSET(0, &rdfdstmp))//检查文件描述符0是否在集合中
{
fgets(buff, sizeof(buff), stdin);//0
printf("STDIN: %s\n", buff);
}
if(FD_ISSET(fifofd, &rdfdstmp))
{
memset(buff, 0, sizeof(buff));
read(fifofd, buff, sizeof(buff));
printf("FIFO: %s\n", buff);
}
}
close(fifofd);
return 0;
}
(2)使用select实现TCP服务端IO多路复用代码
cs
#define SER_PORT 50000
#define SER_IP "192.168.0.165"
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. 是否有监听套接字事件到达 ----》三次握手已完成,可以accept
if (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;
}
【END】