一、實驗目的
理解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)。
在前兩種情況的等待中,如果進程捕獲了一個信號並從信號處理程序返回,那麼等待一般被中斷。
參數readset、writeset和execeptset指定讓內核測試讀、寫、異常條件的描述字。如果我們對它們不感興趣,可將其設為空指針。
select()函數使用描述字集為參數readset(writeset或exceptset)指定多個描述字,描述字集是一個整數數組,每個數中的每一個對應於一個描述字,例如32位整數,則數組的第一個元素對應於0~31描述字,第二個元素對應於32~63描述字等。
參數readset、writeset、exceptset為值—結果參數,調用select時,指定我們所關心的描述字,返回時結果指示那些描述字已准備好。
參數maxfdp1指定被測試的描述字的個數,它是被測試的最大描述字加1。如要測試1,2,4描述字,則必須測試0,1,2,3,4共5個描述字。
采用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.c和mproc_client.c所在目錄。
4、執行命令g++ –oioio.c生成可執行文件io。
5、執行命令./io,運行服務器端。
6、打開第2個“終端”,執行命令進入io.c和mproc_client.c所在目錄。
7、執行命令./mproc_client127.0.0.1,模擬客戶1。
8、打開第3個“終端”,執行命令進入io.c和mproc_client.c所在目錄。
9、執行命令./mproc_client127.0.0.1,模擬客戶2。
10、程序運行結果如下:
服務器端:
客戶1:
客戶2 :
11、在客戶端按下Ctrl+D,關閉客戶連接。
12、認真分析源代碼,體會單線程並發服務器程序和客戶程序的編寫。
六、參考程序(io.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/time.h>
#define PORT 1234
#define BACKLOG 5
#define MAXDATASIZE 1000
typedef struct {
int fd;
char *name;
struct sockaddr_in addr;
char *data;
}CLIENT;
void process_cli(CLIENT *client, char* recvbuf, int len);
void savedata(char*recvbuf, int len, char* data);
main()
{
int i, maxi,maxfd,sockfd;
int nready;
ssize_t n;
fd_set rset, allset;
int listenfd,connectfd;
struct sockaddr_in server;
CLIENT client[FD_SETSIZE];
char recvbuf[MAXDATASIZE];
socklen_t sin_size;
if ((listenfd =socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Creatingsocket failed.");
exit(1);
}
int opt =SO_REUSEADDR;
setsockopt(listenfd,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr= htonl (INADDR_ANY);
if (bind(listenfd,(struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {
perror("Bind()error.");
exit(1);
}
if(listen(listenfd,BACKLOG)== -1){
perror("listen()error\n");
exit(1);
}
sin_size=sizeof(struct sockaddr_in);
maxfd = listenfd;
maxi = -1;
for (i = 0; i <FD_SETSIZE; i++) {
client[i].fd =-1;
}
FD_ZERO(&allset);
FD_SET(listenfd,&allset);
while(1)
{
struct sockaddr_in addr;
rset = allset;
nready =select(maxfd+1, &rset, NULL, NULL, NULL);
if(FD_ISSET(listenfd, &rset)) {
if ((connectfd =accept(listenfd,(struct sockaddr *)&addr,&sin_size))==-1) {
perror("accept() error\n");
continue;
}
for (i = 0; i <FD_SETSIZE; i++)
if(client[i].fd < 0) {
client[i].fd = connectfd;
client[i].name = new char[MAXDATASIZE];
client[i].addr = addr;
client[i].data = new char[MAXDATASIZE];
client[i].name[0] = '\0';
client[i].data[0] = '\0';
printf("You got a connection from %s. ",inet_ntoa(client[i].addr.sin_addr) );
break;
}
if (i ==FD_SETSIZE) printf("too many clients\n");
FD_SET(connectfd, &allset);
if (connectfd> maxfd) maxfd = connectfd;
if (i >maxi) maxi = i;
if (--nready<= 0) continue;
}
for (i = 0; i <=maxi; i++) {
if ( (sockfd= client[i].fd) < 0) continue;
if(FD_ISSET(sockfd, &rset)) {
if ( (n =recv(sockfd, recvbuf, MAXDATASIZE,0)) == 0) {
close(sockfd);
printf("Client( %s ) closed connection. User's data:%s\n",client[i].name,client[i].data);
FD_CLR(sockfd, &allset);
client[i].fd = -1;
delete client[i].name;
delete client[i].data;
}
else
process_cli(&client[i], recvbuf, n);
if(--nready <= 0) break;
}
}
}
close(listenfd);
}
void process_cli(CLIENT *client, char* recvbuf, int len)
{
char sendbuf[MAXDATASIZE];
recvbuf[len-1] ='\0';
if(strlen(client->name) == 0) {
memcpy(client->name,recvbuf, len);
printf("Client'sname is %s.\n",client->name);
return;
}
printf("Receivedclient( %s ) message: %s\n",client->name, recvbuf);
savedata(recvbuf,len,client->data);
for (int i1 = 0; i1< len - 1; i1++) {
sendbuf[i1] =recvbuf[len - i1 -2];
}
sendbuf[len - 1] ='\0';
send(client->fd,sendbuf,strlen(sendbuf),0);
}
void savedata(char *recvbuf, int len, char *data)
{
int start =strlen(data);
for (int i = 0; i <len; i++) {
data[start + i]= recvbuf[i];
}
}
摘自 yihaibobb的專欄