地址已在使用中

Address Already in Use.

本文关键字:地址      更新时间:2023-10-16

最近我一直在编写一些客户端代码,用于使用线程从服务器发送和接收消息。以下代码在运行时表现异常。在输入要发送到服务器的消息后,代码完成了任务,尽管出现了"套接字已在使用"错误,但服务器会收到它。但我尝试发送到服务器上的每一条后续消息都没有立即收到,但当客户端程序终止时,似乎都会同时收到。

(此外,我确信错误发生在客户端,如果对输出函数进行注释,则不会显示出奇怪的行为。)

如何修复此错误?

客户端

#include <stdio.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string>
#include <iostream>
#include <errno.h>
#include <pthread.h>    
void* input(void* ptr)
{
    int on = 1;
    bool *input_done = ((struct thread_args*)ptr)->process_done;
    struct addrinfo *res = ((struct thread_args*)ptr)->result;
    char msg[256];
    int sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(char *)&on,sizeof(on));
    bind(sock,res->ai_addr,res->ai_addrlen);
    connect(sock,res->ai_addr,res->ai_addrlen);
    cin.getline(msg,256);
    if (msg[0] == '/') {exit(1);}
    send(sock,msg,sizeof msg,0);
    cout << "You:" << msg << endl;
    *input_done = 1;
    close(sock);
    pthread_exit(NULL);
}
void* output(void* ptr)
{
        int on = 1;
        bool *output_done = ((struct thread_args*)ptr)->process_done;
    struct addrinfo *res = ((struct thread_args*)ptr)->result;
    char msg[256];
    int sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
    bind(sock,res->ai_addr,res->ai_addrlen);
    connect(sock,res->ai_addr,res->ai_addrlen);
    recv(sock,msg,sizeof msg,0);
    cout << "Recieved:" << msg;
    *output_done = 1;
    close(sock);
    pthread_exit(NULL);
}
void io_client()
{
    //thread function variables
    pthread_t t1,t2;
    bool input_done = 1, output_done = 1;
    //socket setup variables
    struct addrinfo hints, *res;
    memset(&hints,0,sizeof hints);
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    getaddrinfo("localhost","8080",&hints,&res);
    //setting up structures to pass data to threaded functions
    struct thread_args i_args, o_args;
    i_args.result = res; i_args.process_done = &input_done;
    o_args.result = res; o_args.process_done = &output_done;
    while(1)
    {
        if (output_done)
        {
            pthread_create(&t2,NULL,output,&o_args);
            output_done = 0;
        }
        if (input_done)
        {
            pthread_create(&t1,NULL,input,&i_args);
            input_done = 0;
        }
    }
}
int main()
{
    io_client();
}

服务器

void server()
{
    struct addrinfo hints, *res;
    int sock=-1, newsock=-1;
    int length, on=1;
    char **address_list; int entries = 0;
    //fd_set read_fd;
    //struct timeval timeout;
    char buffer[100];
    memset(&hints,0,sizeof hints);
    res = NULL;
    memset(&res,0,sizeof res);
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    getaddrinfo("localhost","8080",&hints,&res);
    sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(char *)&on,sizeof(on));
    bind(sock,res->ai_addr,res->ai_addrlen);
    listen(sock,10);
    while(1)
    {
        struct sockaddr_storage addr;
        char ipstr[INET6_ADDRSTRLEN];
        socklen_t len;
        len = sizeof addr;
        newsock = accept(sock,NULL,NULL);
        getpeername(newsock,(struct sockaddr*)&addr,&len);
        struct sockaddr_in *s = (struct sockaddr_in*)&addr;
        inet_ntop(AF_INET,&s->sin_addr,ipstr,sizeof ipstr);
        length = 100;
        setsockopt(newsock,SOL_SOCKET,SO_RCVLOWAT, (char*)&length,sizeof length);
        recv(newsock,buffer,sizeof buffer,0);
        cout << buffer << endl;
    }
    if (newsock != -1)
    {
        close(newsock);
    }
    if (sock != -1)
    {
        close(sock);
    }
}
int main()
{
    server();
}

看起来您正试图将客户端bind()连接到与服务器相同的端口。这没有必要。更糟糕的是,您正试图绑定到服务器的IP地址,这也是一个更大的问题。通常,对于要调用connect()函数的客户端套接字,您应该只将套接字绑定到端口0和IP 0,这样操作系统就可以为您选择一个随机可用的端口,并为连接使用正确的本地IP地址和适配器。您可以调用getsockname()来发现操作系统在调用connect后为您选择的端口。

如果你让操作系统为你选择客户端端口,你就不需要那个SO_REUSESADDR调用了。尽管如此,您的服务器代码可以在关闭后需要重新启动且连接仍有待关闭的情况下调用它。

此外。您没有检查任何套接字调用的返回值。这可能就是为什么你会得到一些神秘的结果。对bind()的调用更有可能失败,因为您正在指定服务器IP,但connect()成功了,因为它将自动绑定套接字(如果尚未绑定)。

下面是input()函数的清理版本。转换output()函数是留给读者的练习。如果你以我为榜样,你的身体会很好。

void* input(void* ptr)
{
    int on = 1;
    bool *input_done = ((struct thread_args*)ptr)->process_done;
    int ret;
    int success = true;
    struct sockaddr_in addrLocal = {};
    struct addrinfo *res = ((struct thread_args*)ptr)->result;
    char msg[256];
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    success = (sock != -1);
    if (success)
    {
        addrLocal.sin_family = AF_INET;
        addrLocal.sin_port = INADDR_ANY;        // INADDR_ANY == 0 --> pick a random port for me
        addrLocal.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY == 0 --> use all appropriate network 
        ret = bind(sock,(sockaddr*)&addrLocal,sizeof(addrLocal));
        if (ret == -1) perror("bind: ");
        success = (ret != -1);
    }
    if (success)
    {
        ret = connect(sock,res->ai_addr,res->ai_addrlen);
        if (ret == -1) perror("connect: ");
        success = (ret != -1);
    }
    if (success)
    {
        cin.getline(msg,256);
        if (msg[0] == '/') {exit(1);}
        ret = send(sock,msg,sizeof msg,0);
        if (ret == -1) perror("send: ");
        success = (ret != -1);
    }
    if (success)
    {
        cout << "You:" << msg << endl;
        *input_done = 1;
    }
    if (sock != -1)
    {
        close(sock);
        sock = -1;
    }
    return NULL;
}

我想"SO_ REUSEADDR";您提供的套接字选项就是问题所在。

您是否在不关闭客户端套接字的情况下一次又一次地调用该函数?在这种情况下,它不会起作用

此套接字选项的目的是"当已打开的同一地址的套接字处于TIME_WAIT状态时重用该地址,否则将出现上述错误"。

如果您的客户端每次都打开一个新的连接,那么我必须说,您必须更有效地构建代码,并处理套接字关闭场景。