实际应用中,一个服务器需要能够同时处理多个客户端的连接请求,在上一篇的代码中没能实现,本篇对上一篇的代码做了一点改进。服务器端将监听时间与处理请求这两个功能分开,主进程负责监听,当接收到客户端连接请求后,创建一个子进程与客户端通信,而主进程则继续监听。注意的时,每当主进程执行accept()获得请求的描述符,并创建子进程之后,需要将这个描述符关闭,因为子进程会完整得复制这个描述符。子进程处理完事件后,关闭描述符并推出。
下面是代码:
服务器端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //gethostbyaddr
#define MAXLINE 1024
/*
可同事处理多个客户端连接请求的并发服务器
*/
void toggle(int conn_sock);
int startup(int *port);
void error_die(const char *sc);
/*
处理僵尸进程
*/
void sigchld_handler(int sig){
while(waitpid(-1,0,WNOHANG)>0);
return;
}
int main(int argc,char **argv){
int listen_sock,conn_sock,port,clientlen,cpid;
struct sockaddr_in clientaddr;
struct hostent *hp;
char *haddrp;
if(argc!=2){
fprintf(stderr,"input:%s \n" ,argv[0]);
exit(1);
}
port=atoi(argv[1]); //string to int
signal(SIGCHLD,sigchld_handler);
listen_sock = startup(&port);
while(1){
clientlen=sizeof(clientaddr);
conn_sock=accept(listen_sock,(struct sockaddr*)&clientaddr,&clientlen);
if(fork() == 0){
//子进程运行,父进程继续循环accept()
hp=gethostbyaddr((const char*)&clientaddr.sin_addr.s_addr,
sizeof(clientaddr.sin_addr.s_addr),AF_INET);
haddrp=inet_ntoa(clientaddr.sin_addr);
cpid = getpid();
printf("Server(chile process %d) connected to %s(%s)\n",cpid,hp->h_name,haddrp);
close(listen_sock);
toggle(conn_sock);
close(conn_sock);
printf("Server(chile process %d) connection closed\n",cpid);
exit(0); //子进程退出
}
close(conn_sock); //父进程关闭这个连接描述符,因为这个描述符对父进程是没有用的,子进程复制了一个
}
exit(0);
}
void toggle(int conn_sock){
int n;
int i;
char buf[MAXLINE];
while(n=recv(conn_sock,buf,MAXLINE,0)){
printf("toggle server received %d bytes\n",n);
for(i=0;i<n;i++){
if(isupper(buf[i]))
buf[i]=tolower(buf[i]);
else
buf[i]=toupper(buf[i]);
}
send(conn_sock,buf,n,0);
}
}
int startup(int *port)
{
int httpd = 0;
struct sockaddr_in name;
httpd = socket(PF_INET, SOCK_STREAM, 0);
if (httpd == -1)
error_die("socket");
memset(&name, 0, sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定socket
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
error_die("bind");
//如果端口没有设置,提供个随机端口
if (*port == 0)
{
socklen_t namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
error_die("getsockname");
*port = ntohs(name.sin_port);
}
//监听
if (listen(httpd, 5) < 0)
error_die("listen");
return(httpd);
}
void error_die(const char *sc)
{
perror(sc);
exit(1);
}
客户端:
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 1024
//一个客户端程序
int open_client_sock(char *host,int port);
void error_die(const char *sc);
int main(int argc,char **argv){
int client_sock,port,n;
char *host,buf[MAXLINE];
if(argc!=3){
fprintf(stderr,"input:%s \n" ,argv[0]);
exit(1);
}
host=argv[1]; //char * server's name
port=atoi(argv[2]); //int server's port
client_sock=open_client_sock(host,port);
while(fgets(buf,MAXLINE,stdin)!=NULL){
n = send(client_sock,buf,strlen(buf),0);
printf("send %d bytes\n",n);
recv(client_sock,buf,MAXLINE,0);
fputs(buf,stdout);
}
close(client_sock);
exit(0);
}
//传入一个服务器主机名,监听端口,返回已连接描述符
int open_client_sock(char *host,int port){
int http = 0;
struct hostent *hp;
struct sockaddr_in serveraddr;
http = socket(AF_INET, SOCK_STREAM, 0);
if(http == -1)
error_die("socket");
if((hp = gethostbyname(host)) == NULL)//由域名获取IP
error_die("no host");
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
bcopy((char *)hp->h_addr_list[0], (char *)&serveraddr.sin_addr.s_addr, hp->h_length);
if(connect(http, (struct sockaddr *)&serveraddr, sizeof(serveraddr))<0)
error_die("can't connect");
return http;
}
void error_die(const char *sc)
{
perror(sc);
exit(1);
}
执行结果: