UNIX网络编程卷1:套接字联网-第5章:TCP客户/服务器程序示例

1. 简单回射客户/服务器图解


2.代码实例

以下程序摘自UNIX网络编程,我会对其加上一些注解,方便阅读

TCP回射服务器程序:main函数

/*怎么编译运行可以查看我的另一篇博文:unpv13e编译和运行

/* tcpserv01.c */   
#include <sys/socket.h>
#include <strings.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int
main(int argc, char **argv)
{
    int                   listenfd, connfd;  //用来存储监听套接字fd和连接套接字fd
    pid_t                 childpid;          // 用来存储子进程fd
    socklen_t             clilen;            //用来存储套接字地址大小
    struct sockaddr_in    cliaddr, servaddr;  //用来存储服务端和客户端套接字地址
    
    if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)     //创建使用ipv4,tcp的流套接字
    {
        perror("socket");
        exit(1);
    }
    
    bzero(&servaddr, sizeof(servaddr));     //初始化服务端套接字地址为0,bzero已经过时,此处最好使用memset
    servaddr.sin_family = AF_INET;          //使用ipv4协议族
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  //将主机字节序转换为网络字节序(默认大端字节序)并赋予相应值,INADDR_ANY表示接受任意地址的请求
    servaddr.sin_port = htons(9877);   //赋予服务端套接字地址结构中的端口值

    if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) //将创建的套接字listenfd和服务端套接字地址结构绑定在一起
    {
        perror("bind");
        exit(1);
    }
    if(listen(listenfd, 5) < 0)   //监听端口收到的请求,最大连接数设置为5,调用listen后内核会维护两个队列,具体参见tcp套接字编程
    {
        perror("listen");
        exit(1);
    }

    for(;;)    //最常见的服务进程是被动的等待请求,除非手动关闭或者遇到异常,否则不会主动关闭
    {
        clilen = sizeof(cliaddr);
        if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) //若已连接队列为空,则服务进程阻塞于此,否则取出队列头,内核生成一个全新的描述符,cliaddr是值-结果参数
        {
            perror("accept");        
            exit(1);
        }
        
        if((childpid = fork()) < 0)   //毫无悬念,创建子进程。注意,创建子进程后,对父进程现有套接字的引用都增1,表示子进程也可使用父进程拥有的套接字,此时注意close一些不是用的 
        {
            perror("fork");
            exit(1);
        }
        else if(childpid == 0)    /* child process */ fork返回两次,返回0的表示处于子进程
        {
            if(close(listenfd) < 0) /* close listening socket */  /*关掉子进程对监听套接字的可使用权,监听套接字引用计数减1,直至计数为0,才回真正关闭套接字*/
            {
                perror("child close");
                exit(1);
            }
            str_echo(connfd);    /* process the request */  //传递连接套接字给服务器程序的业务处理函数,从而进行通信操作
            exit(0); /*此处别忘了哟,子进程用完了要退出,要不然站着进程资源不释放,又没用,你猜会怎么样
            
        }
        if(close(connfd) < 0) /* parent close connected socket */ //父进程也要关闭连接套接字,(以为accept成功返回的时候,父进程也拥有了对连接套接字的使用权)
        {
            perror("parent close");
            exit(1);
        }
    }
}

TCP回射服务器程序:str_echo函数

/* str_echo.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

void
str_echo(int sockfd)
{
    ssize_t        n;
    char           buf[4096]; //创建一个应用层缓冲区,用于存放从套接字中读到的数据
again:
    while((n = read(sockfd, buf, 4096)) > 0)//如果套接字队列没有数据可读,则read处于阻塞状态
        writen(sockfd, buf, n);

    if(n < 0 && errno == EINTR)  //read被信号中断,此时重新读
        goto again;
    else if(n < 0)       //read发生错误,打印错误信息,并终止进程
    {
        perror("read");
        exit(1);
    }
        
}



TCP回射客户程序:main函数

/* tcpcli01.c */
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

int
main(int argc, char **argv)
{
    int                   sockfd;
    struct sockaddr_in    servaddr;
    
    if(argc != 2)
    {
        printf("usage: tcpcli <IPaddress> ");
        exit(0);
    }
    
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)  //不管是服务程序还是客户程序,想要网络通信,都得先创建通信用的网络套接字
    {
        perror("socket");  //创建套接字失败的下场
        exit(1);
    }
    
    bzero(&servaddr, sizeof(servaddr));       //同样的,初始化套接字地址结构为0
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(9877);
    if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)  
    {
        perror("inet_pton");    
        exit(1);
    }
    
    if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) //调用connect会发起三路握手的连接过程
    {
        perror("connect");
        exit(1);
    }
    
    str_cli(stdin, sockfd);    /* do it all */  //调用客户端的业务处理程序,传入标准输入流(用于得到客户端的输入)和用于网络通信的套接字描述符

    exit(0);
}


TCP回射客户程序:str_cli函数

/* str_cli.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void
str_cli(FILE *fp, int sockfd)
{
    char    sendline[4096], recvline[4096];    //应用程序创建一个发送缓冲区和接收缓冲区

    while(fgets(sendline, 4096, fp) != NULL)   //
    {
        writen(sockfd, sendline, strlen(sendline));
        
        if(readline(sockfd, recvline, 4096) == 0)
        {
            printf("str_cli: server terminated prematurely");
            exit(0);
        }
        fputs(recvline, stdout);
    }
}

当然,上述程序还有诸多问题,我们只是以一个简单示例程序来展示tcp客户端和服务通信


改进的服务器程序:(增加了清除僵尸进程的机制和accept被中断重新调用的机制)

    #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);  /* 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) {    /* child process */  
                Close(listenfd);    /* close listening socket */  
                str_echo(connfd);   /* process the request */  
                exit(0);  
            }  
            Close(connfd);          /* parent closes connected socket */  
        }  
    }  

void sig_chld(int signo)  
{  
    pid_t pid;  
    int stat;  
  
    pid = wait(&stat);    //  
    printf("child %d terminated\n",pid);     
    return;  
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五癫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值