zoukankan      html  css  js  c++  java
  • linux网络编程之socket编程(六)

    经过一个国庆长假,又有一段时间没有写博文了,今天继续对linux网络编程进行学习,如今的北京又全面进入雾霾天气了,让我突然想到了一句名句:“真爱生活,珍惜生命”,好了,言归正传。

    回顾一下我们之间实现在TCP回射客户/服务器程序,首先回顾一下第一个版本:

    TCP客户端从stdin获取(fgets)一行数据,然后将这行数据发送(write)到TCP服务器端,这时TCP服务器调用read方法来接收然后再将数据回射(write)回来,客户端收到(read)这一行,然后再将其输出fputs标准输出stdout,但是这个程序并没有处理粘包问题,因为TCP是流协议,消息与消息之间是没有边界的,关于粘包问题,可以参考博文:http://www.cnblogs.com/webor2006/p/3946541.html,为了解决这个问题,于是第二个改进版程序诞生了:

    一行一行的发送数据,每一行都有一个 字符,所以我们在服务器端实现了按行读取的readline方法,另外发送也并不能保证一次调用write方法就将tcp应用层的所有缓冲区拷贝到了套接口缓冲区,所以我们封装了一个writen方法进行一个更可靠消息的发送,所以就很好的解决了粘包问题。

    回顾一下程序:

    echosrv.c:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    ssize_t readn(int fd, void *buf, size_t count)
    {
        size_t nleft = count;
        ssize_t nread;
        char *bufp = (char*)buf;
    
        while (nleft > 0)
        {
            if ((nread = read(fd, bufp, nleft)) < 0)
            {
                if (errno == EINTR)
                    continue;
                return -1;
            }
            else if (nread == 0)
                return count - nleft;
    
            bufp += nread;
            nleft -= nread;
        }
    
        return count;
    }
    
    ssize_t writen(int fd, const void *buf, size_t count)
    {
        size_t nleft = count;
        ssize_t nwritten;
        char *bufp = (char*)buf;
    
        while (nleft > 0)
        {
            if ((nwritten = write(fd, bufp, nleft)) < 0)
            {
                if (errno == EINTR)
                    continue;
                return -1;
            }
            else if (nwritten == 0)
                continue;
    
            bufp += nwritten;
            nleft -= nwritten;
        }
    
        return count;
    }
    
    ssize_t recv_peek(int sockfd, void *buf, size_t len)
    {
        while (1)
        {
            int ret = recv(sockfd, buf, len, MSG_PEEK);
            if (ret == -1 && errno == EINTR)
                continue;
            return ret;
        }
    }
    
    ssize_t readline(int sockfd, void *buf, size_t maxline)
    {
        int ret;
        int nread;
        char *bufp = buf;
        int nleft = maxline;
        while (1)
        {
            ret = recv_peek(sockfd, bufp, nleft);
            if (ret < 0)
                return ret;
            else if (ret == 0)
                return ret;
    
            nread = ret;
            int i;
            for (i=0; i<nread; i++)
            {
                if (bufp[i] == '
    ')
                {
                    ret = readn(sockfd, bufp, i+1);
                    if (ret != i+1)
                        exit(EXIT_FAILURE);
    
                    return ret;
                }
            }
    
            if (nread > nleft)
                exit(EXIT_FAILURE);
    
            nleft -= nread;
            ret = readn(sockfd, bufp, nread);
            if (ret != nread)
                exit(EXIT_FAILURE);
    
            bufp += nread;
        }
    
        return -1;
    }
    
    void echo_srv(int conn)//由于这个函数的意义就是回显消息给客户端,所以改一个函数名
    {
        char recvbuf[1024];
            while (1)
            {
                    memset(recvbuf, 0, sizeof(recvbuf));
                    int ret = readline(conn, recvbuf, 1024);
            if (ret == -1)
                ERR_EXIT("readline");
            if (ret == 0)
            {
                printf("client close
    ");
                break;
            }
            
                    fputs(recvbuf, stdout);
                    writen(conn, recvbuf, strlen(recvbuf));
            }
    }
    
    int main(void)
    {
        int listenfd;
        if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
    /*    if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
            ERR_EXIT("socket");
    
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        /*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
        /*inet_aton("127.0.0.1", &servaddr.sin_addr);*/
    
        int on = 1;
        if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
            ERR_EXIT("setsockopt");
    
        if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
            ERR_EXIT("bind");
        if (listen(listenfd, SOMAXCONN) < 0)
            ERR_EXIT("listen");
    
        struct sockaddr_in peeraddr;
        socklen_t peerlen = sizeof(peeraddr);
        int conn;
    
        pid_t pid;
        while (1)
        {
            if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
                ERR_EXIT("accept");
    
            printf("ip=%s port=%d
    ", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
    
            pid = fork();
            if (pid == -1)
                ERR_EXIT("fork");
            if (pid == 0)
            {
                close(listenfd);
                echo_srv(conn);
                exit(EXIT_SUCCESS);
            }
            else
                close(conn);
        }
        
        return 0;
    }

    echocli.c:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    ssize_t readn(int fd, void *buf, size_t count)
    {
            size_t nleft = count;
            ssize_t nread;
            char *bufp = (char*)buf;
    
            while (nleft > 0)
            {
                    if ((nread = read(fd, bufp, nleft)) < 0)
                    {
                            if (errno == EINTR)
                                    continue;
                            return -1;
                    }
                    else if (nread == 0)
                            return count - nleft;
    
                    bufp += nread;
                    nleft -= nread;
            }
    
            return count;
    }
    
    ssize_t writen(int fd, const void *buf, size_t count)
    {
            size_t nleft = count;
            ssize_t nwritten;
            char *bufp = (char*)buf;
    
            while (nleft > 0)
            {
                    if ((nwritten = write(fd, bufp, nleft)) < 0)
                    {
                            if (errno == EINTR)
                                    continue;
                            return -1;
                    }
                    else if (nwritten == 0)
                            continue;
    
                    bufp += nwritten;
                    nleft -= nwritten;
            }
    
            return count;
    }
    
    ssize_t recv_peek(int sockfd, void *buf, size_t len)
    {
            while (1)
            {
                    int ret = recv(sockfd, buf, len, MSG_PEEK);
                    if (ret == -1 && errno == EINTR)
                            continue;
                    return ret;
            }
    }
    
    
    ssize_t readline(int sockfd, void *buf, size_t maxline)
    {
            int ret;
            int nread;
            char *bufp = buf;
            int nleft = maxline;
            while (1)
            {
                    ret = recv_peek(sockfd, bufp, nleft);
                    if (ret < 0)
                            return ret;
                    else if (ret == 0)
                            return ret;
    
                    nread = ret;
                    int i;
                    for (i=0; i<nread; i++)
                    {
                            if (bufp[i] == '
    ')
                            {
                                    ret = readn(sockfd, bufp, i+1);
                                    if (ret != i+1)
                                            exit(EXIT_FAILURE);
    
                                    return ret;
                            }
                    }
    
                    if (nread > nleft)
                            exit(EXIT_FAILURE);
    
                    nleft -= nread;
                    ret = readn(sockfd, bufp, nread);
                    if (ret != nread)
                            exit(EXIT_FAILURE);
    
                    bufp += nread;
            }
    
            return -1;
    }
    
    void echo_cli(int sock)//将之前这一段代码封装成一个函数,回显客户端,让其代码更加整洁。
    {
        char sendbuf[1024] = {0};
            char recvbuf[1024] = {0};
            while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
            {
                    writen(sock, sendbuf, strlen(sendbuf));
    
                    int ret = readline(sock, recvbuf, sizeof(recvbuf));
                    if (ret == -1)
                            ERR_EXIT("readline");
                    else if (ret == 0)
                    {
                            printf("client close
    ");
                            break;
                    }
    
                    fputs(recvbuf, stdout);
                    memset(sendbuf, 0, sizeof(sendbuf));
                    memset(recvbuf, 0, sizeof(recvbuf));
            }
    
            close(sock);
    }
    
    int main(void)
    {
        int sock;
        if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
            ERR_EXIT("socket");
    
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
            ERR_EXIT("connect");
    
        struct sockaddr_in localaddr;
        socklen_t addrlen = sizeof(localaddr);
        if (getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0)
            ERR_EXIT("getsockname");
    
        printf("ip=%s port=%d
    ", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));
    
    
        echo_cli(sock);
    
        return 0;
    }

    运行效果如下:

     

    对于上面运行程序,当客户端退出之后,服务端会产生僵进程:

    【说明】:关于什么是僵尸进程,可以参考博文:http://www.cnblogs.com/webor2006/p/3512781.html

    对于僵进程的避勉,有如下方法,之前都有介绍过:

    忽略SIGCHLD信号,所以,可以在服务端加入:

    编译运行,看是否解决了僵进程的问题:

    第二种方式,则可以捕捉SIGCHLD信号来进行忽略,具体代码如下:

    此时再编译运行:

    但是,对于上面两种解决方式还是会存在一些问题,如果有很多个子进程同时退出,wait函数并不能等待所有子进程的退出,因为wait仅仅只等待第一个子进程退出就返回了,这时就需要用到waitpid了,在这之前,需要模拟一下由五个客户端并发连接至服务器,并同时退出的情况,用简单示例图来描述如下:

    客户端创建五个套接字来连接服务器,一旦连接了服务器就会创建一个子进程出来为客户端进行处理,从图中可以看,服务端创建了5个子进程出来。

    对于服务端程序是不需要进行修改的,只需修改客户端创建五个套接字既可,修改客户端程序如下:

    这时,编译运行:

    关于这个比较容易理解,是由于wait函数只等待一个子进程退出就返回了,所以有四个进程处于僵尸的状态。

    再运行一次:

    那如果再运行一次呢?

    从以上结果来看,僵尸进程的数目不一定。

    由于wait函数只能返回一个子进程,这时候我们应该用什么方法解决呢?可以用waitpid函数来解决,具体程序修改如下:

    再来运行看是否还存在僵尸进程呢?

    这种情况有一点不太好解释,但是还会遇到这种情况:

    对于这种情况,就可以用理论来解释了,原因是由于有五个子进程都要退出,这就意味着有五个信号要发送给父进程,

    在关闭连接时,会向服务器父进程发送SIGCHLD信号,具体如下图:

    此时父进程处于一个handle_sigchld()的过程,而在这个处理过程中,如果其它信号到了,其它信号会丢失,之前也提到过,这些信号是不可靠信号不可靠信号只会排队一个,如果只排队一个的话,那么最终能够处理两个子进程,所以,最后存在三个僵尸进程。

    那为什么有时会有2个僵进程,有时又会有四个呢,这里来解释一下:

    原因可能跟FIN(客户端在终止时,会向服务器发送FIN)到达的时机有关,服务器收到FIN的时候,返回等于0就退出了子进程,这时就会向服务器发送SIGCHLD信号,如果这些信号都是同时到达的话,那么就有可能只处理一个,这时就会出现了4个僵尸进程;如果不是同时到达,handle_sigchld()函数就会执行多次,如果被执行了两次,捕捉到了两个信号的话,那就此时就会出现3个僵进程,以此类推,但不管结果怎样,都是属于需要解决的情况。

    那怎么解决此问题呢,可以用一个循环来做:

    这时再编译运行:

    好了,今天就学到这,下节见~~

  • 相关阅读:
    theano 深度学习大全
    theano 深度学习大全
    hann function
    hann function
    matlab 各种文件的读取(及读写问题的解决)
    matlab 各种文件的读取(及读写问题的解决)
    极简代码(八)—— binary activation function
    极简代码(八)—— binary activation function
    微积分基本概念相关证明
    微积分基本概念相关证明
  • 原文地址:https://www.cnblogs.com/webor2006/p/4014586.html
Copyright © 2011-2022 走看看