如果你想了解TCP客户/服务器,以及多进程并发服务器具体过程,可以看:http://blog.youkuaiyun.com/songshimvp1/article/details/51819820 和 http://blog.youkuaiyun.com/songshimvp1/article/details/51819765。
TCP客户/服务器(回射服务器)完整简单示例:
需求:客户从标准输入读入数据,递给服务器;服务器从网络输入读入数据,并回射给客户;客户从网络输入读入数据,并显示在标准输出上。
(1)TCP回射服务器程序:
#include "unp.h"
/*
*执行对每个客户的服务:从客户读入数据,并把这些数据回射给客户
*/
void
str_echo(int sockfd)
{
ssize_t n;
char buf[MAXLINE];
again:
while ( (n = read(sockfd, buf, MAXLINE)) > 0) //从套接字读入数据
Writen(sockfd, buf, n); //把数据回射给客户
if (n < 0 && errno == EINTR)
goto again;
else if (n < 0)
err_sys("str_echo: read error");
}
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) { /* child process */ //多进程并发服务器
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */ //执行对客户的服务
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}
(2)TCP回射客户程序:
#include "unp.h"
//处理客户输入,从标注输入读入一行文本,写到服务器上,读回服务器对该行的回射,并把回射数据写到标准输出上
void
str_cli(FILE *fp, int sockfd)
{
char sendline[MAXLINE], recvline[MAXLINE];
while (Fgets(sendline, MAXLINE, fp) != NULL) { //读入
Writen(sockfd, sendline, strlen(sendline)); //发给服务器
if (Readline(sockfd, recvline, MAXLINE) == 0) //读回反射数据
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout); //写到标准输出
}
}
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
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); /* do it all */
exit(0);
}
(3)正常启动与终止:
(4)正常终止客户与服务器的步骤:
A. 当键入Ctrl+D(EOF)时,fgets返回空指针,然后str_cli函数返回到main函数;main函数通过exit终止。
B. 客户进程终止,关闭所有打开的描述符(客户打开的套接字由内核关闭)。导致客户TCP发送FIN给服务器,服务器响应一个ACK;至此,服务器套接字处于CLOSE_WAIT状态,客户套接字处于FIN_WAIT_2状态。
C. 服务器接受到FIN阻塞于read,read返回0,导致str_echo函数返回,服务器子进程通过exit终止。
D. 服务器子进程所有描述符关闭;由子进程来关闭已连接套接字会引发TCP连接终止序列的最后两个分节:从服务器到客户的FIN和客户的ACK;至此,连接完全终止,客户进入TIME_WAIT状态。
E. 进程终止的另一部分内容是:服务器子进程终止时,向父进程发送SIGCHLD信号。该程序没捕获该信号,该信号默认被忽略。既然父进程未处理,子进程僵死,如下图所示: