I/O多路复用服务器编程

一、实验目的

理解I/O多路复用技术的原理。

学会编写基本的单线程并发服务器程序和客户程序。

二、实验平台

ubuntu-8.04操作系统

三、实验内容

采用I/O多路复用技术实现单线程并发服务器,完成使用一个线程处理并发客户请求的功能。

四、实验原理

除了可以采用多进程和多线程方法实现并发服务器之外,还可以采用I/O多路复用技术。通过该技术,系统内核缓冲I/O数据,当某个I/O准备好后,系统通知应用程序该I/O可读或可写,这样应用程序可以马上完成相应的I/O操作,而不需要等待系统完成相应I/O操作,从而应用程序不必因等待I/O操作而阻塞。

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

对于I/O复用典型的应用如下:

1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。

2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。

3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。

5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

I/O复用调用select()poll()函数,并在该函数上阻塞,等待数据报套接口可读;当select()返回可读条件时,调用recvfrom()将数据报拷贝到应用程序缓冲区中,如图8.1所示。

8.1 I/O多路复用工作过程

select()函数:

select()函数允许进程指示内核等待多个事件中的任意一个发生,并仅在一个或多个事件发生或经过指定的时间时才唤醒进程。这个函数的形式如下:

-------------------------------------------------------------------
#include<sys/select.h>

#include<sys/time.h>

intselect(intmaxfdp1,fd_set*readset,fd_set*writeset,fd_set*execepset,conststructtimeval*timeout);

返回:返回值表示所有描述字集中已准备好的描述字个数。如定时到,则返回0;若出错,则返回-1

-------------------------------------------------------------------

在上面的参数中可以看到一个timeval结构,这个结构可以提供秒数和毫秒数成员,形式如下:

structtimeval

{

long tv_sec; /second*/

long tv_usec; /*microsecond*/

}

这个timeval结构有以下3种可能:

1)永远等待下去:仅在有一个描述字准备好I/O时才返回,因此可以将参数timeout设置为空指针。

2)等待固定时间:在有一个描述字准备好I/O时返回,但不超过由timeout参数所指timeval结构中指定的秒数和微秒数。

3)根本不用等待:检查描述字后立即返回,这称为轮询(polling)。

在前两种情况的等待中,如果进程捕获了一个信号并从信号处理程序返回,那么等待一般被中断。

参数readsetwritesetexeceptset指定让内核测试读、写、异常条件的描述字。如果我们对它们不感兴趣,可将其设为空指针。

select()函数使用描述字集为参数readsetwritesetexceptset)指定多个描述字,描述字集是一个整数数组,每个数中的每一个对应于一个描述字,例如32位整数,则数组的第一个元素对应于031描述字,第二个元素对应于3263描述字等。

参数readsetwritesetexceptset为值结果参数,调用select时,指定我们所关心的描述字,返回时结果指示那些描述字已准备好。

参数maxfdp1指定被测试的描述字的个数,它是被测试的最大描述字加1。如要测试124描述字,则必须测试012345个描述字。

采用select()函数实现I/O多路复用的基本步骤如下:

1)清空描述符集合;

2)建立需要监视的描述符与描述符集合的联系;

3)调用select()函数;

4)检查所有需要监视的描述符,利用FD_ISSET宏判断是否已经准备好;

5)对已经准备好的描述符进行I/O操作。


五、实验步骤

1、登陆进入ubuntu操作系统,新建一个文件,命名为io.c

2、在io.c中编写相应代码并保存,作为服务器端程序。客户端程序代码同上次的mproc_client.c一致,博客地址:http://blog.csdn.net/yueguanghaidao/article/details/7060350

3、打开一个终端,执行命令进入io.cmproc_client.c所在目录。

4、执行命令g++oioio.c生成可执行文件io

5、执行命令./io,运行服务器端。

6、打开第2终端,执行命令进入io.cmproc_client.c所在目录。

7、执行命令./mproc_client127.0.0.1,模拟客户1

8、打开第3终端,执行命令进入io.cmproc_client.c所在目录。

9、执行命令./mproc_client127.0.0.1,模拟客户2

10、程序运行结果如下:

服务器端:

I/O多路复用服务器编程_第1张图片


客户1

I/O多路复用服务器编程_第2张图片


客户2


11、在客户端按下Ctrl+D,关闭客户连接。

12、认真分析源代码,体会单线程并发服务器程序和客户程序的编写。

六、参考程序(io.c


[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include<sys/types.h>  
  6. #include<sys/socket.h>  
  7. #include<netinet/in.h>  
  8. #include<arpa/inet.h>  
  9. #include<sys/time.h>  
  10. #define PORT 1234  
  11. #define BACKLOG 5  
  12. #define MAXDATASIZE  1000  
  13. typedef struct {  
  14. int fd;  
  15. char  *name;  
  16. struct  sockaddr_in   addr;  
  17. char *data;  
  18. }CLIENT;  
  19. void  process_cli(CLIENT  *client, char* recvbuf, int len);  
  20. void savedata(char*recvbuf, int len, char* data);  
  21.   
  22. main()  
  23. {  
  24. int i, maxi,maxfd,sockfd;  
  25. int nready;  
  26. ssize_t  n;  
  27. fd_set  rset, allset;  
  28. int listenfd,connectfd;  
  29. struct sockaddr_in  server;  
  30. CLIENT  client[FD_SETSIZE];  
  31. char recvbuf[MAXDATASIZE];  
  32. socklen_t  sin_size;  
  33.   
  34. if ((listenfd =socket(AF_INET, SOCK_STREAM, 0)) == -1) {  
  35. perror("Creatingsocket failed.");  
  36. exit(1);  
  37. }  
  38.   
  39. int opt =SO_REUSEADDR;  
  40. setsockopt(listenfd,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  
  41.   
  42. bzero(&server,sizeof(server));  
  43. server.sin_family=AF_INET;  
  44. server.sin_port=htons(PORT);  
  45. server.sin_addr.s_addr= htonl (INADDR_ANY);  
  46. if (bind(listenfd,(struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {  
  47. perror("Bind()error.");  
  48. exit(1);  
  49. }  
  50.   
  51. if(listen(listenfd,BACKLOG)== -1){  
  52. perror("listen()error\n");  
  53. exit(1);  
  54. }  
  55.   
  56. sin_size=sizeof(struct   sockaddr_in);  
  57. maxfd = listenfd;  
  58. maxi = -1;  
  59. for (i = 0; i <FD_SETSIZE; i++) {  
  60. client[i].fd =-1;  
  61. }  
  62. FD_ZERO(&allset);  
  63. FD_SET(listenfd,&allset);  
  64.   
  65. while(1)  
  66. {  
  67. struct sockaddr_in   addr;  
  68. rset = allset;  
  69. nready =select(maxfd+1, &rset, NULL, NULL, NULL);  
  70.   
  71. if(FD_ISSET(listenfd, &rset)) {  
  72. if ((connectfd =accept(listenfd,(struct sockaddr *)&addr,&sin_size))==-1) {  
  73. perror("accept() error\n");  
  74. continue;  
  75. }  
  76. for (i = 0; i <FD_SETSIZE; i++)  
  77. if(client[i].fd < 0) {  
  78. client[i].fd = connectfd;  
  79. client[i].name = new char[MAXDATASIZE];  
  80. client[i].addr = addr;  
  81. client[i].data = new char[MAXDATASIZE];  
  82. client[i].name[0] = '\0';  
  83. client[i].data[0] = '\0';  
  84. printf("You got a connection from %s. ",inet_ntoa(client[i].addr.sin_addr) );  
  85. break;  
  86. }  
  87. if (i ==FD_SETSIZE) printf("too many clients\n");  
  88. FD_SET(connectfd, &allset);  
  89. if (connectfd> maxfd) maxfd = connectfd;  
  90. if (i >maxi) maxi = i;  
  91. if (--nready<= 0) continue;  
  92. }  
  93.   
  94. for (i = 0; i <=maxi; i++) {  
  95. if ( (sockfd= client[i].fd) < 0) continue;  
  96. if(FD_ISSET(sockfd, &rset)) {  
  97. if ( (n =recv(sockfd, recvbuf, MAXDATASIZE,0)) == 0) {  
  98. close(sockfd);  
  99. printf("Client( %s ) closed connection. User's data:%s\n",client[i].name,client[i].data);  
  100. FD_CLR(sockfd, &allset);  
  101. client[i].fd = -1;  
  102. delete  client[i].name;  
  103. delete  client[i].data;  
  104. }  
  105. else  
  106. process_cli(&client[i], recvbuf, n);  
  107. if(--nready <= 0) break;  
  108. }  
  109. }  
  110. }  
  111. close(listenfd);  
  112. }  
  113.   
  114. void process_cli(CLIENT *client, char* recvbuf, int len)  
  115. {  
  116. char  sendbuf[MAXDATASIZE];  
  117. recvbuf[len-1] ='\0';  
  118. if(strlen(client->name) == 0) {  
  119. memcpy(client->name,recvbuf, len);  
  120. printf("Client'sname is %s.\n",client->name);  
  121. return;  
  122. }  
  123.   
  124. printf("Receivedclient( %s ) message: %s\n",client->name, recvbuf);  
  125. savedata(recvbuf,len,client->data);  
  126. for (int i1 = 0; i1< len - 1; i1++) {  
  127. sendbuf[i1] =recvbuf[len - i1 -2];  
  128. }  
  129. sendbuf[len - 1] ='\0';  
  130.   
  131. send(client->fd,sendbuf,strlen(sendbuf),0);  
  132. }  
  133.   
  134. void savedata(char  *recvbuf, int len, char   *data)  
  135. {  
  136. int start =strlen(data);  
  137. for (int i = 0; i <len; i++) {  
  138. data[start + i]= recvbuf[i];  
  139. }  
  140. }  

你可能感兴趣的:(I/O多路复用服务器编程)