网络编程--IO模型示例

来源:互联网 发布:java守护线程有什么用 编辑:程序博客网 时间:2024/04/29 11:47

IO模型在Richard Stevens的《UNIX网络编程,第一卷》(程序猿必备!)一书中有非常详尽的描述,以下简要介绍,并给出代码示例。

另外比较好的总结性blog,推荐:
使用异步 I/O 大大提高应用程序的性能
IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)

常见网络IO模型:阻塞式IO、无阻塞式IO、IO复用、异步IO、信号驱动

阻塞式IO:
在一个进程发出IO请求后,进入阻塞状态,直到内核返回数据,才重新运行,如图:

代码
sever端:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <netinet/in.h>#include <sys/socket.h>#include <unistd.h>int main(){int sockfd, new_fd;int sin_size, numbytes;struct sockaddr_in addr, cliaddr;//创建socketif((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){perror("createSocket");return -1;}//初始化socket结构memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(7092);addr.sin_addr.s_addr = htonl(INADDR_ANY);//绑定套接口if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr))==-1){perror("bind");return -1;}//创建监听套接口if(listen(sockfd,10)==-1){perror("listen");return -1;}printf("server is running!\n");char buff[1024];//等待连接while(1) {sin_size = sizeof(struct sockaddr_in);//接受连接if((new_fd = accept(sockfd, (struct sockaddr *)&cliaddr, (socklen_t*)&sin_size))==-1){perror("accept");return -1;}//生成一个子进程来完成和客户端的会话,父进程继续监听if(!fork()){//读取客户端发来的信息memset(buff,0,sizeof(buff));if((numbytes = recv(new_fd,buff,sizeof(buff),0))==-1){perror("recv");return -1;}printf("buff=%s\n",buff);//将从客户端接收到的信息再发回客户端if(send(new_fd,buff,strlen(buff),0)==-1){perror("send");}close(new_fd);return 0;}//父进程关闭new_fdclose(new_fd);}close(sockfd);}
client端:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <netdb.h>#include <sys/types.h>#include <netinet/in.h>#include <sys/socket.h>#include <unistd.h>int main(int argc,char *argv[]){if(argc!=3){printf("%s: input IP & port\n",argv[0]);return 1;}int sockfd,numbytes;char buf[100] = "hello world";struct hostent *he;struct sockaddr_in their_addr;//将基本名字和地址转换he = gethostbyname(argv[1]);//建立一个TCP套接口if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){perror("socket");exit(1);}//初始化结构体their_addr.sin_family = AF_INET;their_addr.sin_port = htons(atoi(argv[2]));their_addr.sin_addr = *((struct in_addr *)he->h_addr);bzero(&(their_addr.sin_zero),8);//和服务器建立连接if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr))==-1){perror("connect");exit(1);}//向服务器发送字符串if(send(sockfd,buf,strlen(buf),0)==-1){perror("send");exit(1);}memset(buf,0,sizeof(buf));//接受从服务器返回的信息if((numbytes = recv(sockfd,buf,100,0))==-1){perror("recv");exit(1);}close(sockfd);return 0;}

运行:
$ ./bin/server
server is running!
buff=hello world
buff=hello world

$ ./bin/client 10.32.49.10 7092
$ ./bin/client 10.32.49.10 7092

无阻塞式IO:
在一个进程发出IO请求后,不阻塞,如果数据没有准备好,就直接返回错误码,如图:

可以通过fcntl控制socket描述符属性。
int flags;
flag=fcntl(sockfd,F_GETFL,0);
fcntl(sockfd,F_SETFL,flag|O_NONBLOCK)

非阻塞式I/O模型对4种I/O操作返回的错误
读操作:接收缓冲区无数据时返回EWOULDBLOCK
写操作:发送缓冲区无空间时返回EWOULDBLOCK;空间不够时部分拷贝,返回实际拷贝字节数
建立连接:启动3次握手,立刻返回错误EINPROGRESS;服务器客户端在同一主机上connect立即返回成功
接受连接:没有新连接返回EWOULDBLOCK

代码:
server端:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <netinet/in.h>#include <sys/socket.h>#include <unistd.h>#include <fcntl.h>int main(){int sockfd, new_fd;int sin_size;struct sockaddr_in addr, cliaddr;//创建socketif((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){perror("createSocket");return -1;}//初始化socket结构memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(7092);addr.sin_addr.s_addr = htonl(INADDR_ANY);//绑定套接口if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr))==-1){perror("bind");return -1;}//创建监听套接口if(listen(sockfd,10)==-1){perror("listen");return -1;}printf("server is running!\n");char buff[1024];//等待连接while(1) {sin_size = sizeof(struct sockaddr_in);//接受连接if((new_fd = accept(sockfd, (struct sockaddr *)&cliaddr, (socklen_t*)&sin_size))==-1){perror("accept");return -1;}//生成一个子进程来完成和客户端的会话,父进程继续监听if(!fork()){//设置new_fd无阻塞属性int flags;if((flags=fcntl(new_fd, F_GETFL, 0))<0)              {            perror("fcntl F_GETFL");              }        flags |= O_NONBLOCK;        if(fcntl(new_fd, F_SETFL,flags)<0)              {            perror("fcntl F_SETFL");  }//读取客户端发来的信息memset(buff,0,sizeof(buff));while(1){if((recv(new_fd,buff,sizeof(buff),0)) < 0){if(errno==EWOULDBLOCK){perror("recv error, wait....");sleep(1);continue;}}else{printf("buff=%s\n",buff);}break;}//发送数据while(1){if(send(new_fd,buff,strlen(buff),0) < 0){if(errno==EWOULDBLOCK){perror("send error, wait....");sleep(1);continue;}}else{printf("buff=%s\n",buff);}break;}close(new_fd);return 0;}//父进程关闭new_fdclose(new_fd);}close(sockfd);}

client端:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <netdb.h>#include <sys/types.h>#include <netinet/in.h>#include <sys/socket.h>#include <unistd.h>int main(int argc,char *argv[]){if(argc!=3){printf("%s: input IP & port\n",argv[0]);return 1;}int sockfd,numbytes;char buf[100] = "hello world";struct hostent *he;struct sockaddr_in their_addr;//将基本名字和地址转换he = gethostbyname(argv[1]);//建立一个TCP套接口if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){perror("socket");exit(1);}//初始化结构体their_addr.sin_family = AF_INET;their_addr.sin_port = htons(atoi(argv[2]));their_addr.sin_addr = *((struct in_addr *)he->h_addr);bzero(&(their_addr.sin_zero),8);//和服务器建立连接if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr))==-1){perror("connect");exit(1);}sleep(5);//向服务器发送字符串if(send(sockfd,buf,strlen(buf),0)==-1){perror("send");exit(1);}memset(buf,0,sizeof(buf));sleep(5);//接受从服务器返回的信息if((numbytes = recv(sockfd,buf,100,0))==-1){perror("recv");exit(1);}close(sockfd);return 0;}

运行:
$ ./bin/server
server is running!
recv error, wait....: Resource temporarily unavailable
recv error, wait....: Resource temporarily unavailable
recv error, wait....: Resource temporarily unavailable
recv error, wait....: Resource temporarily unavailable
recv error, wait....: Resource temporarily unavailable
buff=hello world
buff=hello world

$ ./bin/client 10.32.49.10 7092

IO复用:
IO复用阻塞在select、poll或epoll这样的系统调用上,通过这种方式,在不使用多线程的前提下,单个进程可以同时处理多个网络连接的IO。如图:

代码
sever端:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <netinet/in.h>#include <sys/socket.h>#include <unistd.h>#include <fcntl.h>#include <netdb.h>#include <sys/epoll.h>#define MAXEVENT 1024int create_server_socket(int& sockfd){struct sockaddr_in addr;//创建socketif((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){perror("createSocket");return -1;}//初始化socket结构memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(7092);addr.sin_addr.s_addr = htonl(INADDR_ANY);//绑定套接口if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr))==-1){perror("bind");return -1;}//创建监听套接口if(listen(sockfd,10)==-1){perror("listen");return -1;}return 0;}int set_socket_non_blocking(int fd){int flags, s;flags = fcntl (fd, F_GETFL, 0);if (flags == -1){perror ("fcntl F_GETFL failed");return -1;}flags |= O_NONBLOCK;s = fcntl (fd, F_SETFL, flags);if (s == -1){perror ("fcntl F_SETFL failed");return -1;}return 0;}int main(){int sockfd, efd;struct epoll_event event;struct epoll_event *events;int s;if(create_server_socket(sockfd) != 0){perror("create server sock failed\n");return 1;}set_socket_non_blocking(sockfd);printf("server is running!\n");//创建一个epoll的句柄//int epoll_create(int size)  //Since Linux 2.6.8, the size argument is unused. (The kernel dynamically sizes the required data structures without needing this initial hint.)efd = epoll_create(MAXEVENT);if (efd == -1){perror ("epoll_create");abort ();}//注册新事件到epoll efdevent.data.fd = sockfd;event.events = EPOLLIN | EPOLLET;s = epoll_ctl(efd, EPOLL_CTL_ADD, sockfd, &event);if (s == -1){perror ("epoll_ctl EPOLL_CTL_ADD failed");abort ();}events = (epoll_event*)calloc(MAXEVENT, sizeof(event));while (1){int n, i;n = epoll_wait(efd, events, MAXEVENT, -1);for (i = 0; i < n; i++){    //fd error      if ((events[i].events & EPOLLERR) ||              (events[i].events & EPOLLHUP) ||              (!(events[i].events & EPOLLIN)))        {perror("epoll error\n");close (events[i].data.fd);continue;}//新连接      else if (sockfd == events[i].data.fd)       {while (1){struct sockaddr in_addr;socklen_t in_len;int infd;char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];//接受连接in_len = sizeof(in_addr);infd = accept(sockfd, &in_addr, &in_len);if (infd == -1){if ((errno == EAGAIN) ||  (errno == EWOULDBLOCK)){//已接受所有连接      break;    }else{perror ("accept");break;}}s = getnameinfo (&in_addr, in_len,               hbuf, sizeof hbuf,               sbuf, sizeof sbuf,               NI_NUMERICHOST | NI_NUMERICSERV);if (s == 0){  printf("Accepted connection on descriptor %d "         "(host=%s, port=%s)\n", infd, hbuf, sbuf);}/* 设置新接受的socket连接无阻塞*/s = set_socket_non_blocking (infd);if (s == -1){return 1;}//注册新事件到epollevent.data.fd = infd;event.events = EPOLLIN | EPOLLET;s = epoll_ctl(efd, EPOLL_CTL_ADD, infd, &event);if (s == -1){  perror ("epoll_ctl");  return 1;}}continue;            }            //数据可读          else            {              int done = 0;              while (1)              {                  ssize_t count;                  char buf[512];                  count = read(events[i].data.fd, buf, sizeof(buf));                  if(count == -1)                  {                      //数据读完                      if (errno != EAGAIN)                      {                          perror ("read");                          done = 1;                      }                      break;                  }                  else if(count == 0)                  {                      /* End of file. The remote has closed the                         connection. */                      done = 1;                      break;                   }                  printf("recv: %s\n", buf);                }              if (done)              {                  printf ("Closed connection on descriptor %d\n", events[i].data.fd);                  close (events[i].data.fd);              }            }        }    }  free (events);  close(sockfd);  return 0;}
client端:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <netdb.h>#include <sys/types.h>#include <netinet/in.h>#include <sys/socket.h>#include <unistd.h>int main(int argc,char *argv[]){if(argc!=3){printf("%s: input IP & port\n",argv[0]);return 1;}int sockfd,numbytes;char buf[100] = "hello world";struct hostent *he;struct sockaddr_in their_addr;//将基本名字和地址转换he = gethostbyname(argv[1]);//建立一个TCP套接口if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){perror("socket");exit(1);}//初始化结构体their_addr.sin_family = AF_INET;their_addr.sin_port = htons(atoi(argv[2]));their_addr.sin_addr = *((struct in_addr *)he->h_addr);bzero(&(their_addr.sin_zero),8);//和服务器建立连接if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr))==-1){perror("connect");exit(1);}//向服务器发送字符串while(1){if(send(sockfd,buf,strlen(buf),0)==-1){perror("send");exit(1);}sleep(2);}memset(buf,0,sizeof(buf));close(sockfd);return 0;}

运行:
$ ./bin/server    
server is running!
Accepted connection on descriptor 5 (host=10.32.49.10, port=39001)
recv: hello world
recv: hello world
recv: hello world
recv: hello world

./bin/client 10.32.49.10 7092

异步IO:
在一个进程发出IO请求后直接返回,内核在整个操作(包括将数据复制到进程缓冲区)完成后通知进程,如图:

代码
server端:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <netinet/in.h>#include <sys/socket.h>#include <unistd.h>#include <fcntl.h>#include <aio.h>#include <pthread.h>#define BUF_SIZE 1024void aio_completion_handler(sigval_t sigval);void setup_io(int fd, aiocb& my_aiocb){//初始化AIO请求bzero( (char *)&my_aiocb, sizeof(struct aiocb) );my_aiocb.aio_fildes = fd;my_aiocb.aio_buf = malloc(BUF_SIZE+1);my_aiocb.aio_nbytes = BUF_SIZE;my_aiocb.aio_offset = 0;//设置线程回调函数my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;my_aiocb.aio_sigevent.sigev_notify_function = aio_completion_handler;my_aiocb.aio_sigevent.sigev_notify_attributes = NULL;my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;}//回调函数void aio_completion_handler(sigval_t sigval){struct aiocb *req;int ret;req = (struct aiocb *)sigval.sival_ptr;if (aio_error(req) == 0) {if((ret = aio_return(req)) > 0){printf("Thread id %u recv:%s\n", (unsigned int)pthread_self(), (char*)req->aio_buf);}}char* buf = (char*)req->aio_buf;if(send(req->aio_fildes, buf, strlen(buf), 0) == -1){perror("send");return;}close(req->aio_fildes);return;}int main(){int sockfd;int sin_size;struct sockaddr_in addr, cliaddr;//创建socketif((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){perror("createSocket");return -1;}//初始化socket结构memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(7092);addr.sin_addr.s_addr = htonl(INADDR_ANY);//绑定套接口if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr))==-1){perror("bind");return -1;}//创建监听套接口if(listen(sockfd,10)==-1){perror("listen");return -1;}printf("server is running!\n");//等待连接while(1) {int new_fd;  struct aiocb my_aiocb;sin_size = sizeof(struct sockaddr_in);//接受连接if((new_fd = accept(sockfd, (struct sockaddr *)&cliaddr, (socklen_t*)&sin_size))==-1){perror("accept");return -1;}printf("Thread id %u accept connect, fd: %d\n", (unsigned int)pthread_self(), new_fd);setup_io(new_fd, my_aiocb);aio_read(&my_aiocb);}close(sockfd);}

client端:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <netdb.h>#include <sys/types.h>#include <netinet/in.h>#include <sys/socket.h>#include <unistd.h>int main(int argc,char *argv[]){if(argc!=3){printf("%s: input IP & port\n",argv[0]);return 1;}int sockfd,numbytes;char buf[100] = "hello world";struct hostent *he;struct sockaddr_in their_addr;//将基本名字和地址转换he = gethostbyname(argv[1]);//建立一个TCP套接口if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){perror("socket");exit(1);}//初始化结构体their_addr.sin_family = AF_INET;their_addr.sin_port = htons(atoi(argv[2]));their_addr.sin_addr = *((struct in_addr *)he->h_addr);bzero(&(their_addr.sin_zero),8);//和服务器建立连接if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr))==-1){perror("connect");exit(1);}//向服务器发送字符串if(send(sockfd,buf,strlen(buf),0)==-1){perror("send");exit(1);}         //接收数据if((numbytes = recv(sockfd, buf, 100, 0)) == -1){perror("recv");return 1;}printf("recv: %s\n", buf);close(sockfd);return 0;}

运行:
$ ./bin/server
server is running!
Thread id 2505492000 accept connect, fd: 4
Thread id 1084246368 recv:hello world
(注意:线程ID不一样)

$ ./bin/client 10.32.49.10 7092
recv: hello world


信号驱动IO:
使用信号驱动I/O时,当网络套接字可读后,内核通过发送SIGIO信号通知应用进程,于是应用可以开始读取数据。如图:

为了让套接字描述符可以工作于信号驱动I/O模式,应用进程必须完成如下三步设置:
1.注册SIGIO信号处理程序。(安装信号处理器)
2.使用fcntl的F_SETOWN命令,设置套接字所有者。(设置套接字的所有者)
3.使用fcntl的F_SETFL命令,置O_ASYNC标志,允许套接字信号驱动I/O。(允许这个套接字进行信号输入输出)
注意,必须保证在设置套接字所有者之前,向系统注册信号处理程序,否则就有可能在fcntl调用后,信号处理程序注册前内核向应用交付SIGIO信号,导致应用丢失此信号。

在UDP编程中使用信号驱动I/O,此时SIGIO信号产生于下面两种情况:
套接字收到一个数据报。
套接字上发生了异步错误。
因此,当应用因为收到一个UDP数据报而产生的SIGIO时,要么可以调用recvfrom读取该数据报,要么得到一个异步错误。
对于TCP编程,信号驱动I/O就没有太大意义了,因为对于流式套接字而言,有很多情况都可以导致SIGIO产生,而应用又无法区分是什么具体情况导致该信号产生的
信号驱动IO模型在网络编程中极少使用,这里不写例子了,有兴趣的同学可以参考:http://blog.csdn.net/yskcg/article/details/6021275

例子源码打包下载:
http://download.csdn.net/detail/yfkiss/4288465

reference:
UNIX网络编程,第一卷
Linux下 fcntl 函数用法说明
Linux Epoll详解
使用异步 I/O 大大提高应用程序的性能
信号驱动IO
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 3岁幼儿不会数数怎么办 四岁宝宝不认识数字怎么办 三岁宝宝不认识数字怎么办 4岁半了不认识数字怎么办 5岁小朋友数字不认识怎么办 孩子读一年级字都不识几个怎么办 宝宝二周多了不分颜色怎么办 4岁儿童手指脱皮怎么办 孩子老是不会写2怎么办 宝宝胃浅容易吐怎么办 农村户口小孩去城里读书怎么办 小孩从城市回农村读书怎么办 天冷了怎么办教案反思 小孩上幼儿园不爱学习怎么办 曰光灯管监控要反光怎么办 立邦乳胶漆墙面脏了怎么办 橱柜门黑色边颜色花了怎么办 地板上有真实漆怎么办 吸了泡泡球的气怎么办 办公室上班坐着太累怎么办 金雀盆景生虫怎么办 租的房子墙脏怎么办 租住个厕所对着卧室怎么办 农村的房子没有房产证怎么办 墙面漆颜色深了怎么办 浴室门对卧室门怎么办 卧室40多个平方太大怎么办 卧室门洞太窄了怎么办 刷漆的墙面脏了怎么办 卧室颜色刷重了怎么办 感觉房子买小了怎么办 淋浴房一边是窗怎么办 1岁宝宝让狗咬了怎么办 2岁宝宝让狗咬了怎么办 带4个小孩只买两张儿童票怎么办 墙面补漆颜色有色差怎么办 墙壁刷的乳胶漆颜色不满意怎么办 门被水泡的鼓了怎么办 定制衣柜做大了怎么办 找平的地面高了怎么办 卫生间推拉门锁坏了怎么办