码迷,mamicode.com
首页 > 其他好文 > 详细

TCP客户/服务器编程入门

时间:2015-06-18 23:59:16      阅读:435      评论:0      收藏:0      [点我收藏+]

标签:

  最近在学习UNP,特此记录。

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)
        {
            Close(listenfd);
            str_echo(connfd);
            exit(0);
        }
        Close(connfd);
    }
}
        

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: cli <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);
}

  此时,一个简单的客户/服务器程序就写好了。运行服务器程序和客户程序,回射功能正常。

技术分享

 

  但是,此时程序仍然存在问题。

  首先是服务器程序。

  服务器子进程终止时,会给父进程发送一个SIGCHLD信号,但是我们没有在代码中捕获该信号,而该信号的默认行为是被忽略。这样,子进程最终进入僵死状态。

  即使我们编写了SIGCHLD的信号处理函数,服务器程序仍然存在问题。

  因为当SIGCHLD信号递交给父进程时,父进程正阻塞于慢系统调用accept。于是内核就会使accept返回一个EINTR错误。而父进程不处理该错误,于是终止。(有些系统自动重启被中断的系统调用)

因此,我们必须对慢系统调用返回EINTR有所准备。

3. 修正的服务器程序

#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");
}

void sig_chld(int signo)
{
    pid_t pid;
    int stat;    
    
    while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
        printf("child %d terminated\n", pid);
    return;
}

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);
    
    Signal(SIGCHLD, sig_chld);    /* must call waitpid */

    for (;;)
    {
        clilen = sizeof(cliaddr);
        if ( (connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)
        {
            if(errno = EINTR)
                continue;        /* back to for */
            else
                err_sys("accept error");
        
        }
    
        if ( (childpid = Fork()) == 0)
        {
            Close(listenfd);
            str_echo(connfd);
            exit(0);
        }
        Close(connfd);
    }
}
        

  服务器程序已经正确,但是客户程序还有些问题。

  考虑这样一种情形:当客户与服务器建立TCP连接并成功执行一次回射操作后,使用kill命令杀死子进程。子进程的终止会导致向客户发送一个FIN,但是,客户TCP接收到FIN只是表示服务器进程已关闭了连接的服务器端,从而不再往其中发送任何数据而已。FIN的接收并没有告知客户TCP服务器进程已经终止。我们需要修改代码( 使用select或poll函数),使得服务器进程的终止一旦发生,客户就能检测到。

  另外值得一提的是,在本例中,客户实际上在应对两个描述符——套接字和用户输入。它不能单纯阻塞在这两个源中某个特定源的输入上,而是应该阻塞在其中任何一个源的输入上。

  下面的代码可以修正上述问题。

void str_cli(FILE *fp, int sockfd)
{
    int maxfdp1, stdineof;
    fd_set rset;
    char buf[MAXLINE];
    int n;
        
    stdineof = 0;
    FD_ZERO(&rset);
    for (;;)
    {
        if(stdineof == 0)
            FD_SET(fileno(fp), &rset);
        FD_SET(sockfd, &rset);
        maxfdp1 = max(fileno(fp), sockfd) + 1;
        Select(maxfdp1, &rset, NULL, NULL, NULL);

        if (FD_ISSET(sockfd, &rset))    /* socket is readable */
        {
            if( (n = Read(sockfd, buf, MAXLINE)) == 0)
            {
                if(stdineof == 1)
                    return;
                else
                    err_quit("str_cli: server terminated prematurely");
            }
            Write(fileno(stdout), buf, n);
        }
        
        if (FD_ISSET(fileno(fp), &rset))    /* input is readable */
        {
            if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0)
            {
                stdineof = 1;
                Shutdown(sockfd, SHUT_WR);
                FD_CLR(fileno(fp), &rset);
                continue;
            }
            Write(sockfd, buf, n);
        }
     }
}

 

TCP客户/服务器编程入门

标签:

原文地址:http://www.cnblogs.com/gattaca/p/4587175.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!