TCP并发服务器模型:
TCP并发服务器模型之多线程
1.解决TCP并发问题
2.因为线程占据独立栈空间,所以同时创建线程个数是有限的,如果并发客户端太多,系统有崩溃风险
3.一般搭配线程池来使用
四种常见的IO模型,
1.阻塞IO
2.非阻塞IO
3.信号驱动IO
4.多路复用IO
==================================================================================================
2.非阻塞IO
1.设置非阻塞模式
#include <fcntl.h>
// 获取当前文件描述符标志
int flags = fcntl(sockfd, F_GETFL, 0);
// 添加 O_NONBLOCK 标志
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
或
// 使用 SOCK_NONBLOCK 标志
int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
或
// 使用 accept4() 函数(Linux 特有)
int client_fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK);
===================================================================================================
3.信号驱动IO
1.注册信号处理函数
2.设置文件描述符为异步模式
3.设置接收信号的进程ID
1.signal(SIGIO, sigio_handler);
void handler(int signo)
{
char tmpbuff4096 = {0};
memset(tmpbuff, 0, sizeof(tmpbuff));
read(fd, tmpbuff, sizeof(tmpbuff));
printf("FIFO:%s\n", tmpbuff);
return;
}
2.设置文件描述符为异步模式
//在文件描述符原来的标志位基础上新增O_ASYNC,异步IO标志
int flags = fcntl(fd, F_GETFL);
flags |= O_ASYNC;
fcntl(fd, F_SETFL, flags); //fcntl(sockfd, F_SETFL, flags | O_ASYNC);
fd为fifo的管道文件的文件描述符
3.设置接收信号的进程ID
// 告诉内核当 I/O 就绪时,向哪个进程发送 SIGIO
fcntl(sockfd, F_SETOWN, getpid());
例子:
read.c
#include "head.h"
int fd = 0;
void handler(int signo)
{
char tmpbuff4096 = {0};
memset(tmpbuff, 0, sizeof(tmpbuff));
read(fd, tmpbuff, sizeof(tmpbuff));
printf("FIFO:%s\n", tmpbuff);
return;
}
int main(void)
{
int flags = 0;
char tmpbuff4096 = {0};
signal(SIGIO, handler);
mkfifo("/tmp/myfifo", 0664);
fd = open("/tmp/myfifo", O_RDONLY);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
//在文件描述符原来的标志位基础上新增O_ASYNC,异步IO标志
flags = fcntl(fd, F_GETFL);
flags |= O_ASYNC;
fcntl(fd, F_SETFL, flags);
//指定异步IO给当前进程发送信号
fcntl(fd, F_SETOWN, getpid());
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
gets(tmpbuff);
printf("STDIN:%s\n", tmpbuff);
}
close(fd);
return 0;
}
write.c
#include "head.h"
int main(void)
{
int fd = 0;
char tmpbuff4096 = {0};
mkfifo("/tmp/myfifo", 0664);
fd = open("/tmp/myfifo", O_WRONLY);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
while (1)
{
gets(tmpbuff);
write(fd, tmpbuff, strlen(tmpbuff));
if (0 == strcmp(tmpbuff, ".quit"))
{
break;
}
}
close(fd);
return 0;
}
=================================================================================================
4.多路复用
select/poll/epoll
select:
#include <sys/select.h> *
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:同时监视多个文件描述符,阻塞直到至少一个描述符就绪(可读/可写/异常),或超时。它会将产生事件的文件描述符留在集合中,将没有发生事件的文件描述符清出集合
参数:
nfds 需要监视的最大文件描述符值 + 1(max_fd + 1)
readfds 监视可读事件的描述符集合(传入传出参数)
writefds 监视可写事件的描述符集合(传入传出参数)一般为NULL
exceptfds 监视异常事件的描述符集合(传入传出参数)一般为NULL
timeout 超时时间(NULL 表示无限等待)(阻塞)
返回值:
> 0 就绪的文件描述符总数
0 超时(没有文件描述符就绪)
-1 失败,设置 errno
void FD_ZERO(fd_set *set) 清空集合
void FD_SET(int fd, fd_set *set) 将 fd 加入集合
void FD_CLR(int fd, fd_set *set) 将 fd 从集合中移除
void FD_ISSET(int fd, fd_set *set) 检查 fd 是否在集合中就绪
==================================================================================================
epoll:
#include <sys/epoll.h>
1.epoll_create
int epoll_create(int size);
功能: 创建一个 epoll 实例,返回一个文件描述符,用于后续的 epoll_ctl() 和 epoll_wait() 操作。一个(内核)监听事件表, 内核,更加高级
参数:size 建议内核初始分配的事件表大小(从 Linux 2.6.8 起被忽略,但必须 > 0)
返回值:
成功 返回 epoll 文件描述符(非负整数)
失败 返回 -1,设置 errno
================================================================================================
epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:用于向 epoll 实例中添加、修改或删除需要监控的文件描述符。控制 epoll 实例中的事件表:添加、修改或删除要监控的文件描述符及其事件类型。
参数:
epfd epoll 实例的文件描述符(由 epoll_create() 返回)
op 操作类型:EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL
EPOLL_CTL_ADD 添加 将 fd 加入 epoll 实例的监控列表
EPOLL_CTL_MOD 修改 修改 fd 已注册的事件类型
EPOLL_CTL_DEL 删除 从 epoll 实例中移除 fd(不再监控)
fd 要操作的目标文件描述符(如 sockfd、confd)
event 指向 struct epoll_event 的指针,描述关注的事件和用户数据
返回值:
0 成功
-1 失败,设置 errno
struct epoll_event {
uint32_t events; // 事件掩码(关注的事件类型)
epoll_data_t data; // 用户数据(通常存文件描述符)
};
typedef union epoll_data {
void *ptr;
int fd; // 最常用:存文件描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;
events:
EPOLLIN 可读事件(数据到达)
EPOLLOUT 可写事件(发送缓冲区有空间)
EPOLLERR 发生错误(自动监测,无需设置)
EPOLLHUP 对端关闭连接(自动监测,无需设置)
EPOLLET 边缘触发模式(默认是水平触发)
EPOLLONESHOT 一次性触发(事件发生后自动移除)
==================================================================================================
epoll_wait
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:
等待 epoll 实例中的文件描述符产生事件,将就绪的事件填充到 events 数组中返回。
参数:
epfd epoll 实例的文件描述符(由 epoll_create() 返回)
events 指向 struct epoll_event 数组的指针,用于接收就绪事件
maxevents events 数组的大小(最多返回多少个事件)
timeout 超时时间(毫秒),-1 表示无限等待,0 表示非阻塞轮询
返回值:
> 0 就绪的文件描述符数量(即 events 数组中填充的数量)
0 超时(没有任何事件就绪)
-1 失败,设置 errno