程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> I/O多路復用服務器編程

I/O多路復用服務器編程

編輯:C++入門知識

一、實驗目的

理解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的專欄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved