1.TCP服务端并发模型
(1)多进程
进程资源开销大,安全性高。
cs
#include "head.h"
#define SIN_PORT 50000
#define SIN_ADDR "192.168.0.176"
int init_tcp_ser()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket error");
return -1;
}
//允许绑定处于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(SIN_PORT);
seraddr.sin_addr.s_addr = inet_addr(SIN_ADDR);
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;
}
void wait_handler(int signo)
{
wait(NULL);
}
int main(int argc, char const *argv[])
{
signal(SIGCHLD, wait_handler);
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int sockfd = init_tcp_ser();
if(sockfd < 0)
{
return -1;
}
while(1)
{
int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
if(connfd < 0)
{
perror("connfd error");
return -1;
}
pid_t pid = fork();
if(pid > 0)
{
}
else if(0 == pid)
{
char buff[1024] = {0};
while(1)
{
memset(buff, 0, sizeof(buff));
ssize_t cnt = recv(connfd, buff, sizeof(buff),0);
if(cnt < 0)
{
perror("recv error");
return -1;
}
else if(0 == cnt)
{
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");
cnt = send(connfd, buff, strlen(buff), 0);
if(cnt < 0)
{
perror("send error");
return -1;
}
}
close(connfd);
}
}
close(sockfd);
return 0;
}
(2)多线程
线程相对于进程资源开销小(相同资源环境下),并发量比进程大。
cs
#include "head.h"
#define SIN_PORT 50000
#define SIN_ADDR "192.168.0.176"
int sockfd = -1;
struct addr
{
int connfd;
struct sockaddr_in cliaddr;
socklen_t clilen;
}; // 移除全局实例addr_t,避免共享冲突
int init_tcp_ser()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket error");
return -1;
}
//允许绑定处于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(SIN_PORT);
seraddr.sin_addr.s_addr = inet_addr(SIN_ADDR);
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;
}
void *task(void *arg)
{
// 正确解析参数,获取当前连接的客户端信息
struct addr *client_addr = (struct addr *)arg;
int connfd = client_addr->connfd;
struct sockaddr_in cliaddr = client_addr->cliaddr;
char buff[1024] = {0};
while (1)
{
memset(buff, 0, sizeof(buff));
ssize_t cnt = recv(connfd, buff, sizeof(buff)-1, 0); // 预留1字节防止溢出
if(cnt < 0)
{
perror("recv error");
break;
}
else if(0 == cnt)
{
// 使用当前连接的客户端信息,而非全局变量
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);
// 使用snprintf替代strcat,防止缓冲区溢出
snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), "----->ok");
cnt = send(connfd, buff, strlen(buff), 0);
if(cnt < 0)
{
perror("send error");
break;
}
}
close(connfd);
free(client_addr); // 释放动态分配的结构体
return NULL;
}
int main(int argc, char const *argv[])
{
sockfd = init_tcp_ser();
if(sockfd < 0)
{
return -1;
}
while(1)
{
// 为每个连接创建独立的结构体,避免共享冲突
struct addr *client_addr = malloc(sizeof(struct addr));
client_addr->clilen = sizeof(client_addr->cliaddr);
client_addr->connfd = accept(sockfd, (struct sockaddr *)&client_addr->cliaddr, &client_addr->clilen);
if(client_addr->connfd < 0)
{
perror("accept error");
free(client_addr); // 出错时释放内存
return -1;
}
pthread_t tid;
// 传递当前连接的结构体指针,而非全局变量
int ret = pthread_create(&tid, NULL, task, client_addr);
if(ret != 0)
{
perror("pthread_create error");
close(client_addr->connfd);
free(client_addr); // 线程创建失败时释放内存
continue;
}
// 将线程设置为分离状态,结束后自动回收资源
pthread_detach(tid);
}
close(sockfd);
return 0;
}
(3)线程池
为了解决多线程(多进程)模型,在服务器运行过程中,频繁创建和销毁线程(进程)带来的时间消耗问题(基于生产者和消费者编程模型,以及任务队列等,实现的一套多线程框架)。
(4)IO多路复用
对多个文件描述符的读写可以复用一个进程(在不创建新的进程和线程的前提下,使用一个进程实现对多个文件读写的同时监测)。
三种实现方式:
①select
②poll
③epoll
select实现IO多路复用
步骤:
①创建文件描述符集合
②添加关注的文件描述符到集合
③使用select传递集合表给内核,内核开始监测事件
④当内核检测到事件时,应用层select将解除阻塞,并获得相关的事件结果
⑤根据select返回的结果做不同的任务处理
函数接口: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);
清空文件描述符集合,移除所有文件描述符。
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
cs
#include "head.h"
#include <string.h>
#include <arpa/inet.h>
#define SIN_PORT 50000
#define SIN_ADDR "192.168.0.176"
#define MAX_SIZE 1024
int init_tcp_ser()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket error");
return -1;
}
//允许绑定处于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(SIN_PORT);
seraddr.sin_addr.s_addr = inet_addr(SIN_ADDR);
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, char const *argv[])
{
// 存储每个连接的客户端地址
struct sockaddr_in cliaddrs[MAX_SIZE];
memset(cliaddrs, 0, sizeof(cliaddrs));
socklen_t clilen = sizeof(struct sockaddr_in);
int sockfd = init_tcp_ser();
if(sockfd < 0)
{
return -1;
}
fd_set serfd;
fd_set serfdtmp;
FD_ZERO(&serfd);
FD_SET(sockfd, &serfd);
int maxfd = sockfd;
while (1)
{
serfdtmp = serfd;
int cnt = select(maxfd + 1, &serfdtmp, NULL, NULL, NULL);
if(cnt < 0)
{
perror("select error");
return -1;
}
// 遍历所有可能的文件描述符,检查就绪状态
for(int fd = 0; fd <= maxfd; fd++)
{
if(!FD_ISSET(fd, &serfdtmp))
{
continue;
}
// 处理新连接请求
if(fd == sockfd)
{
struct sockaddr_in temp_cliaddr;
socklen_t temp_clilen = sizeof(temp_cliaddr);
int connfd = accept(sockfd, (struct sockaddr *)&temp_cliaddr, &temp_clilen);
if(connfd < 0)
{
perror("accept error");
continue;
}
// 保存客户端地址信息
cliaddrs[connfd] = temp_cliaddr;
FD_SET(connfd, &serfd);
if(connfd > maxfd)
{
maxfd = connfd;
}
printf("[%s:%d] online\n", inet_ntoa(cliaddrs[connfd].sin_addr), ntohs(cliaddrs[connfd].sin_port));
}
// 处理已连接客户端的数据
else
{
char buff[1024] = {0};
ssize_t cnt = recv(fd, buff, sizeof(buff)-1, 0);
if(cnt < 0)
{
perror("recv error");
FD_CLR(fd, &serfd);
close(fd);
}
// 客户端断开连接
else if(cnt == 0)
{
printf("[%s:%d] offline\n", inet_ntoa(cliaddrs[fd].sin_addr), ntohs(cliaddrs[fd].sin_port));
FD_CLR(fd, &serfd);
close(fd);
}
else
{
printf("[%s:%d] : %s\n", inet_ntoa(cliaddrs[fd].sin_addr), ntohs(cliaddrs[fd].sin_port), buff);
strcat(buff, "---->ok");
send(fd, buff, strlen(buff), 0);
}
}
}
}
close(sockfd);
return 0;
}