Linux網路程式設計之I/O複用迴圈伺服器
原文:http://blog.csdn.net/chenjin_zhong/article/details/7270166
1.介紹
在前幾節,我們介紹了迴圈伺服器,併發伺服器. 簡單的迴圈伺服器每次只能處理一個請求,即處理的請求是序列的。而併發伺服器可以通過建立多個程序或者是執行緒來併發的處理多個請求。但由於程序或執行緒的切換會帶來一定的開銷。而且隨著客戶端請求的增多,建立的執行緒或程序的數目也越來越多,開銷勢必會增加。因此,本文提出了I/O複用的迴圈伺服器。I/O複用的迴圈伺服器建立兩個執行緒,一個是客戶端連線處理執行緒,專門用來處理客戶端的連線,當有客戶端到來的時候,此執行緒把客戶端的套接字描述符放到一塊公共的區域中。另一個是業務處理執行緒,此執行緒輪循(select)客戶端套接字描述符集合中有沒有資料到來,如果有資料到來,那麼就進行處理。
2. I/O複用迴圈伺服器處理流程
socket(...);
bind(...);
listen(...);
pthread_create(...);
pthread_join(..);
close(...); //關閉伺服器套接字
連線處理執行緒:
while(1){
accept(...);
store(...);//儲存客戶端套接字描述符
}
業務處理執行緒:
while(1){
get(...);//取出套接字描述符放到FD_SET
select(...);
recv(....);
process(...);
send(...);
close(....);
}
從演算法的主要流程可以看出,I/O複用迴圈伺服器只用兩個執行緒,一個是請求業務連線執行緒,專門處理連線。另一個是業務處理執行緒,輪循客戶端的套接字有沒有資料。
3. 相關例子
伺服器;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <time.h>
#include <pthread.h>
/**
I/O複用迴圈伺服器
I/O併發伺服器隨著客戶端的增多,必須增加處理單元,系統的負載會移動多個處理單元的切換上,切換程序或者是執行緒
而I/O複用伺服器包括兩個執行緒,一個是業務連線執行緒,專門處理客戶端的連線,另一個是業務請求處理執行緒,對多個客戶端描述符進行一定時間的等待,即select監聽多個描述符
**/
#define PORT 8888
#define CLIENTNUM 1024
#define BUFFERSIZE 1024
#define BACKLOG 10
static int connect_host[CLIENTNUM];
static int connect_number=0;//連線的客戶數
static void* handle_connect(void*argv){//業務連線函式,處理客戶端的連線,將客戶端的套接字描述符加入到連線池中
int ret;
int s;
int sc;
s=*((int*)argv);//服務端套接字描述符
int i=0;
struct sockaddr_in client_addr;
int len;
len=sizeof(struct sockaddr_in);
for(;;){//監聽有沒有客戶端到來
sc=accept(s,(struct sockaddr*)&client_addr,&len);
printf("a client connect,from:%s\n",inet_ntoa(client_addr.sin_addr));
if(sc>0){
for(i=0;i<CLIENTNUM;i++){
if(connect_host[i]==-1){
connect_host[i]=sc;
connect_number++;
break;
}
}
}
}
}
//請求業務處理執行緒
static void* handle_request(void*argv){
time_t now;
char buffer[BUFFERSIZE];
int size;
//設定輪循的時間,每隔1秒
struct timeval tv;
tv.tv_sec=1;
tv.tv_usec=0;
int ret;
int i=0;
int maxfd;
fd_set scanfd;
for(;;){
FD_ZERO(&scanfd);//清空檔案描述符集合
//將檔案描述符放入檔案描述符集合
for(i=0;i<CLIENTNUM;i++){
if(connect_host[i]!=-1){
FD_SET(connect_host[i],&scanfd);
if(maxfd<connect_host[i]){
maxfd=connect_host[i];
}
}
}
//select
ret=select(maxfd+1,&scanfd,NULL,NULL,&tv);//監控讀檔案描述符集,看看是否有資料可讀
switch(ret){
case -1://錯誤
break;
case 0://超時
break;
default://有資料到來
if(connect_number<0)
break;
for(i=0;i<CLIENTNUM;i++){
if(connect_host[i]!=-1){
if(FD_ISSET(connect_host[i],&scanfd)){//檔案描述符在檔案集合中
memset(buffer,0,BUFFERSIZE);
size=recv(connect_host[i],buffer,BUFFERSIZE,0);
if(size>0&&!strncmp(buffer,"TIME",4)){//時間請求
memset(buffer,0,BUFFERSIZE);
now=time(NULL);
sprintf(buffer,"%24s\r\n",ctime(&now));
send(connect_host[i],buffer,strlen(buffer),0);
}
//更新檔案描述符陣列中的值
connect_host[i]=-1;
connect_number--;
close(connect_host[i]);//關閉客戶端套接字描述符
}
}
}
break;
}
}
}
int main(int argc,char*argv[]){
int ret;
int s;
struct sockaddr_in server_addr;
int i;
pthread_t thread[2];//兩個執行緒,一個是處理連線,另一個是處理請求
//建立TCP套接字
s=socket(AF_INET,SOCK_STREAM,0);
memset(connect_host,-1,CLIENTNUM);//將儲存套接字描述符的陣列為-1
if(s<0){
perror("socket error");
return -1;
}
//將地址結構繫結到套接字描述符
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(PORT);
ret=bind(s,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));
if(ret<0){
perror("bind error");
return -1;
}
//監聽
ret=listen(s,BACKLOG);
if(ret<0){
perror("listen error");
return -1;
}
pthread_create(&thread[0],NULL,handle_connect,(void*)&s);//處理客戶端連線,傳遞的是伺服器的套接字描述符
pthread_create(&thread[1],NULL,handle_request,NULL);//handle_request執行緒回撥函式
//等待執行緒結束
for(i=0;i<2;i++){
pthread_join(thread[i],NULL);
}
close(s);
}
客戶端:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFERSIZE 1024
int main(int argc,char*argv[]){
int s;
int ret;
int size;
struct sockaddr_in server_addr;
char buffer[BUFFERSIZE];
s=socket(AF_INET,SOCK_STREAM,0);
if(s<0){
perror("socket error");
return -1;
}
bzero(&server_addr,sizeof(server_addr));
//將地址結構繫結到套接字
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(PORT);
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
//連線伺服器
ret=connect(s,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(ret==-1){
perror("connect error");
return -1;
}
memset(buffer,0,BUFFERSIZE);
strcpy(buffer,"TIME");
size=send(s,buffer,strlen(buffer),0);
if(size<0){
perror("send error");
return -1;
}
memset(buffer,0,BUFFERSIZE);
size=recv(s,buffer,BUFFERSIZE,0);
if(size<0){
perror("recv error");
return;
}
printf("%s",buffer);
close(s);
return 0;
}
執行結果:
[[email protected] 14章伺服器模式]# ./select-tcp
a client connect,from:127.0.0.1
[[email protected] 14章伺服器模式]# ./circle-tcpc14
Sat Feb 18 14:28:10 2012
總結:本文主要針對簡單迴圈伺服器,併發伺服器的不足,介紹了I/O複用的迴圈伺服器,最後給出了例項.
相關推薦
Linux網路程式設計之I/O複用迴圈伺服器
原文:http://blog.csdn.net/chenjin_zhong/article/details/7270166 1.介紹 在前幾節,我們介紹了迴圈伺服器,併發伺服器. 簡單的迴圈伺服器每次只能處理一個請求,即處理的請求是序列的。而併發伺服器可以通過建立多
Linux----網路程式設計(I/O複用之select系統呼叫)
io_select_ser.c 1. #include <string.h> 2. #include <assert.h> 3. #include <unistd.h> 4. #include <stdio.h> 5. #in
網路程式設計中I/O複用select的用法
網路程式設計select的用法 select使用流程圖 在網路程式設計中需要新增的程式碼行以及意義 例程 參考文獻及部落格 注:本文對select函式、相關引數及結構體不做解釋 select使用流程
嵌入式Linux網路程式設計,I/O多路複用,epoll()示例,epoll()客戶端,epoll()伺服器,單鏈表
文章目錄 1,I/O多路複用 epoll()示例 1.1,epoll()---net.h 1.2,epoll()---client.c 1.3,epoll()---sever.c 1.4,epoll()---linklist.h
嵌入式Linux網路程式設計,I/O多路複用,poll()示例,poll()客戶端,poll()伺服器,單鏈表
文章目錄 1,IO複用poll()示例 1.1,poll()---net.h 1.2,poll()---client.c 1.3,poll()---sever.c 1.4,poll()---linklist.h 1.5,p
嵌入式Linux網路程式設計,I/O多路複用,select()示例,select()客戶端,select()伺服器,單鏈表
文章目錄 1,IO複用select()示例 1.1 select()---net.h 1.2 select()---client.c 1.3 select()---sever.c 1.4 select()---linklist.h
嵌入式Linux網路程式設計,I/O多路複用,阻塞I/O模式,非阻塞I/O模式fcntl()/ioctl(),多路複用I/O select()/pselect()/poll(),訊號驅動I/O
文章目錄 1,I/O模型 2,阻塞I/O 模式 2.1,讀阻塞(以read函式為例) 2.2,寫阻塞 3,非阻塞模式I/O 3.1,非阻塞模式的實現(fcntl()函式、ioctl() 函式)
Linux 併發echo伺服器之I/O複用之select
select 簡介: 將待監聽套接字加入集合,監測,若有資料到來,進行echo,若有新連線請求到來,accept並將對應套接字加入集合 函式: int select(int , fd_set* rd, fd_set* wr , fd_set* except ,
Linux高效能服務程式設計(I/O複用)
I/O複用(本身是阻塞的)網路程式需要使用I/O複用技術的情況:1.客戶端程式需要同時處理多個socket。2.客戶端需要同時處理使用者輸入和網路連線3.TCP伺服器要同時處理監聽socket和連線socket4.伺服器要同時處理TCP請求和UDP請求5.伺服器要同時監聽多個
嵌入式Linux應用程式設計之I/O程序(上)
【1】i/o 本質就是輸入輸出函式,也是讀寫函式 【2】系統呼叫和庫函式 系統呼叫: 使用函式控制linux核心,linux核心來操作硬體 庫函式: 庫函式的本質還是系統呼叫,只不過需要在記憶體當中開闢一塊空間(緩衝區),從而減少系統呼叫的次數 【3】io分類
《Linux網路程式設計》: 埠複用(多個套接字繫結同一個埠)
在《繫結( bind )埠需要注意的問題》提到:一個網路應用程式只能繫結一個埠( 一個套接字只能繫結一個埠 )。 請檢視《Linux網路程式設計》: 繫結( bind )埠需要注意的問題 實際上,預設的情況下,如果一個網路應用程式的一個套接字 綁定了一個埠( 佔用了 80
Linux C網路程式設計 ————6、IO複用併發伺服器程式設計
伺服器端程式碼實現#include<stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h>
linux應用程式設計之I/O程式設計
相關函式: 一、相關函式 1 fopen() 呼叫open開啟指定的檔案,返回 一個檔案描述符(int 型),分配一個file結構體,包括檔案描述符,I/O快取區,和當前讀寫位置等資訊。 2 fgetc() 通過傳入的資訊到I/O快取區讀取一個字元,如果能讀取成功則直接返回
Linux網路程式設計---I/O複用模型之epoll
Linux網路程式設計—I/O複用模型之epoll 1. epoll模型簡介 epoll是Linux多路服用IO介面select/poll的加強版,e對應的英文單詞就是enhancement,中文翻譯為增強,加強,提高,充實的意思。所以epoll模型會顯
Linux網路程式設計——I/O複用函式之epoll
https://blog.csdn.net/lianghe_work/article/details/46544567一、epoll概述epoll 是在 2.6 核心中提出的,是之前的 select() 和 poll() 的增強版本。相對於 select() 和 poll()
Linux網路程式設計——tcp併發伺服器(I/O複用之select)
與多執行緒、多程序相比,I/O複用最大的優勢是系統開銷小,系統不需要建立新的程序或者執行緒,也不必維護這些執行緒和程序。 程式碼示例: #include <stdio.h> #include <unistd.h> #include <
Linux網路程式設計——I/O複用之poll函式
#include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/select.h> #include <sys/time.
Linux網路程式設計之TCP(下)- I/O資料複用
轉自http://blog.csdn.net/chenjin_zhong/article/details/7255705 1.介紹 網路資料的傳送與接收有多種方式,可以直接直接從套接字讀取資料或向套接字寫入函式,如read/write. 也可以通過向量傳送與接收資料
linux網路程式設計之TCP狀態轉換及埠複用
(1)TCP狀態轉換圖 其中圖中分為三種狀態:實線代表的主動發起連線,虛線代表的被動發起連線,細實線代表的可以雙向發起連線的狀態。 主動發起連線方狀態變化:1)主動發起連線的一方傳送SYN標誌位,進入SYN_SENT狀態,等待接收被髮起連線方
Linux I/O複用之select函式詳解
置頂 2017年02月12日 20:50:08 難免有錯_ 閱讀數:7438更多 select函式的功能和呼叫順序 使用select函式時統一監視多個檔案描述符的: 1、 是否存在套接字接收資料? 2、 無需阻塞傳輸資料的套接字有哪些? 3、 哪些套接字發生了