1.概念
與前面介紹的循環服務器不同,併發服務器對服務請求併發處理。而循環服務器只能夠一個一個的處理客戶端的請求,顯然效率很低. 併發服務器通過建立多個子進程來實現對請求的併發處理,但是由於不清楚請求客戶端的數目,因此很難確定子進程的數目。因此可以動態增加子進程與事先分配的子進程相結合的方法來實現併發服務器。
2. 算法流程
(1)TCP簡單併發服務器:
服務器子進程1:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
recv(....);
process(...);
send(.....);
}
close(....);//關閉客戶端
服務器子進程2:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
recv(....);
process(...);
send(.....);
}
close(....);//關閉客戶端
...............
...............
...............
(2)UDP簡單併發服務器
服務器子進程1:
socket(...);
bind(...);
while(1)
{
recvfrom(....);
process(...);
sendto(.....);
}
服務器子進程2,3....與1相同.
這樣,同時到達多個請求,就分別由多個進程併發的處理,此算法的的性能取決於請求的數目,如果請求數目越多,這樣的子進程就越多,那麼進程的切換開銷就會很大.而且還不容易確定子進程的數目,所以這是一種簡單的併發服務器模型.
3.相關實例
(1)簡單併發服務器TCP
服務器:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
/**
TCP併發服務器,預先建立進程,同時到來的客戶端分別由不同的進程併發處理
**/
#define PORT 8888
#define BUFFERSIZE 1024
#define PIDNUM 2
static void handle(int s){
int sc;
struct sockaddr_in client_addr;//客戶端地址
char buffer[BUFFERSIZE];
int len;
int ret;
int size;
len=sizeof(client_addr);
time_t now;
//接收客戶端的連接
while(1){
memset(buffer,0,BUFFERSIZE);
sc=accept(s,(struct sockaddr*)&client_addr,&len);
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 s;
int ret;
struct sockaddr_in server_addr;
pid_t pid[PIDNUM];
int i;
s=socket(AF_INET,SOCK_STREAM,0);
if(s<0){
perror("socket error");
return -1;
}
//綁定地址到套接字
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<0){
perror("bind error");
return -1;
}
ret=listen(s,10);//監聽
//建立子進程處理同時到來的客戶端請求
for(i=0;i<PIDNUM;i++){
pid[i]=fork();
if(pid[i]==0){
handle(s);
}
}
while(1);
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;
}
運行結果:
[root@localhost 14章服務器模式]# ./circle-tcpc14
Sat Feb 18 10:28:59 2012
(2)UDP簡單併發服務器
服務器:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
/**
UDP循環服務器處理完一個請求之後才能處理下一個請求,而不能同時處理請求
UDP併發服務器通過事先建立進程,併發的處理來自客戶端的請求
缺點:事先建立好子進程,但不知道客戶端的數目,難以適應動態變化
**/
//建立2個進程,併發的處理客戶端的請求
#define PORT 8888
#define PIDNUM 2
#define BUFFERSIZE 1024
//兩個子進程,分別併發的處理客戶端的請求
static void handle(int s){
char buffer[BUFFERSIZE];
struct sockaddr_in client_addr;
int len;
int size;
len=sizeof(client_addr);
time_t now;
while(1){
memset(buffer,0,BUFFERSIZE);
size=recvfrom(s,buffer,BUFFERSIZE,0,(struct sockaddr*)&client_addr,&len);
if(size>0&&!strncmp(buffer,"TIME",4)){
memset(buffer,0,BUFFERSIZE);
now=time(NULL);
sprintf(buffer,"%24s\r\n",ctime(&now));
size=sendto(s,buffer,strlen(buffer),0,(struct sockaddr*)&client_addr,sizeof(client_addr));
if(size<=0){
perror("sendto error");
}
}
}
}
int main(int argc,char*argv[]){
int s;//套接字描述符
int ret;//返回值
int size;
int i;
pid_t pid[PIDNUM];
struct sockaddr_in server_addr;
//建立套接字
s=socket(AF_INET,SOCK_DGRAM,0);
if(s<0){
perror("socket error");
return -1;
}
bzero(&server_addr,sizeof(server_addr));
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(server_addr));
if(ret==-1){
perror("bind error");
return -1;
}
//接收數據並處理,主函數建立兩個進程,讓進程併發的處理請求
for(i=0;i<PIDNUM;i++){
pid[i]=fork();
if(pid[i]==0){
handle(s);//讓子進程去處理
}
}
while(1);//父進程等待
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>
/**
UDP端,客戶端向服務器發送時間請求,服務器返回相應的時間
**/
#define PORT 8888
#define BUFFERSIZE 1024
int main(int argc,char*argv[]){
int s;//套接字描述符
int ret;//建立套接字的返回值
int size;
struct sockaddr_in server_addr;//地址結構
int len;
char buffer[BUFFERSIZE];
s=socket(AF_INET,SOCK_DGRAM,0);//建立流式套接字
if(s<0){
perror("socket error");
return -1;
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(PORT);
memset(buffer,0,BUFFERSIZE);
strcpy(buffer,"TIME");
//向服務器發送數據
size=sendto(s,buffer,strlen(buffer),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(size<0){
perror("sendto error");
return -1;
}
//從服務器接收數據
len=sizeof(server_addr);
size=recvfrom(s,buffer,BUFFERSIZE,0,(struct sockaddr*)&server_addr,&len);
if(size<0){
perror("recvfrom error");
return -1;
}
//write(1,buffer,size);
printf("%s\n",buffer);
close(s);
return 0;
}
總結:本文主要介紹了簡單的TCP與UDP併發服務器,並給出了實例.這種簡單的併發服務器很難適應動態變化的客戶端請求,所以,接下來將介紹高級併發服務器.