采用多
进程的方式实现
服务器的并发访问的经典范例。
程序实现功能:
1.
客户端从标准输入读入一行文字,发送到服务器.
2.服务器接收到客户端发来的文字后,原样返回给客户端.
3.客户端接收到服务器的发来的文字后,输出到标准输出,然后继续以上步骤。
服务器端过程:建立好监听套接字后,等待客户端的连接,接收到一个连接后,创建一个子进程来与客户端进行通信,主进程则继续等待其他客户端的连接。代码如下:
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<netdb.h>
#defineSERV_PORT1113
#defineLISTENQ32
#defineMAXLINE1024
/***连接处理函数***/
voidstr_echo(intfd);
int
main(intargc,char*argv[]){
intlistenfd,connfd;
pid_tchildpid;
socklen_tclilen;
structsockaddr_inservaddr;
structsockaddr_incliaddr;
if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1){
fprintf(stderr,"Socketerror:%s\n\a",strerror(errno));
exit(1);
}
/*服务器端填充sockaddr结构*/
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
/*捆绑listenfd描述符*/
if(bind(listenfd,(structsockaddr*)(&servaddr),sizeof(structsockaddr))==-1){
fprintf(stderr,"Binderror:%s\n\a",strerror(errno));
exit(1);
}
/*监听listenfd描述符*/
if(listen(listenfd,5)==-1){
fprintf(stderr,"Listenerror:%s\n\a",strerror(errno));
exit(1);
}
for(;;){
clilen=sizeof(cliaddr);
/*服务器阻塞,直到客户程序建立连接*/
if((connfd=accept(listenfd,(structsockaddr*)(&cliaddr),&clilen))==-1){
fprintf(stderr,"Accepterror:%s\n\a",strerror(errno));
exit(1);
}
//有客户端建立了连接后
if((childpid=fork())==0){/*子进程*/
close(listenfd);/*关闭监听套接字*/
str_echo(connfd);/*处理该客户端的请求*/
exit(0);
}
close(connfd);/*父进程关闭连接套接字,继续等待其他连接的到来*/
}
}
voidstr_echo(intsockfd){
ssize_tn;
charbuf[MAXLINE];
again:
while((n=read(sockfd,buf,MAXLINE))>0)
write(sockfd,buf,n);
if(n<0&&errno==EINTR)//被中断,重入
gotoagain;
elseif(n<0){//出错
fprintf(stderr,"readerror:%s\n\a",strerror(errno));
exit(1);
}
}
客户端过程:创建连接套接字,主动向服务器发起连接请求,建立连接后,等待标准输入,输入完成后,将输入的内容发送给服务器,接着接收服务器发送过来的内容,并将接收到的内容输出到标准输出。代码如下:
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<netdb.h>
#defineSERV_PORT1113
#defineMAXLINE1024
voidstr_cli(FILE*fp,intsockfd);
int
main(intargc,char**argv)
{
intsockfd;
structsockaddr_inservaddr;
if(argc!=2){
fprintf(stderr,"usage:tcpcli<IPaddress>\n\a");
exit(0);
}
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
fprintf(stderr,"Socketerror:%s\n\a",strerror(errno));
exit(1);
}
/*客户程序填充服务端的资料*/
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(SERV_PORT);
if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr)<=0){
fprintf(stderr,"inet_ptonError:%s\a\n",strerror(errno));
exit(1);
}
/*客户程序发起连接请求*/
if(connect(sockfd,(structsockaddr*)(&servaddr),sizeof(structsockaddr))==-1){
fprintf(stderr,"connectError:%s\a\n",strerror(errno));
exit(1);
}
str_cli(stdin,sockfd);/*doitall*/
exit(0);
}
void
str_cli(FILE*fp,intsockfd)
{
intnbytes=0;
charsendline[MAXLINE],recvline[MAXLINE];
while(fgets(sendline,MAXLINE,fp)!=NULL){//从标准输入中读取一行
write(sockfd,sendline,strlen(sendline));//将该行发送给服务器
if((nbytes=read(sockfd,recvline,MAXLINE))==0){//从sockfd读取从服务器发来的数据
fprintf(stderr,"str_cli:serverterminatedprematurely\n");
exit(1);
}
recvline[nbytes]='\0';
fputs(recvline,stdout);
}
}
运行结果:
1.首先启动服务器端程序。
viidiot@ubuntu$./dissrv&(在后台运行)
2.启动一个客户端
viidiot@ubuntu$./discli127.0.0.1
hello,world!(客户端输入的内容)
hello,world!(服务器端返回的内容)
3.输入命令netstat-at查看tcp的连接情况,可以发现服务器端与客户端已经建立起了一个连接,而服务器的主进程则仍然在1113端口监听,等待连接其他连接的到来。
viidiot@ubuntu$netstat–at
tcp00*:1113*:*LISTEN
tcp00localhost.localdom:1113localhost.localdo:57430ESTABLISHED
tcp00localhost.localdo:57430localhost.localdom:1113ESTABLISHED
客户端输入EOF结束通信。
但是当我们开启多个客户端连接到服务器进行通信,完了之后通过按EOF结束通信后,可以从后台发现一个有趣的事情,即出现了大量的
僵尸进程。如下所示:
viidiot@ubuntu$ps-a
PIDTTYTIMECMD
19403pts/100:00:00dissrv
19405pts/100:00:00dissrv<defunct>
19423pts/100:00:00dissrv<defunct>
19434pts/100:00:00dissrv<defunct>
19441pts/100:00:00dissrv<defunct>
造成大量僵尸进程的原因是,服务器子进程终止时,会发送一个SIGCHLD信号给父进程。但我们的代码中,并没有捕获该信号,而这个信号的默认动作是忽略,因此,为了避免僵尸进程的产生,我们需要捕获SIGCHLD来清楚僵尸进程。