1.单循环服务器
2.并发服务器
1. 设置socket属性
2. 进程
3. 线程
3.多路IO复用模型 - 提高并发程度
1. 区别
2. IO处理模型
1. 阻塞IO模型
2. 非阻塞IO模型
3. 信号驱动IO
4. IO多路复用
3. 特点
4. 函数接口
1. select
2. poll
3. epoll
半包
1.单循环服务器
2.并发服务器
1. 设置socket属性
原型:int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)
功能:
在bind前 设置socket的属性
参数:
sockfd 要设置的socket
level 设置socket层次//socket本身 tcp ip
optname 选项名字
optval 选项值
optlen 长度
设置一个选项(开启一个功能))---让地址重用,即可停止后立即使用
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int));
2. 进程:
ps aux | grep ser 查找包含ser的进程
signal(SIGCHILD, do_child)父进程接收子进程结束信号exit(0)后回收子进程资源,防止僵尸态
void do_child(int signo)
{ wait(NULL);}
3. 线程:
退出个体线程使用pthread_exit(NULL)
pthread_join()会阻塞程序,无法接收多个客户端信息,所以选择pthread_detach()回收资源
3.多路IO复用模型 - 提高并发程度
1. 区别
多进程和多线程:一个客户端对应一个进程或线程
IO多路复用 :多个客户端对应一个进程或线程
2. IO处理模型
1. 阻塞IO模型:
特点:简单,低效
i - 读:scanf, getchar,read,recv
操作:从终端stdin读取数据 => 阻塞等待读取数据 => 返回用户空间o- 写:pipe(一直向管道写入数据,管道内存满时阻塞)
2. 非阻塞IO模型
特点:轮询,CPU负担重
操作:从终端stdin读取数据 => 不等待读取数据 => 直接返回用户空间
O_NONBLOCK 打开文件时可以设置为非阻塞模式
3. 信号驱动IO
设置非阻塞:fcntl,并与信号结合
特点:处理多路IO时资源数量有限
- 原型:int fcntl(int fd, int cmd, ...)
- 功能:
维护文件描述符
- 参数
fd 要操作的fd
cmd 要做的操作F_GETFL 开启异步信号
F_SETFL
F_SETOWN 设置与信号的关联
fcntl(fd, F_SETOWN, gtpid());
... 可变参数 eg:printf(const char *format,...); printf("hello"); printf("a = %d\n", a);
- 返回值
成功时返回值取决于所作的操作
与信号的结合:signo
4. IO多路复用
3. 特点
1. 可以处理多路IO
2. 不需要阻塞
3. 不需要轮询
4. 函数接口
1. select
- 原型:int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);- 功能:
实现IO多路复用
- 参数
nfds 文件描述符中最大文件描述符+1
遍历时即可遍历到自己 for(i = 0; i < nfds; i++)
readfds 读操作的文件描述符的集合
writefds 写操作的文件描述符的集合
exceptfds 异常的文件描述符的集合
timeout 设置超时时间 struct timeval_t = {3, 0}; //阻塞3秒NULL表示select是阻塞功能
0 - 非阻塞
n > 0 - 每次阻塞的时间
- 返回值
成功返回就绪的文件描述符的数量
失败返回-1
1. 辅助函数
void FD_CLR(int fd, fd set *set); // 将fd从set集合中清除
int FD_ISSET(int fd, fd_set *set); // 判断fd是否在set中
void FD_SET(int fd, fd_set *set); // 将fd添加到set集合中
void FD_ZERO(fd_set *set);t*set); // 将set集合清空
2. 步骤
1. 建立一张表 监控
fd_set readfds ; 建立表
FD_ZERO(&readfds) 清空表
2. 将要监控的文件描述符加入表中
FD_SET(0,&readfds); 0表示标准输入
FD_SET(fd,&readfds);
3.准备参数
nfds = fd + 1
select(nfs, &readfds, NULL, NULL, NULL);
4. 监控文件描述符
3. 缺点
1.最大监听数受限:FD_SETSIZE 默认1024(Linux)
2.每次调用需重置fdSet:内核会修改集合,必须每次重新
3.用户态与内核态拷贝开销大
4.返回后仍需遍历所有fd才能知道哪个就绪
5.效率随fd数量增长下降明显
4. 点对点聊天
server.c:
client.c:
超时设置:
5. 多路IO复用
server.c
client.c:
(退出.quit不管用)
解决: 使用strncpy函数除去输入的\n
2. poll
- 原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
- 功能:
对文件描述符监控
- 参数:
events 事件:POLLIN 读
POLLOUT 写
POLLERR 错误
revents 返回就绪的事件nfds 监控的文件描述符的个数
timeout 时间值,-1阻塞 时间值,单位ms
0非阻塞
- 返回值
成功返回就绪文件的数量,0 超时情况下没有就绪事件
失败返回 -1
1. 步骤
1. 建立监控表
struct pollfd fds[10]; // 监控10个fd
2. 将要监控的文件描述符加入表中
两路:int nfds = 0;
fds[0].fd = 0; fds[1].fd = sockfd;
fds[0].events = POLLIN | POLLOUT; fds[0].events = POLLIN | POLLOUT;
nfds++; nfds++;
// 关心读和写,只会返回就绪的事件
3.准备参数
4. 监控文件描述符
2. 改进与缺点
- 相比select的改进
1.无1024限制:只要系统允许打开足够多fd
2.无需重置集合:^events和」revents丶分离
3.更清晰的事件机制
4.效率更高:仅遍历传入的数组,不遍历整个fd范围
- 仍存在的问题
1.每次调用仍需将整个fds[]拷贝到内核
2.返回后仍需遍历全部元素查找就绪fd
3.时间复杂度仍是O(n),连接数多时性能下降
3. 点对点聊天
server.c:
client.c:
4. 多路IO复用
server.c:
3. epoll
1. epoll_create
- 原型:int epoll_create(int size)
- 功能:
创建一个epoll对象
- 参数:
size 忽略,但是必须大于0
- 返回值
成功返回epoll对象的fd
失败返回-1
2. epoll_ctl
- 原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 功能:
控制epoll对象
- 参数:
epfd epoll对象的fd
op EPOLL_CTL_ADD 添加
EPOLL_CTL_MOD 修改
EPOLL_CTL_DEL 删除
fd 关心的文件描述符
event 事件
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;-------------------------------------------------------------------------------------------------------
struct epoll_event {
uint32_t events; // EPOLLIN 读// EPOLLOUT 写
// EPOLLERR 出错
// EPOLLET 边沿触发
epoll_data_t data;
};
- 返回值
成功返回0,失败返回-1
中断
1. 水平(电平)触发
有数据就会一直通知
2. 边沿触发
每完成从没数据到有数据的过程,通知一次
3. epoll_wait
- 原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- 功能:
监控对应文件描述符,查看是否就绪
- 参数:
epfd epoll对象
events 保存就绪结果的数组的首地址
maxevents 数组大小timeout 设置超时时间,单位ms
- 返回值:
成功返回就绪数量,失败返回-1
4. 步骤
1. 创建一个epoll对象 -- 监控的表
int epfd = epoll_create(2);
2. 添加文件描述符
int add_fd(int fd, int epfd)
{
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
if( epoll_ctl(epfd,EPOLL_CTL_ADD, fd, &ev) < 0)
{ perror("fail to add"); return -1;}
return 0;
}
add_fd(0, epfd);
add_fd(fd, epfd);
删除文件描述符
int del_fd(int fd, int epfd)
{
if ( epoll_ctl(epfd,EPOLL_CTL_DEL, fd, NULL))
{ perror("fail to delte"); return -1; }
return 0;
}
3. 监控文件描述符
5. 点对点聊天
server.c
client.c
半包
想要接受4000的数据,但网络层一次只能打包1500字节的数据
解决:多接受几次,注意越界判断