如何避免服务器套接字的TIME_WAIT?

8
我知道你会将其标记为重复(question1question2question3),但答案并不是我要的(我想其他人也是)。所以,我向套接字高手们请教:如果我关闭套接字,如何获得绑定错误(地址已在使用中)?我将描述我的问题。
引用:

我有一个客户端与 服务器 通信
在服务器上,我有两个套接字:sockS(主套接字,监听)和 sockTX(客户端套接字)
如果我调用程序一次,通信很好,然后我关闭两个套接字
如果我重新启动服务器和客户端,则会出现错误,并且我必须等待 TIME_WAIT(在 Ubuntu 32位上大约3分钟)。

为什么在关闭两个套接字后仍会出现绑定错误?
是否有一种方法可以使其正常工作而不需要任何魔法(SO_REUSEADDR)?我知道我的代码有问题...

谢谢大家。

这是代码:
客户端
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 5000
#define SERVER "127.0.0.1"
#define MAXLINE 128

int printMessage(char* str);

int main(){
char buff[MAXLINE+1];

struct sockaddr_in server, client;
struct hostent *host;
int sock, n;
//socklen_t len;

    if((sock = socket(AF_INET,SOCK_STREAM,0)) == -1){
        perror("\nErrore socket()");
        return -1;
    }

    client.sin_family = AF_INET;
    client.sin_port = htons(0); // la porta e' scelta dal sistema operativo
    client.sin_addr.s_addr = htonl(INADDR_ANY);

    if( bind(sock,(struct sockaddr*)&client, sizeof(client)) == -1){
        perror("\nErrore bind()");
        return -1;
    }

    server.sin_family = AF_INET;
    server.sin_port = htons(PORT);

    if((host = gethostbyname(SERVER)) == NULL ){
        perror("\nErrore gethostbyname()");
        return -1;
    }

    server.sin_addr = *((struct in_addr *)host->h_addr);

    if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0){
        perror("\nErrore connect");
        return -1;
    }

    // MESSAGGIO

    sprintf(buff, "CTX\nclientTX\nserver\nCiao da client\n");
    if(send(sock, buff, strlen(buff), 0) < 0) {
        perror("\nErrore sendto");
        return -1;
    }
    else {
        printf("\nMessaggio inviato");
    }

    if((n = recv(sock, buff, MAXLINE, 0)) < 0) {
        perror("\nErrore ricezione risposta");
        return -1;
    } else {
        buff[n] = '\0';
        int test = printMessage(buff);
        printf("\nEsito: %s\n", (test == 1 ? "OK" : "FAIL"));
    }

    shutdown(sock, 2); // 2 = RD_WR
    close(sock);
    return 0;
}

int printMessage(char* str){
int i;
char* temp;

    printf("Mittente: ");
    for(i = 0; str[i] != '\n'; i++)
        printf("%c", str[i]);
    printf(" ");
    for(i = i+1; str[i] != '\n'; i++)
        printf("%c", str[i]);
    printf("\nDestinatario: ");
    for(i = i+1; str[i] != '\n'; i++)
        printf("%c", str[i]);

    temp = (char*)malloc(30 * sizeof(char));
    strncpy(temp, str+i+1, 30);
    printf("Messaggio: %s\n", temp);

    if(strcmp(temp, "OK") == 0)
        return 1;
    else
        return 0;
}


服务器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 5000
#define SERVER "127.0.0.1"
#define MAXLINE 128

int printMessage(char* str);

int main() {
char buff[MAXLINE+1];
struct sockaddr_in server; //, client;
int sockS, sockTX, n;

    if((sockS = socket(AF_INET,SOCK_STREAM,0)) == -1)
    {
        perror("\nErrore socket()");
        return -1;
    }

    server.sin_family = AF_INET;
    server.sin_port = htons(PORT);
    server.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(sockS, (struct sockaddr *)&server, sizeof(server)) == -1)
    {
        perror("\nErrore bind()");
        return -1;
    }

    if(listen(sockS, 10) == -1)
    {
        perror("\nErrore listen()");
        return -1;
    }
    printf("SERVER\nInizializzazione completata!\n");

    if((sockTX = accept(sockS, (struct sockaddr *)NULL, NULL)) == -1)
    {
        perror("\nErrore accept()");
        return -1; 
    }
    printf("Socket connesso\n");

    // INVIO
    if((n = recv(sockTX, buff, MAXLINE, 0)) < 0) {
        perror("\nErrore recv()");
        return -1; // BREAK??

    } else {
        buff[n] = '\0';
        printMessage(buff);

    }

    sprintf(buff, "S\nserver\nclientTX\nOK\n");
    if(send(sockTX, buff, strlen(buff), 0) < 0){
        perror("\nErrore send()");
        return -1; // BREAK??

    } else {
        printf("Risposta inviata\n");

    }

    shutdown(sockTX, 2);
    close(sockTX);

    shutdown(sockS, 2);
    close(sockS);
    return 0;   
}

int printMessage(char* str){
int i;
char* temp;

    printf("Mittente: ");
    for(i = 0; str[i] != '\n'; i++)
        printf("%c", str[i]);
    printf(" ");
    for(i = i+1; str[i] != '\n'; i++)
        printf("%c", str[i]);
    printf("\nDestinatario: ");
    for(i = i+1; str[i] != '\n'; i++)
        printf("%c", str[i]);

    temp = (char*)malloc(30 * sizeof(char));
    strncpy(temp, str+i+1, 30);
    printf("Messaggio: %s\n", temp);

    if(strcmp(temp, "OK\n") == 0)
        return 1;
    else
        return 0;
}

大家好,谢谢你们的支持

编辑1:可能的解决方案(不是很完美,但比SOCK_REUSEADDR更好)
在两个服务器套接字关闭之前,尝试添加sleep(1)。
客户端将在服务器之前关闭,并且它会正常工作。
我知道这不是很完美。

或者更好的方法是,在服务器内部关闭第一个套接字之前,您可以检查客户端是否关闭了连接(类似于这里


1
SO_REUSEADDR并不是“魔法”,它是你问题的答案。你不喜欢正确的答案并不是重复问题的理由;答案不会改变。 - rici
@Duck 我在寻找更加“优雅”的东西 :) - incud
所以你的问题基本上是:“为什么关闭接收套接字要等待一段时间?” - jxh
@jxh 不完全是这样。我想问是否有一种标准正确的方法可以关闭我的套接字,告诉操作系统我的连接已关闭,并让我立即重用端口 :) - incud
@rici:不使用SO_REUSEADDR的一个原因是,当真的有另一个服务器使用端口时,它可能掩盖了错误。提问者没有很好地提出问题,但问题是是否有一种关闭套接字的方法可以避免TIME-WAIT状态。 - jxh
显示剩余11条评论
1个回答

6
如果您遵循TCP状态机图,您会发现如果套接字启动了发送FIN,则必须将其转换到TIME-WAIT状态。使用shutdown(sockTX, 2)而不等待客户端的FIN就可以做到这一点。
如果您希望服务器等待客户端的FIN,请在recv()上阻塞等待首先返回0值。然后,您可以调用close()
请注意,除非您以某种方式复制了套接字(使用dup*()fork()调用),否则如果它紧接着是close(),则无需调用shutdown()。您只需调用close()即可(如果套接字已被复制,则仅在关闭最后一个副本时才会发送FIN)。
根本没有必要shutdown()接受套接字(sockS)。
因此,我建议您将服务器端更改为以下内容以清理套接字:
while (recv(sockTX,...) > 0) {}
close(sockTX);

close(sockS);

哦,太好了:D在关闭第一个套接字之前,我刚刚添加了一个“recv”,现在它可以工作了:D我尝试过只使用发送,但是它会(显然)出现一些问题。目前为止,它可以工作了。谢谢:D - incud
只是一个注意事项:如果我写while(recv() < 0);,它会一直等待。如果我测试条件一次,它就可以工作了。嗯,第二种情况(看起来可以工作的那种)是正确的吗?我认为不是,因为当我测试条件时,客户端已经关闭了套接字,这只是巧合。为什么'while(recv() < 0)'不能工作呢? - incud
"while (recv() < 0);"几乎和我的建议完全相反。你想要做的是等待客户端FIN,所以你需要测试返回值是否为0或者出现了错误。如果你保存返回值,你会看到你接收到的值,如果小于0,你应该检查错误是什么。 - jxh
是的,抱歉,这是注释中的错误打印。我的代码是 while(1) { if(recv(sockTX, buff, MAXLINE, 0) < 0) break; }。 - incud
使用那个版本,如果recv()返回0,代码将再次调用它,这是错误的。返回0意味着FIN已经到达。 - jxh
哦,我是一个糟糕的程序员。将我的代码更改为“while((n = recv()) >= 0) printf("%d\n", n);”后,我发现recv返回了0。它小于0而不是小于等于0。而jxh写得是正确的。我错了。好吧,套接字教会了我一个艰苦的教训。再次感谢你,伙计。 - incud

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接