复用IO网络编程
复用IO所需的API
为啥要使用复用IO对网络进行改造?
- 应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
- 若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
- 若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;
所以比较好的方法是使用I/O多路复用。其基本思想是: - 先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
- 函数返回时告诉进程那个描述符已就绪,可以进行I/O操作
复用io改造需要的API
#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);
#include<sys/poll.h>
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
select()参数
-nfds 所有监控文件描述符中最大的哪一个加1
-readfds 所有要读的文件描述符的集合
-writefds 所有要写文件描述符的集合
-exceptfds 其他要向我们通知的文件描述符
-timeout 超时设置
--NULL: 一直阻塞,直到有文件描述符就绪或出错
--时间值为0,仅仅检测文件描述符集的状态,然后立即返回
--时间值不为0,在指定时间内,如果没有事件发生,则超时返回
在我们调用select时进程会一直阻塞直到以下的一种情况发生.
- 有文件可以读.
- 有文件可以写.
- 超时所设置的时间到.
-FD_SET 将fd加到fdset
-FD_CLR 将fd从fdset里清除
-FD_ZERO 从fdset清除所有的文件描述符
-FD_ISSET 判断fd是否在fdset集合中
宏的形式:
void FD_ZERO(fd_set *fdset)
void FD_SET(int fd,fd_set *fdset)
void FD_CLR(int fd,fd_set *fdset)
int FD_ISSET(int fd,fd_set *fdset)
复用IO编程代码
server 端
#include "net.h"
#include <pthread.h>
#include <sys/select.h>
typedef struct client_info{
int newfp;
char ipv4_addr[14];
int port;
}Client_info, *pClient_info ;
void * process(pClient_info arg);
int main()
{
fd_set client_net, client_check;
Client_info cin;
struct timeval timeout;
int fp;
struct sockaddr_in myaddr;
// 1. open socket
if((fp = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket fail");
exit(0);
}
int b_reuse = 1;
setsockopt(fp, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));
// 2. set bind()
memset(&myaddr, 0, sizeof(myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(Netport);
//优化,使服务器能够接受绑定在任意IP上 INADDR_ANY
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(fp,(struct sockaddr*) &myaddr,sizeof(myaddr)) < 0)
{
perror("bind fail");
exit(0);
}
// 3. set listen()
if(listen(fp, BACKLOG) < 0)
{
perror("listen fail");
exit(0);
}
// 4. set accept()
int newfp = -1;
FD_ZERO(&client_net);
FD_SET(fp,&client_net);
int Maxfp = fp;
struct sockaddr_in recv_addr;
socklen_t recv_addr_len = sizeof(recv_addr);
int ret;
while(1)
{
client_check = client_net;
timeout.tv_sec=5;
timeout.tv_usec=5000;
if((ret=select(Maxfp+1, &client_check, 0, 0, &timeout))==-1)
break;
if(ret==0)
{
printf("fd_num=0 \n");
printf("Maxfp = %d\n", Maxfp);
continue;
}
int i;
for(i=0; i<Maxfp+1;i++){
if(FD_ISSET(i,&client_check))
{
if(fp == i) //有新的连接请求
{
newfp = accept(fp, (struct sockaddr *)&recv_addr, (socklen_t *)&recv_addr_len);
if(newfp < 0)
{
perror("accept fail");
exit(0);
}
FD_SET(newfp,&client_net);
if(Maxfp<newfp)
newfp=Maxfp;
Maxfp++;
bzero(cin.ipv4_addr, 16);
if(NULL == inet_ntop(AF_INET,(void *)&recv_addr.sin_addr, cin.ipv4_addr, sizeof(recv_addr)))
{
perror("inet_ntop");
exit(0);
}
cin.port = ntohs(recv_addr.sin_port);
cin.newfp = newfp;
printf("newfp = %d\n", newfp);
printf("success the connet !\n");
printf("connect ip address = %s, connect port = %d \n",cin.ipv4_addr, cin.port);
}
else //原有fp有新数据传入
{
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
char buf2[] = "sever send: ";
ret = read(i, buf, BUFSIZ-1);
if(ret == 0){
FD_CLR(i, &client_net);
Maxfp--;
close(i);
}
else
{
printf("(%s : %d): %s \n", cin.ipv4_addr, cin.port, buf);
write(i,strcat(buf2,buf),strlen(strcat(buf2,buf)));
}
}
}
}
}
// 5. set close
close(newfp);
close(fp);
return 0;
}
client 端
/*
复用IO实验
*/
#include "net.h"
int main(int argc, char* argcv[])
{
if(argc != 3)
{
printf("enter error!\n");
printf("please fill:ip_addr, netport \n");
exit(0);
}
// 1. set
int fp = -1;
if((fp = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket fail");
exit(0);
}
// 2.set connect
struct sockaddr_in myaddr;
memset(&myaddr, 0, sizeof(myaddr));
myaddr.sin_family = AF_INET;
int netport = atoi(argcv[2]);
if(netport < 50000)
{
netport = Netport;
}
myaddr.sin_port = htons(netport);
//myaddr.sin_addr.s_addr = inet_addr(Netip);
if(inet_pton(AF_INET, argcv[1], (void *)&myaddr.sin_addr.s_addr) != 1)
{
perror("inet_pton fail");
exit(0);
}
if(connect(fp,(struct sockaddr *)&myaddr, sizeof(myaddr)) < 0)
{
perror("connect fail");
exit(0);
}
// 3. set read
char buf[BUFSIZ];
char recv[BUFSIZ];
while(1)
{
bzero(buf,BUFSIZ);
bzero(recv, BUFSIZ);
if(fgets(buf, BUFSIZ, stdin) == NULL)
{
continue;
}
write(fp, buf, strlen(buf));
if(!strncmp(buf,QUIT_STR, strlen(QUIT_STR)))
{
printf("Client is exiting !\n");
break;
}
read(fp,recv,BUFSIZ-1);
printf("%s",recv);
}
close(fp);
return 0;
}
注意事项
在使用select函数时需要注意以下问题:
select( )函数里面的各个文件描述符fd_set集合的参数在select( )前后发生了变化:
slect处理前:表示关心的文件描述符集合
select处理后:有数据的集合(在超时还回情况下)
所以设置两个fd_set,一个用于保存所有产生的文件描述符集合,一个用于进行select检测。
Maxfp的值需要格外注意,当产生newfp后需要找到所有文件描述符中最大的值当做Maxfp,在使用if判断新生成的newfp和Maxfp大小时需要注意有可能两者相等,这时可手动+1,保证后续for循环能顺利找到发生数据变换的文件描述符。且当文件描述符被close后,记得将对应监控位置0