5.1简单的回射客户/服务器
5.2 TCP回射服务器程序
#include "unp.h"
#include <time.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 const *argv[])
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in servaddr, cliaddr;
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);
while {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
if ( (childpid = Fork()) ==0)
{
Close(listenfd);
str_echo(connfd);
exit(0);
}
Close(connfd);
}
return 0;
}
5.3 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 const *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);
exit(0);
return 0;
}
客户调用socket和connect,后者引起TCP的三路握手过程。当三路握手完成后,客户的connect和服务器中的accept均返回,连接于是建立。接着发生的步骤如下:
- 1.客户调用str_len函数,该函数阻塞于fgets调用,等待键盘输入一行文本;
- 2.当服务器在的accept返回时,服务器调用fork,再由子进程调用str_echo。该函数调用readline,readline调用read,而read在等待客户送入一行文本期间阻塞。
- 3.另一方面,服务器父进程再次调用accept并阻塞,等待下一个客户连接。
总结下正常终止客户和服务器的步骤:
- 输入EOF字符时,fgets返回一个空指针,str_len函数返回;
- 当str_len返回到客户的main函数时,main通过调用exit终止;
- 进程终止处理的一部分是关闭所有打开的描述字,因此客户打开的套接口由内核关闭。这导致客户TCP发送一个FIN给服务器,服务器TCP则以ACK响应,这就是TCP连接终止序列的前半部分。至此,服务器套接口处于CLOSE_WAIT状态,客户套接口则处于FIN_WAIT_2状态;
- 当服务器TCP接收FIN时,服务器进程阻塞于readline调用,于是readline返回0。这导致str_echo函数返回服务器子进程的main函数;
- 服务器子进程通过调用exit来终止;
- 服务器子进程中打开的所有描述字随之关闭。由子进程关闭已连接套接口引发TCP连接终止序列最后两个分节:一个从服务器到客户的FIN和一个从客户到服务器的ACK。至此,连接完全终止,客户套接口进入TIME_WAIT状态;
- 进程终止处理的另一部分内容是:在服务器子进程终止时,给父进程发送一个SIGCHILD信号。此处父进程未对该信号进行处理,因此子进程进入僵死状态(状态Z)。
5.8 POSIX信号处理
Sigfunc *signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM)
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART;
#endif
}
if (sigaction(signo, &act, &oact) < 0)
{
return(SIG_ERR);
}
return(oact.sa_handler);
}
5.9 处理SIGCHILD信号
- 设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息包括子进程的进程ID、终止状态以及资源利用信息(CPU时间、内存使用量等等)。
- 如果一个进程终止,而该进程有子进程处于僵死状态,那么它的所有僵死子进程的父进程ID将被重置为1(init进程)。
- 继承这些子进程的init进程将清理它们(即init进程将wait它们,从而去除它们的僵死状态)。
|