Linux网络编程3-select模型
1.select函数
cpp
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
/*
函数作用:委托内核监控可读、可写、异常事件
函数参数:
nfds:输入参数,告诉内核要监控文件描述符的范围,一般取值为最大文件描述符+1
readfds:
输入参数:告诉内核要监控哪些文件描述符
输出参数:内核告诉应用程序哪些文件描述符有变化
writefds:
输入参数:告诉内核要监控哪些文件描述符
输出参数:内核告诉应用程序哪些文件描述符有变化
exceptfds:
输入输出参数,一般表示异常事件
timeout:超时时间:
NULL:表示永久阻塞,直到有事件发生
0:表示不阻塞,不管有没有事件发生,都会立刻返回
>0:表示阻塞的时长,若没有超过超时时长,则一直阻塞
若在时长内,有事件发生,则立刻返回,
若超过时长,则立刻返回
返回值:
成功返回发生变化的文件描述符个数。
*/
void FD_CLR(int fd, fd_set *set); // 从set集合中清楚fd
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); // 清空文件描述符集
2.使用select模型-服务器端开发流程
cpp
//1 创建socket, 得到监听文件描述符lfd---socket()
//2 设置端口复用-----setsockopt()
//3 将lfd和IP PORT绑定----bind()
//4 设置监听---listen()
//5
fd_set readfds; //定义文件描述符集变量
fd_set tmpfds;
FD_ZERO(&readfds); //清空文件描述符集变量
FD_SET(lfd, &readfds);//将lfd加入到readfds集合中;
maxfd = lfd;
while(1)
{
tmpfds = readfds;
nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
if(nready<0)
{
if(errno==EINTR)//被信号中断
{
continue;
}
break;
}
//有客户端连接请求到来
if(FD_ISSET(lfd, &tmpfds))
{
//接受新的客户端连接请求
cfd = accept(lfd, NULL, NULL);
//将cfd加入到readfds集合中
FD_SET(cfd, &readfds);
//修改内核监控的文件描述符的范围
if(maxfd<cfd)
{
maxfd = cfd;
}
//如果只有连接请求,就不用执行下面的代码处理客户端的数据。
if(--nready==0)
{
continue;
}
}
//有客户端数据发来
for(i=lfd+1; i<=maxfd; i++)
{
if(FD_ISSET(i, &tmpfds))
{
//read数据
n = read(i, buf, sizeof(buf));
if(n<=0)
{
close(i);
//将文件描述符i从内核中去除
FD_CLR(i, &readfds);
}
else
{
printf("n=[%d], buf=[%s]\n", n, buf);
// 转大写
//write应答数据给客户端
write(i, buf, n);
}
// 本次select返回的可变文件描述符处理完,提前结束for循环
if(--nready==0)
{
break;
}
}
}
close(lfd);
return 0;
}
3.select服务器代码
cpp
//select版本的服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include <pthread.h>
#include "wrap.h"
int main() {
//创建socket
int lfd = Socket(AF_INET, SOCK_STREAM, 0);
//设置端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
//绑定--将lfd 和 IP PORT绑定
struct sockaddr_in serv;
bzero(&serv, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(lfd, (struct sockaddr *) &serv, sizeof(serv));
//监听
Listen(lfd, 128);
fd_set readfds; //定义文件描述符集变量
fd_set tmpfds;
FD_ZERO(&readfds); //清空文件描述符集变量
FD_ZERO(&tmpfds);
FD_SET(lfd, &readfds);//将lfd加入到readfds集合中;
int maxfd = lfd;
int cfd;
while (1) {
tmpfds = readfds;
int nready = select(maxfd + 1, &tmpfds, NULL, NULL, NULL);
if (nready < 0) {
if (errno == EINTR)//被信号中断
{
continue;
}
break;
}
//有客户端连接请求到来
if (FD_ISSET(lfd, &tmpfds)) {
//接受新的客户端连接请求
cfd = accept(lfd, NULL, NULL);
//将cfd加入到readfds集合中
FD_SET(cfd, &readfds);
//修改内核监控的文件描述符的范围
if (maxfd < cfd) {
maxfd = cfd;
}
//如果只有连接请求,就不用执行下面的代码处理客户端的数据。
if (--nready == 0) {
continue;
}
}
//有客户端数据发来
for (int i = lfd + 1; i <= maxfd; i++) {
if (FD_ISSET(i, &tmpfds)) {
char buf[1024];
bzero(buf,sizeof(buf));
//read数据
int n = read(i, buf, sizeof(buf));
if (n <= 0) {
close(i);
//将文件描述符i从内核中去除
FD_CLR(i, &readfds);
} else {
printf("n=[%d], buf=[%s]\n", n, buf);
// 转大写
//write应答数据给客户端
write(i, buf, n);
}
// 本次select返回的可变文件描述符处理完,提前结束for循环
if (--nready == 0) {
break;
}
}
}
} // end while(1)
Close(lfd);
return 0;
}
4.select优缺点
select优点:
1.一个进程可以支持多个客户端
2.select支持跨平台
select缺点:
1.代码编写困难
2.会涉及到用户区到内核区的来回拷贝
3.当客户端多个连接, 但少数活跃的情况, select效率较低
例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下
4.最大支持1024个客户端连接
select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的, 而是由FD_SETSIZE=1024限制的.
FD_SETSIZE=1024 fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核.