Адрес уже используется. - PullRequest
1 голос
/ 20 января 2012

Недавно я работал над некоторым клиентским кодом для отправки и получения сообщений с сервера с использованием потоков.Приведенный ниже код ведет себя странно при запуске.После ввода сообщения для отправки на сервер код завершает задачу, хотя с ошибкой «сокет уже используется» сервер получает его.Но каждое последующее сообщение, которое я пытаюсь отправить на сервер, не принимается немедленно, но, по-видимому, все сразу принимается, когда клиентская программа завершается.

(Кроме того, я уверен, что ошибка на стороне клиента, странное поведение не проявляется, если прокомментировать функцию вывода.)

Как я могу исправить эту ошибку?

Клиент

#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();
}

Ответы [ 2 ]

3 голосов
/ 20 января 2012

Похоже, вы пытаетесь привязать клиента () к тому же порту, что и сервер. Это не обязательно. И что еще хуже, вы пытаетесь привязаться к 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;
}
0 голосов
/ 20 января 2012

Я полагаю, что проблема с параметром сокета "SO_REUSEADDR", который вы даете, является проблемой.

Вы вызываете эту функцию снова и снова, не закрывая клиентский сокет?В этом случае это не будет работать.

Цель этого параметра сокета - «повторно использовать адрес, когда уже открытый сокет для того же адреса находится в состоянии TIME_WAIT, в противном случае вы получите указанную ошибку».

Если ваш клиентоткрывая новое соединение каждый раз, я должен сказать, что вам придется более эффективно структурировать свой код и обрабатывать сценарии закрытия сокетов.

...