sin的博客

时间悄悄地流过,今天你做了什么
posts - 17, comments - 3, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

UNP读书笔记三 TCP客户/服务器程序分析

Posted on 2010-02-21 19:49 sin 阅读(572) 评论(0)  编辑 收藏 引用 所属分类: 读书笔记
首先是一个TCP客户/服务器编程的例子

客户端
#include "unp.h"

void cli_echo(FILE* fp, int fd);

int main(int argc, char **argv)
{
    
int sockfd;
    
struct sockaddr_in servaddr;

    
if (argc != 2)
    {
        err_quit(
"usage:echotcp <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));

    cli_echo(stdin, sockfd);

    exit(
0);
}

void cli_echo(FILE* fp, int fd)
{
    
int nread;
    
char sendline[MAXLINE],recvline[MAXLINE];

    
while ( Fgets(sendline,MAXLINE,fp) != NULL )
    {
        Writen(fd,sendline,strlen(sendline));
        
if ( Readline(fd,recvline,MAXLINE)==0 )
            err_quit(
"error:server terminated!");
        Fputs(recvline,stdout);
    }
}

服务器端
#include "unp.h"

void serv_echo(int clifd);
void sig_child(int signo);

int main(int argc, char **argv)
{
    
int listenfd,clifd;
    
struct sockaddr_in cliaddr,servaddr;
    socklen_t clilen;
    pid_t childpid;

    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_child);

    
for(;;)
    {
        clilen 
= sizeof(cliaddr);
        
if ((clifd = Accept(listenfd,(SA*)&cliaddr,&clilen)) < 0 )
        {
            
if (errno == EINTR)
                
continue;
            
else
                err_sys(
"accept error");
        }
        
if ( (childpid=fork()) == 0 )
        {
            Close(listenfd);
            printf(
"child process %d\n", getpid());
            serv_echo(clifd);
            exit(
0);
        }
        Close(clifd);
    }
    
    exit(
0);
}

void serv_echo(int clifd)
{
    
int nread;
    
char recvline[MAXLINE];
    
while ( (nread=Readline(clifd,recvline,MAXLINE)) > 0 )
    {
        Writen(clifd,recvline,nread);
    }
}

void sig_child(int signo)
{
    pid_t pid;
    
int  stat;

    pid 
= wait(&stat);
    printf(
"child %d ternminated\n", pid);
    
return;
}


正常启动
首先服务器程序启动后,调用socket bind listen 和accept,并阻塞在accept。可以用netstat来检测到服务器进程有一个处于LISTEN状态的套接口。然后启动客户程序,客户调用socket connect后,引起TCP的三路握手过程。三路握手完成后,客户程序从connect返回,服务器程序从accept返回,到此TCP连接建立。服务器进程产生一个子进程用来处理于客户的连接,服务器主进程则继续阻塞于accept调用,等待下一个客户连接。
连接建立后,客户程序阻塞于fgets调用,等待用户输到此TCP连接建立。入一行。服务器程序则阻塞于readline调用,等待客户程序送出一行。此时可以通过netstat来检测到:服务器主进程处于LISTEN状态,服务器子进程和客户进程则处于ESTABLISHED状态。

正常终止
当对客户程序键入EOL字符(Ctrl+C键)以终止客户,立即运行netstat。可以检测到客户程序的套接口处于TIME_WAIT状态,服务器程序一个套接口则处于LISTEN状态。
正常终止过程如下:客户程序接受到终止字符后,从fgets返回一个空指针,客户进程调用exit后结束。于是,客户套接口被内核关闭,并向服务器发送一个FIN,服务器则以ACK相应,此时客户套接口处于FIN_WAIT_2状态,服务器套接口处于CLOSE_WAIT状态。
服务器收到FIN时,从readline调用返回并返回0,这导致服务器子进程结束。服务器的连接套接口随之被关闭,服务器子进程向客户发送一个FIN,客户则以ACK回应之,连接终止。客户进程虽然结束,但是客户进程套接口仍然处于TIME_WAIT状态。

Posix信号
信号是发生某事件时对进程的通知,也称为为软中断。可以由一个进程发往另一个进程,也可以由内核发往进程。SIGCHLD信号就是进程终止时,内核发送给终止进程父进程的信号。每个信号都有一个处理方法,我们可以通过调用sigaction来设置我们自己的信号处理程序,也可以通过设置SIG_IGN来忽略信号,设置SIG_DFL来设置默认的处理方法。

僵尸进程
设置进程为僵尸状态的目的是为了维护子进程的信息(子进程ID、终止状态和子进程的资源利用信息)。如果一个进程终止,且该进程仍有子进程处于僵尸状态,则所有僵尸进程的父进程设为init进程。僵尸进程占用内核空间,所以我们应该避免进程变为僵尸进程,方法是wait waitpid系统调用。可以设置信号SIGCHLD的信号处理程序,在此信号处理程序里调用wait或waitpid。这样可以避免僵尸进程。
在某些系统上这样作可能会导致服务器进程错误。原因是服务器子进程终止后,向父进程发送一个SIGCHLD信号,导致accept调用被中断,某些系统在信号处理程序执行完后不会重启accept调用,而是返回EINTR错误。由于我们的服务程序没有处理这种错误,所以会导致错误。为了移植性,当我们编写信号处理程序时,必须对慢系统调用(可能常时间阻塞的系统调用)返回EINTR有所装备。收到EINTR错误时,自己来重启被中断的系统调用。

用waitpid来防止产生僵尸进程
waitpid的WNOHANG选项,通知内核在没有子进程终止时不要阻塞。
假如服务器程序有5个子进程,每个进程负责处理一个连接。则可能5个子进程同时终止,同时向父进程发送SIGCHLD信号。而信号处理程序只能对一个信号进行处理,只有一个子进程正常终止,其余4个子进程则成为僵尸进程。可以通过ps -s命令来进行验证的确有4个子进程成为了僵尸进程。解决方法就是,循环调用witpid(加上WNOHANG选项)。
网络编程时注意的三种情况:
1 当派生子进程时,必须捕捉SIGCHLD信号,防止子进程结束时成为僵尸进程。
2 当捕捉信号时,必须处理被中断的系统断用,因为有些系统不能重启被中断的系统调用,需要我们自己来重启。
3 SIGCHLD信号的处理程序必须正确便学,应使用waitpid来避免多个子进程同时终止时,留下僵尸进程。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理