本章大意
讲解了一个回射C/S程序,并分析各种情况(正常或异常)下执行的情况。
细节摘录
1. 服务器程序代码初步分析:
4. netstat -a可以打印出当前的所有TCP连接
5. 子进程被捕获一定要及时作出处理,否则将导致僵死。
6. 信号处理函数由信号值这个单一的整数参数调用,没有返回值。
7. 信号注册函数解析:
10. 重点区分wait和waitpid两个函数。
11. 使用waitpid版本的信号处理函数:
14. 分析服务器进程异常终止的情况
15. 当进程向某个已经收到RST的套接字写操作时,默认的处理是终止写进程。当然也可以自行去定义。
16. 如果是服务器的住据崩溃,那么服务器对请求不做任何操作。
17. 服务器主机崩溃后重启了,仍然会导致连接异常。因为重启会丢失之前的连接信息。
18. 掌握CS通信时的格式控制。
讲解了一个回射C/S程序,并分析各种情况(正常或异常)下执行的情况。
细节摘录
1. 服务器程序代码初步分析:
#include "unp.h"
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); //关闭已连接套接字(但还在子进程中“生效”着)
}
}
2. 客户程序代码初步分析:
#include "unp.h"
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.si n_addr);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd); // 回射处理
exit(0);
}
3. 在客户程序给出标准输入以前,服务器进程,服务器子进程,以及客户程序都将阻塞。
4. netstat -a可以打印出当前的所有TCP连接
5. 子进程被捕获一定要及时作出处理,否则将导致僵死。
6. 信号处理函数由信号值这个单一的整数参数调用,没有返回值。
7. 信号注册函数解析:
#include "unp.h"
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; /* SVR4, 44BSD */
#endif
}
/*
* 正式注册
*/
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
8. 下面是一个处理僵死进程的示例:
#include "unp.h"
void
sig_chld(int signo) // 参数为信号,唯一整数。
{
pid_t pid; // 进程标识符类型
int stat; // 状态标号
pid = wait(&stat); // 撤销僵死进程,改变状态变量,赋值标识符变量。
printf("child %d terminated\n", pid); // 输出标识符
return;
}
9. 当心慢阻塞系统调用,需要自动手工实现重启。(注册时直接声明为自动重启可以吗?还没弄清)
10. 重点区分wait和waitpid两个函数。
11. 使用waitpid版本的信号处理函数:
#include "unp.h"
void
sig_chld(int signo)
{
pid_t pid;
int stat;
/*
* waitpid函数参数说明:
* -1表示等待第一个终止的子进程,stat保存结果状态,WNOHANG告知内核在有尚未终止的子进程时不要阻塞内核。
*/
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
return;
}
12. 下面是经过信号处理机制完善的服务器回射程序:
#include "unp.h"
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
err_sys("accept error");
}
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 */
}
}
13. 了解accept返回前终止的执行情况
14. 分析服务器进程异常终止的情况
15. 当进程向某个已经收到RST的套接字写操作时,默认的处理是终止写进程。当然也可以自行去定义。
16. 如果是服务器的住据崩溃,那么服务器对请求不做任何操作。
17. 服务器主机崩溃后重启了,仍然会导致连接异常。因为重启会丢失之前的连接信息。
18. 掌握CS通信时的格式控制。