Linux網路程式設計之高階併發伺服器(轉)
1. 介紹
在上一節,我們介紹了Linux簡單的併發伺服器,通過在伺服器端建立多個子程序,來接收客戶端的請求,實現併發處理,但這種方式明顯有缺陷,伺服器並不知道客戶端請求的數量,所以事先建立的程序數不好確定。所以,這裡介紹三種高階併發伺服器模式。第一種是伺服器端統一accept,接收客戶端的到來,然後為每個客戶端分配一個程序去處理. 第二種是統一accept接收請求,然後為每個客戶端分配一個執行緒去處理。第三種建立多個執行緒去處理客戶端請求,每個執行緒獨自監聽客戶端的請求。顯然,第一種方案解決了簡單伺服器的併發問題。第二種方案其實是對第一種方案的改進,因為執行緒切換的開銷明顯要小於程序切換的開銷。第三種方案就是原來用程序去處理每個請求,現在換成用執行緒去處理,個人認為改進不是很大.
2. 高階併發伺服器演算法流程
(1)統一accept,多程序
socket(...);
bind(...);
listen(...);
while(1){
accept(...);
fork(...);//子程序
}
close(...);//關閉伺服器套接字
子程序:
recv(...);
process(...);
send(...);
close(...);//關閉客戶端
(2)統一accept,多執行緒
socket(...);
bind(...);
listen(...);
while(1){
accept(...);
pthread_create(....);
}
close(...);//關閉伺服器
執行緒1:
recv(....);
process(....);
send(...);
close(...);//關閉客戶端
(3)accept放入每個執行緒
socket(...);
bind(...);
listen(...);
pthread_create(...);
pthread_join(...);//等待執行緒結束
close(...);//關閉伺服器
執行緒1:
Mutex_lock//互斥鎖
accept(...);
Mutex_unlock(...);
recv(...);
process(...);
send(...);
close(...);//客戶端
3. 相關例子
TCP伺服器:
(1)統一accept多程序
伺服器;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <time.h>
/**
高階併發伺服器
TCP統一accept
當有客戶端到來時,為每個客戶端建立程序,然後每個程序處理客戶端的請求,動態的建立程序
**/
#define PORT 8888
#define BUFFERSIZE 1024
#define BACKLOG 2
static void handle(int sc){//處理客戶端的請求
char buffer[BUFFERSIZE];
time_t now;
int size;
memset(buffer,0,BUFFERSIZE);
size=recv(sc,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(sc,buffer,strlen(buffer),0);
}
close(sc);
}
int main(int argc,char*argv[]){
int ret;
int s;
int sc;//用於伺服器與客戶端進行資料傳輸的套接字
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int len;
len=sizeof(client_addr);
//建立流式套接字
s=socket(AF_INET,SOCK_STREAM,0);
if(s<0){
perror("socket error");
return -1;
}
//將地址結構繫結到套接字描述符上去
memset(&server_addr,0,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=bind(s,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(ret==-1){
perror("bind error");
return -1;
}
ret=listen(s,BACKLOG);
if(ret<0){
perror("listen error");
return -1;
}
while(1){
sc=accept(s,(struct sockaddr*)&client_addr,&len);
if(sc<0){
continue;
}
if(fork()==0){//子程序
handle(sc);
close(s);//子程序關閉用於監聽的套接字
}else{
close(sc);//父程序關閉客戶端套接字
}
}
}
客戶端:
#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;
}
(2)統一accept多執行緒
伺服器:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <time.h>
/**
TCP併發伺服器,採用多執行緒,每次客戶端傳送請求,主執行緒建立一個子執行緒,用於處理客戶端的請求
執行緒具有速度快,佔用資源少,資料可以共享等優點 統一accept
**/
#define PORT 8888
#define BUFFERSIZE 1024
#define BACKLOG 2
static void handle(void* sc1){
int sc;
time_t now;
char buffer[BUFFERSIZE];
int size;
sc=*((int*)sc1);//轉換成int指標,然後取值,sc1本身就是一個指標
memset(buffer,0,BUFFERSIZE);
size=recv(sc,buffer,BUFFERSIZE,0);
if(size>0&&!strncmp(buffer,"TIME",4)){//請求伺服器的時間
memset(buffer,0,BUFFERSIZE);//清0
now=time(NULL);
sprintf(buffer,"%24s\r\n",ctime(&now));
send(sc,buffer,strlen(buffer),0);//向客戶端傳送資料
}
close(sc);//關閉客戶端
}
int main(int argc,char*argv[]){
int ret;
int s;
int sc;
int len;
pthread_t thread1;//定義執行緒名
struct sockaddr_in server_addr,client_addr;
len=sizeof(client_addr);
//建立流式套接字
s=socket(AF_INET,SOCK_STREAM,0);
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;
}
//接收客戶端的請求
for(;;){
sc=accept(s,(struct sockaddr*)&client_addr,&len);
if(sc<0){
continue;
} else {
pthread_create(&thread1,NULL,handle,(void*)&sc);//建立執行緒,讓執行緒去處理,最後一個欄位是傳遞給執行緒處理函式handle的引數
}
}
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;
}
(3)單獨執行緒accept
伺服器:
#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>
/**
多執行緒TCP併發伺服器
主執行緒建立多個執行緒,然後每個執行緒獨立的accept和進行資料的傳送與接收
多執行緒,獨立accept
**/
#define PORT 8888
#define BUFFERSIZE 1024
#define BACKLOG 2
#define CLIENTNUM 3
static void *handle(void* s1){
int s;
int len;
int sc;
pthread_mutex_t alock=PTHREAD_MUTEX_INITIALIZER;
char buffer[BUFFERSIZE];
int size;
struct sockaddr_in client_addr;
s=*((int*)s1);//得到伺服器端的套接字描述符
//等待客戶端連線
len=sizeof(client_addr);
for(;;){//不停的迴圈等待客戶端的連線
time_t now;
//進入互斥區,每次一個執行緒處理客戶端
pthread_mutex_lock(&alock);
sc=accept(s,(struct sockaddr*)&client_addr,&len);
pthread_mutex_unlock(&alock);
memset(buffer,0,BUFFERSIZE);
size=recv(sc,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(sc,buffer,strlen(buffer),0);
}
close(sc);//關閉客戶端
}
}
int main(int argc,char*argv[]){
int ret;
int s;
int len;
int i;
pthread_t thread[CLIENTNUM];
struct sockaddr_in server_addr;
//建立流式套接字
s=socket(AF_INET,SOCK_STREAM,0);
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==-1){
perror("bind error");
return -1;
}
//監聽
ret=listen(s,BACKLOG);
if(ret==-1){
perror("listen error");
return -1;
}
//建立3個執行緒,每個執行緒獨立的accept
for(i=0;i<CLIENTNUM;i++){
pthread_create(&thread[i],NULL,handle,(void*)&s);//執行緒的處理函式為handle,傳遞的引數為套接字描述符s
}
//while(1);
//等待執行緒結束
for(i=0;i<CLIENTNUM;i++){
pthread_join(thread[i],NULL);
}
//關閉套接字
close(s);
return 0;
}
客戶端:
#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;
}
總結:
統一accept,多程序伺服器是對簡單併發伺服器的改進,而由於程序的切換開銷比較大,所以又有了統一accept,多執行緒的併發伺服器。而單獨執行緒的accept是完全用執行緒來處理請求。這些都是TCP伺服器,由於UDP是突發的資料流,沒有三次握手,所以伺服器不能檢測到客戶端什麼時候傳送資料。以上三種高階併發伺服器仍然存在著效能問題,下一節介紹的I/O複用的迴圈伺服器是對這三種高階併發伺服器的改進。