相关网络编程函数:http://blog.csdn.net/somehow1002/article/details/72648743
服务端流程:
1.初始化套结字2.bind
3.listen
4.阻塞于accept,等待客户端连接
5.有客户端连接到达,父进程通过fork创建子进程对其处理,父进程关闭连接,继续监听
程序:
int main(int argc,char **argv)
{
int listenfd,connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
listenfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
bind(listenfd,(SA *)&servaddr,sizeof(servaddr));
listen(listenfd,LISTENQ);
for(;;)
{
clilen=sizeof(cliaddr);
connfd=accept(listenfd,(SA *)&cliaddr,&clilen);
if((childpid=fork())==0)
{
close(listenfd);
str_echo(connfd);
exit(0);
}
close(connfd);
}
}
void str_echo(int sockfd)
{
ssize_t n;
char buf[MAXLINE];
for(;;)
{
bzero(buf,MAXLINE);
if((n=read(sockfd,buf,MAXLINE))==0)
break;
printf("got message from client!\nlength:%d content:%s\n",n-1,buf);
writen(sockfd,buf,n);
}
printf("No client to serve! I am exiting!");
}
如果客户端关闭连接,那么接受到客户的FIN将导致服务器子进程的read返回0,这又导致str_echo函数返回,从而中止子进程。
客户端流程:
1.初始化套结字
2.通过connect建立与服务器的连接
3.str_cli完成处理工作
程序:
int main(int argc,char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if(argc!=2)
{
printf("useage:tcpcli <IPaddress>");
exit(1);
}
sockfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(SERV_PORT);
inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
connect(sockfd,(SA *)&servaddr,sizeof(servaddr));
str_cli(stdin,sockfd);
exit(0);
}
void str_cli(FILE *fp,int sockfd)
{
char sendline[MAXLINE],recvline[MAXLINE];
while(fgets(sendline,MAXLINE,fp)!=NULL)
{
Writen(sockfd,sendline,strlen(sendline));
bzero(recvline,MAXLINE);
if(read(sockfd,recvline,MAXLINE)==0)
{
printf("str_cli: server terminated prematurely");
close(sockfd);
exit(-1);
}
printf("got message from server:%s\n\n",recvline);
}
close(sockfd);
}
当遇到文件结束符或错误时,fgets将返回一个空指针,于是客户处理循环中止。
问题:
当服务器子进程终止时,给父进程发送了一个SIGCHLD信号,但是我们并没有在代码中捕获该信号,该信号的默认处理是被忽略。因为父进程未加处理,子进程于是进入僵死状态。
于是,我们在服务端程序中加入信号处理函数
void sig_cld(int signo)
{
pid_t pid;
int stat;
while((pid = waitpid(-1, &stat, 0)) > 0)
printf("Child %d terminated\n",pid);
return;
}
有了信号处理函数,我们在服务端的main函数中再添加上相应的调用即可。
问题:
但是当父进程阻塞于accpet时,子进程可能会终止,sig_chld函数执行。因为该信号是父进程阻塞于慢系统调用时由父进程捕获的,内核可能会使accept返回一个EINTR错误(被中断的系统调用),而父进程不处理该错误,于是父进程终止,服务端程序结束。
为了程序的稳定性和可移植性,对accept函数进行失败情况的处理。
最终的TCP服务器程序main函数如下:
int main(int argc,char **argv)
{
int listenfd,connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
void sig_chld(int);//声明信号处理函数
listenfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
bind(listenfd,(SA *)&servaddr,sizeof(servaddr));
listen(listenfd,LISTENQ);
signal(SIGCHLD,sig_chld);//绑定信号信号名与信号处理函数
for(;;)
{
clilen=sizeof(cliaddr);
//accept失败情况处理
if((connfd=accept(listenfd,(SA *)&cliaddr,&clilen))<0)
{
if(errno==EINTR)
continue;//back to for()
else
{
printf("accept error");
exit(0);
}
}
if((childpid=fork())==0)
{
close(listenfd);
str_echo(connfd);
exit(0);
}
close(connfd);
}
}