Socket-IO复用技术
系统运维
(上一篇地址)前面使用socket完成一个服务器对应多个客户端的小实验的时候,针对TCP连接,我们必须得创建新的进程来与新的客户端通信。那么,就意味着,1000个客户端就有有1000个server进程,这显然是不实际的。如果,我们可以提前把要监听的文件描述符放到一个集合里,一旦其中一个发生事件(不管是连上,还是通信),就去处理。这样,会方便很多。所以,今天学习一下IO复用。
创新互联专注于丰镇网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供丰镇营销型网站建设,丰镇网站制作、丰镇网页设计、丰镇网站官网定制、微信小程序服务,打造丰镇网络公司原创品牌,更为您提供丰镇网站排名全网营销落地服务。1 五个I/O模型 阻塞I/O 非阻塞I/O I/O复用(select和poll) 信号驱动I/O 异步I/O 阻塞IO最流行的I/O模型是阻塞I/O模型,缺省时,所有的套接口都是阻塞的。
非阻塞IOIO复用信号驱动IO异步IO2 I/O复用如果一个或多个I/O条件满足(例如:输入已准备好被读,或者描述字可以承接更多输出的时候)我们就能够被通知到,这样的能力被称为I/O复用,是由函数select和poll支持的。
I/O复用网络应用场合 当客户处理多个描述字 一个客户同时处理多个套接口 如果一个tcp服务器既要处理监听套接口,又要处理连接套接口 如果一个服务器既要处理TCP,又要处理UDP select/* According to POSIX.1-2001 */
#include
/* According to earlier standards */
#include
#include
#include
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
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);//清空描述字集合
作用:函数允许进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定的时间后才唤醒进程提供了即时响应多个套接的读写事件 参数: nfds:集合中的文件描述符 + 1 (指定被测试的描述字个数,它的值是要被测试的描述字加1,描述字0、1、2…….一直到nfds均被测试) readfds:要检查读事件的容器 writefds:要检查写事件的容器 timeout:超时时间 返回值:返回触发套接字的个数
中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件所需的描述字
如果我们对某个条件不感兴趣,这三个参数中相应的参数就可以设为空指针 timeout参数
时间的结构体如下:
struct timeval(
long tv_sec; //秒
long tv_usec;//微秒
);
timeout参数有三种可能
永远等待下去:仅在有一个描述字准备好I/O时才返回,为此,我们将timeout设置为空指针 等待固定时间:在有一个描述字准备好I/O是返回,但不超过由timeout参数所指timeval结构中指定的秒数和微秒数根本不等待:检查描述字后立即返回,这称为轮询。定时器的值必须为0
fd_set参数select使用描述字集,它一般是一个整数数组,每个数中的每一位对应一个描述字。
使用流程使用select完成之前socket的测试,流程如下:
客户端代码不变。
#include < sys/types.h>
#include < sys/socket.h>
#include < netinet/in.h> //sockaddr_in
#include < stdio.h>
#include < string.h>
//TCP
int main()
{
int fd;
int ret;
int addrLen;
char acbuf[20] = ;
struct sockaddr_in serAddr = {0};
struct sockaddr_in myAddr = {0};
//1.socket();
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//2.连接connect() 服务器的地址
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(1234);
serAddr.sin_addr.s_addr = inet_addr(192.168.159.5);
ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror(connect);
return -1;
}
//获取自己的地址
addrLen = sizeof(struct sockaddr_in);
ret = getsockname(fd,(struct sockaddr *)&myAddr,&addrLen);
if(ret == -1)
{
perror(getsockname);
return -1;
}
printf(client---ip: %s , port: %d\\n,\\
inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));
//3.通信
while(1)
{
printf(send: );
fflush(stdout);
scanf(%s,acbuf);
if(strcmp(acbuf,exit) == 0)
{
break;
}
write(fd,acbuf,strlen(acbuf));
}
//4.close()
close(fd);
return 0;
}
服务器端:
select.c
#include < sys/types.h> #include < sys/socket.h>
#include < netinet/in.h> //sockaddr_in
#include < stdio.h>
#include < string.h>
#include < signal.h>
#include < sys/select.h>
#include < unistd.h>
#include < sys/time.h>
//TCP
int main()
{
int fd;
int clientfd;
int ret;
pid_t pid;
int i;
int maxfd; //当前套接字
int nEvent;
fd_set set = {0}; //监听集合
fd_set oldset = {0}; //存放所有要监听的文件描述符
struct timeval time = {0};
int reuse = 0;
char acbuf[20] = ;
char client_addr[100] = ;
struct sockaddr_in addr = {0}; //自己的地址
struct sockaddr_in clientAddr = {0}; //连上的客户端的地址
int addrLen = sizeof(struct sockaddr_in);
signal(SIGCHLD,SIG_IGN);
//1.socket()
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//会出现没有活动的套接字仍然存在,会禁止绑定端口,出现错误:address already in use .
//由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,该状态会保留2-4分钟
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
perror(setsockopet error\\n);
return -1;
}
//2.bind()
addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = inet_addr(192.168.159.5);
ret = bind(fd,(struct sockaddr *)&addr,addrLen);
if(ret == -1)
{
perror(bind);
return -1;
}
//3.listen()
ret = listen(fd,10);
if(ret == -1)
{
perror(listen);
return -1;
}
//创建监听集合
FD_ZERO(&oldset);
FD_SET(fd,&oldset);
//maxfdp1:当前等待的套接字。比如:当前fd的值=3,则的套接字就是3
//所以每当有客户端连接进来,就比较一下文件描述符
maxfd = fd;
//select
//select之前,set放的是所有要监听的文件描述符;{3,4,5}
//select之后,set只剩下有发生事件的文件描述符。{3}
while(1)
{
set = oldset;
printf(before accept.\\n);
time.tv_sec = 5;
nEvent = select(maxfd + 1,&set,NULL,NULL,&time); //返回文件描述符的个数(即事件的个数)
printf(after accept.%d\\n,nEvent);
if(nEvent == -1)
{
perror(select);
return -1;
}
else if(nEvent == 0) //超时
{
printf(time out);
return 1;
}
else
{ //有事件发生
//判断是否是客户端产生的事件
for(i = 0 ; i <= maxfd ; i++)
{
if(FD_ISSET(i,&set))
{
if(i == fd)
{
clientfd = accept(fd,(struct sockaddr *)&clientAddr,&addrLen);
FD_SET(clientfd,&oldset);
printf(client ip:%s ,port:%u\\n,inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
if(clientfd > maxfd)
{
maxfd = clientfd;
}
}
else
{
memset(acbuf,0,20);
if(read(i,acbuf,20) == 0) //客户端退出
{
close(i);
//还要从集合里删除
FD_CLR(i,&oldset);
}
else
printf(receive: %s\\n,acbuf);
}
}
}
}
}
return 0;
}
epollepoll用到的函数有以下几个:
#include
int epoll_create(int size);//创建epoll
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//操作函数
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
事件集合的结构体:
(这里 ,还要注意,epoll的超时参数是int,单位是us)
#include #include
#include //sockaddr_in
#include
#include
#include
#include
//epoll
//epoll_wait() epoll_creat() epoll_ctl()
//TCP
int main()
{
int fd;
int clientfd;
int ret;
pid_t pid;
int i;
int epfd;
int nEvent;
struct epoll_event event = {0};
struct epoll_event rtl_events[20] = {0}; //事件结果集
int reuse = 0;
char acbuf[20] = ;
char client_addr[100] = ;
struct sockaddr_in addr = {0}; //自己的地址
struct sockaddr_in clientAddr = {0}; //连上的客户端的地址
int addrLen = sizeof(struct sockaddr_in);
signal(SIGCHLD,SIG_IGN);
//1.socket()
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//会出现没有活动的套接字仍然存在,会禁止绑定端口,出现错误:address already in use .
//由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,该状态会保留2-4分钟
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
perror(setsockopet error\\n);
return -1;
}
//2.bind()
addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = inet_addr(192.168.159.5);
ret = bind(fd,(struct sockaddr *)&addr,addrLen);
if(ret == -1)
{
perror(bind);
return -1;
}
//3.listen()
ret = listen(fd,10);
if(ret == -1)
{
perror(listen);
return -1;
}
epfd = epoll_create(1000); //同时监听的文件描述符
event.data.fd = fd;
event.events = EPOLLIN; //读
epoll_ctl(epfd,EPOLL_CTL_ADD,fd, &event);
while(1)
{
// nEvent = epoll_wait(epfd,rtl_events,20,-1); //-1:阻塞 0:非阻塞
nEvent = epoll_wait(epfd,rtl_events,20,5000);
if(nEvent == -1)
{
perror(epoll_wait);
return -1;
}
else if(nEvent == 0)
{
printf(time out.);
}
else
{
//有事件发生,立即处理
for(i = 0; i < nEvent;i++)
{
//如果是 服务器fd
if( rtl_events[i].data.fd == fd )
{
clientfd = accept(fd,(struct sockaddr *)&clientAddr,&addrLen);
//添加
event.data.fd = clientfd;
event.events = EPOLLIN; //读
epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&event);
printf(client ip:%s ,port:%u\\n,inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
}
else
{
//否则 客户端fd
memset(acbuf,0,20);
ret = read(rtl_events[i].data.fd,acbuf,20);
printf(%d\\n,ret);
if( ret == 0) //客户端退出
{
close(rtl_events[i].data.fd);
//从集合里删除
epoll_ctl(epfd,EPOLL_CTL_DEL,rtl_events[i].data.fd,NULL);
}
else
printf(receive: %s\\n,acbuf);
}
}
}
}
return 0;
}
运行结果如前,正常收发。
文章标题:Socket-IO复用技术
本文来源:http://pwwzsj.com/article/cpodsj.html