zoukankan      html  css  js  c++  java
  • 网络编程Socket它TCP它TIME_WAIT国家具体解释

    下面我们用最简单的一对一的客户server编程模型重现遇到的一些问题:

    初学者socket当写作socket名其妙的问题。比方说bind函数返回的常见错误是EADDRINUSE


    使用以下的程序重现这个状态:

    client:

    int main(int argc, const char * argv[])
    {
    
        struct sockaddr_in serverAdd;
        
        bzero(&serverAdd, sizeof(serverAdd));
        serverAdd.sin_family = AF_INET;
        serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
        serverAdd.sin_port = htons(SERV_PORT);
        
        int connfd = socket(AF_INET, SOCK_STREAM, 0);
        
        int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
        if (connResult < 0) {
            printf("连接失败
    ");
            close(connfd);
            return -1;
        }
        
        ssize_t writeLen;
        ssize_t readLen;
        char recvMsg[65535] = {0};
        char sendMsg[20] = "I am client";
        
        writeLen = write(connfd, sendMsg, sizeof(sendMsg));
        if (writeLen < 0) {
            printf("发送失败
    ");
            close(connfd);
            return -1;
        }
        else
        {
            printf("发送成功
    ");
        }
        
        while (1) {
            
    //        sleep(1);
            
            readLen = read(connfd, recvMsg, sizeof(recvMsg));
            if (readLen < 0) {
                printf("读取失败
    ");
                close(connfd);
                return -1;
            }
            if (readLen == 0) {
                printf("服务器关闭
    ");
                close(connfd);
                return -1;
            }
    
            printf("server said:%s
    ",recvMsg);
            
        }
        
        close(connfd);
        return 0;
    }
    
    

    server:

    int main(int argc, const char * argv[])
    {
    
        struct sockaddr_in serverAdd;
        struct sockaddr_in clientAdd;
        
        bzero(&serverAdd, sizeof(serverAdd));
        serverAdd.sin_family = AF_INET;
        serverAdd.sin_addr.s_addr = htonl(INADDR_ANY);
        serverAdd.sin_port = htons(SERV_PORT);
        
        socklen_t clientAddrLen;
        
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        
        if (listenfd < 0) {
            printf("创建socket失败
    ");
            close(listenfd);
            return -1;
        }
        
        int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
        if (bindResult < 0) {
            close(listenfd);
            printf("绑定port失败,errno = %d
    ",errno);
            return -1;
        }
        else
        {
            printf("绑定port成功
    ");
        }
        
        listen(listenfd, 20);
        
        int connfd;
        unsigned char recvMsg[65535];
        char replyMsg[20] = "I am server";
        
        clientAddrLen = sizeof(clientAdd);
        connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen);
        if (connfd < 0) {
            close(listenfd);
            printf("连接失败
    ");
            return -1;
        }
        else
        {
            printf("连接成功
    ");
        }
        
        ssize_t readLen = read(connfd, recvMsg, sizeof(recvMsg));
        printf("readLen:%ld
    ",readLen);
        if (readLen < 0) {
            printf("读取失败
    ");
            return -1;
        }
        else if (readLen == 0) {
            printf("读取完毕
    ");
            close(listenfd);
            return 0;
        }
        
        printf("client said:%s
    ",recvMsg);
           
        while (1)
        {       
            write(connfd, replyMsg, sizeof(replyMsg));      
        }
        
        close(connfd);
    
        return 0;
    }
    
    

    首先执行server程序,再执行client。然后关闭服务端后立刻再打开服务端,就会打印例如以下信息:48相应EADDRINUSE错误码

    绑定port失败,errno 48


    这里要说明一个问题:

    当一个Unix进程不管自愿的(调用exit或者从main函数返回)还是非自愿的(收到一个终止本进程的信号)终止时,全部打开的描写叙述符都被关闭,这也将导致仍然打开的不论什么TCP连接上发出一个FIN。


    非常明显server已经关闭了。为什么会绑定port失败呢。以下是TCP连接终止的四个分节




    某些情况下第一个分节的FIN随数据一起发送。另外,第二个和第三个分节有可能被合并成一个分节。


    这里我们的server是主动关闭的一端。当主动发送FIN分节以后等待确认,状态变为FIN_WAIT_1,收到确认以后状态变为FIN_WAIT_2,收到client的FIN分节以后状态变为TIME_WAIT状态。这里在TIME_WAIT状态会停留2MSL后才会进入CLOSED状态。所以我们再立刻启动server的时候,之前的连接还没有处于CLOSED状态,还存在者,所以就会绑定失败了;后面会讲到为什么会存在TIME_WAIT状态;

    这里client是被动关闭的一端,收到服务端的FIN之后状态进入CLOSE_WAIT,这个时候read方法会返回0,然后发送对第一个分节的确认。此时client调用close方法发送FIN分节给服务端进入LAST_ACK状态,等待确认到达。收到确认以后连接状态变为CLOSED


    TIME_WAIT状态:停留在该状态的持续时间是最长分节生命期(maximum segment lifetime,MSL)的两倍。有时候称为2MSL。MSL是不论什么IP数据报可以在因特网中存活的最长时间,最大值为255。这是一个跳数限制而不是真正的时间限制。


    TIME_WAIT状态存在理由:

    可靠地实现TCP全双工连接的终止:可能不得不重传终于那个ack,TIME_WAIT后是CLOSED,假设没有TIME_WAIT的2MSL,直接CLOSED,那么假设最后一个ACK丢失了,是不会又一次再发送ACK的,那服务端收不到ACK就会又一次发送终于那个FIN,这个时候client已经是CLOSED了,就会响应一个RST,这个RST就会被server解释成一个错误。

    同意来的反复分节在网络中消逝:保证每成功建立一个TCP连接时。来自该连接先前化身的老的反复分组都已经在网络中消逝了。从而不会被误解成新连接的分组。


    这里另一种情况:打开clientwhile里面的sleep,(或者屏蔽掉client以下的代码)然后再先执行server程序。再执行client,然后关闭服务端后立刻再打开服务端,仍然会绑定失败,此时的状态和之前的有点不一样

    if (readLen == 0) {
                printf("server关闭
    ");
                close(connfd);
                return -1;
            }

    我们从终端信息打印例如以下,此时server处于FIN_WAIT_2状态,就如上面说的由于client还没有关闭连接,没有发送第三个FIN分节,此时client由于已经收到来自服务端的FIN分节而处于CLOSE_WAIT状态;


    wanglijuntekiMac-mini:~ wanglijun$ netstat -an |grep 8000

    tcp4       0      0  192.168.1.103.8000     192.168.1.103.49632    FIN_WAIT_2 

    tcp4  290960      0  192.168.1.103.49632    192.168.1.103.8000     CLOSE_WAIT 


    解决方法:

    这里有一个SO_REUSEADDR套接字选项,打开之后就能解决如上的问题,我们在band之前加入例如以下设置代码:

    <span style="font-size:12px;">int yes = 1;
        setsockopt(listenfd,
                   SOL_SOCKET, SO_REUSEADDR,
                   (void *)&yes, sizeof(yes));</span>

    server重新启动监听时,试图捆绑现有连接上的port会失败,(另一种情况可能之前派生出来的子进程还处理着连接)假设设置了SO_REUSEADDR套接字选项。就会bind成功,全部的TCPserver都应该指定SO_REUSEADDR套接字选项,以同意server在这样的情形下被又一次启动;SO_REUSEADDR同意在同一port上启动同一server的多个实例,仅仅要每一个实例捆绑一个不同的本地IP地址就可以。


    对于TCP,我们绝不可能启动捆绑同样IP地址和同样port号的多个server。


    參考:

    UNIX Network ProgrammingVolume 1, Third Edition: TheSockets Networking API


    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    spring data jpa 动态查询(mysql)
    C#面向对象15 多态
    Visual Studio 2012网站如何只生成一个DLL文件
    C#面向对象14 List泛型集合/装箱和拆箱/字典集合(Dictionary)
    C#面向对象13 文件类操作 Path/File/FileStream
    C#面向对象12 集合
    C#面向对象11 里氏转换
    C#面向对象10 继承
    C#面向对象9 字符串
    C# 面向对象8 值类型和引用类型
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/4688595.html
Copyright © 2011-2022 走看看