UNP学习_I/O复用之select函数实现回射服务器
1、I/O复用模型
I/O复用是阻塞在select系统调用之上,而不是阻塞在真正的I/O系统调用之上。
2、函数声明
#<sys/select.h>
#<sys/time.h>
int select(int maxfdpl,
fd_set* readset, //读事件集合
fd_set* writeset, //写事件集合
fd_set* exceptset, //异常事件集合
const struct timeval *timeout
);
struct timeval {
long tv_sec;//秒
long tv_usec;//微秒
};
//返回值:若有就绪描述符则为其数目,若超时则为0,若出错则为-1
//具体函数介绍参考UNP-P127
3、select实现的tcp回射服务器
// File Name: select_server.c
// Author: AlexanderGan
// Created Time: Mon 20 Jul 2020 08:32:13 PM CST
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<pthread.h>
#include<signal.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<errno.h>
#include<sys/select.h>
int main(int argc, char* argv[]){
if(argc < 2)
{
printf("eg: ./a.out IP port\n");
}
int port = atoi(argv[1]);
struct sockaddr_in serv_addr, client_addr;
socklen_t serv_len = sizeof(serv_addr);
socklen_t cli_len = sizeof(client_addr);
//创建套接字
int lfd = socket(AF_INET,SOCK_STREAM,0);
printf("lfd = %d\n",lfd);
//初始化服务器
memset(&serv_addr,0,serv_len);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(port);
//端口复用(防止在一端处于TIME_WAIT状态还未释放端口导致端口被占用的问题)
int flag = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));
//绑定IP和端口
if(bind(lfd,(struct sockaddr*)&serv_addr,serv_len) < 0){
perror("bind error");
exit(1);
}
//设置同时监听的最大个数
listen(lfd,36);
printf("Start accept !\n");
//设定最大文件描述符
int maxfd = lfd;
//文件描述符读集合
fd_set reads,temp;
//初始化读fd_set
FD_ZERO(&reads);
FD_SET(lfd,&reads);
while(1){
temp = reads;
int ret = select(maxfd+1,&temp,NULL,NULL,NULL);
if(ret == -1)
{
perror("select error");
exit(1);
}
//客户端发起了新的连接
if(FD_ISSET(lfd,&temp)){
//接受连接,accept不阻塞
int cfd = accept(lfd,(struct sockaddr*)&client_addr,&cli_len);
if(cfd == -1){
perror("accept error");
exit(1);
}
//接受成功后将客户端地址和端口打印出来
char ip[64];//存放ip表达式
printf("new client IP:%s, Port: %d\n",
inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip,sizeof(ip)),
ntohs(client_addr.sin_port));
//将cfd加入到待检测的读集合
FD_SET(cfd,&reads);
//更新最大文件描述符
maxfd = maxfd < cfd ? cfd:maxfd;
}
//已经连接的客户端有数据到达
for(int t = lfd+1; t <= maxfd; ++t){
if(FD_ISSET(t,&temp)){
char buf[1024] = {0};
int len = recv(t,buf,sizeof(buf),0);
if(len == -1){
perror("recv error");
exit(1);
}
else if(len == 0)
{
printf("客户端已经断开了连接\n");
close(t);
FD_CLR(t,&reads);
}
else{
//正常接收数据
printf("recv buf is : %s\n",buf);
send(t,buf,strlen(buf)+1,0);
}
}
}
}
close(lfd);
return 0;
}
4、select函数的优缺点
select优点:
select的最大优势是可以等待多个描述符就绪。
如:在调用recv函数之前,先调用select函数,如果系统没有可读数据那么select函数就会阻塞在这里。当系统存在可读或可写数据时,select函数返回,就可以调用recv函数接 收数据了。
可以看出使用select模型,需要两次调用函数。第一次调用select函数第二次accept函数。使用该模式的好处是:可以等待多个套接字。
select缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
(3)select函数支持的文件描述符数量由FD_SETSIZE设置,默认值是1024。