Почему отправка дейтаграммы не работает, если я сначала не создаю соединение TCP - PullRequest
1 голос
/ 10 апреля 2011

Следующая программа на языке c ++ должна преобразовывать каждую строку в верхний регистр с помощью дейтаграммы сокета для связи между двумя потоками.

Example:
Hello World!<return>
HELLO WORLD!
123abc!<return>
123ABC!
<return>
<end program>

Программа, как написано, работает для меня, однако, если я прокомментирую вызов функции bugfix() вОсновная программа ждет бесконечно после первой строки ввода.

Example:
Hello World!<return>
<the program wait indefinitely>

Это происходит в Windows 7 с последним обновлением от 10.04.2011 с использованием последнего MinGW32.

#include <iostream>
#include <cstdlib>
#include <cctype>
#include <sys/types.h>
#include <winsock.h>
#include <windows.h>
#include <process.h>

using namespace std;

#define CHECK(exp, cond)  do { typeof(exp) _check_value_ = exp; check(_check_value_ cond, _check_value_, __LINE__, #exp #cond); } while(0)

template <class T>
void check(bool ok, T value, int line, const char* text) {
    if (!ok) {
        cerr << "ERROR(" << line << "):" << text << "\nReturned: " << value << endl;
        cerr << "errno=" << errno << endl;
        cerr << "WSAGetLastError()=" << WSAGetLastError() << endl;
        exit(EXIT_FAILURE);
    }
}

#define DATA_CAPACITY   1000
#define PORT            23584
#define TEST_IP         "192.0.32.10"
#define MYSELF          "127.0.0.1"
#define DST_IP          MYSELF

sockaddr_in address(u_long ip, u_short port) {
    sockaddr_in addr = { };
    addr.sin_family = AF_INET;
    addr.sin_port = port;
    addr.sin_addr.s_addr = ip;
    return addr;
}

void __cdecl client_thread(void* args) {
    SOCKET s = *(SOCKET*)args;

    sockaddr_in addr = address(inet_addr(DST_IP), htons(PORT));

    char data[DATA_CAPACITY];
    while (1) {
        cin.getline(data, DATA_CAPACITY);
        int data_len = strlen(data);

        CHECK(sendto(s, data, data_len, 0, (sockaddr*)&addr, sizeof addr), >= 0);
        CHECK(recvfrom(s, data, DATA_CAPACITY, 0, NULL, NULL), >= 0);

        cout << data << endl;

        if (data_len == 0)
            break;
    }

    CHECK(closesocket(s), == 0);
}

void __cdecl server_thread(void* args) {
    SOCKET s = *(SOCKET*)args;
    sockaddr_in addr = address(INADDR_ANY, htons(PORT));
    int addr_size = sizeof addr;
    CHECK(bind(s, (sockaddr*)&addr, sizeof addr), != SOCKET_ERROR);

    char data[DATA_CAPACITY];
    while (1) {
        int data_len = recvfrom(s, data, DATA_CAPACITY, 0, (sockaddr*)&addr, &addr_size);
        CHECK(data_len, >= 0);

        for (int i = 0; i < data_len; i++)
            if (islower(data[i]))
                data[i] = toupper(data[i]);

        CHECK(sendto(s, data, data_len, 0, (sockaddr*)&addr, addr_size), >= 0);

        if (data_len == 0)
            break;
    }

    CHECK(closesocket(s), == 0);
}

// This function create a TCP connection with www.example.com and the close it
void bugfix() {
    SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in addr = address(inet_addr(TEST_IP), htons(80));
    connect(s, (sockaddr*)&addr, sizeof addr);
    CHECK(closesocket(s), == 0);
}

int main()
{
    cout << "Convert text to uppercase, an empty line terminate the program" << endl;


    WSADATA wsaData;
    CHECK(WSAStartup(MAKEWORD(2, 2), &wsaData), == 0);

    SOCKET client = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    SOCKET server = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

    CHECK(client, != INVALID_SOCKET);
    CHECK(server, != INVALID_SOCKET);

    // if this function is not called the program doesn't work
    bugfix();

    HANDLE hClient = (HANDLE)_beginthread(client_thread, 0, &client);
    HANDLE hServer = (HANDLE)_beginthread(server_thread, 0, &server);

    HANDLE h[] = { hClient, hServer };

    WaitForMultipleObjects(sizeof h / sizeof *h, h, TRUE, INFINITE);

    CHECK(WSACleanup(), == 0);

    return EXIT_SUCCESS;
}

Ответы [ 2 ]

2 голосов
/ 10 апреля 2011
    int data_len = strlen(data);

Тони Хоар назвал свое определение нулевого указателя своей ошибкой в ​​миллиард долларов.Наличие нулевых строк должно быть ошибкой Денниса Ричи в десять миллиардов долларов.Добавьте один.

В противном случае ваша программа - это сложный способ обнаружить, что UDP не является надежным протоколом.Сетевой стек может произвольно заставить пакеты UDP исчезать или изменять их порядок.Это нормально, если есть еще один протокол, который определяет это, например, TCP.Вы летите без таких бинтов, bugfix () на самом деле не обходной путь.

Используйте TCP, сначала отправьте длину пакета, чтобы получатель узнал, сколько байтов следует, чтобы вы были невосприимчивы к поведению потока.Но, что более важно, обмен данными между потоками через сокет - это действительно дорогой способ избежать использования массива с мьютексом.Потоки имеют свободный доступ к памяти в процессе, вам не нужен механизм межпроцессного взаимодействия, чтобы заставить их обмениваться данными.

0 голосов
/ 11 апреля 2011

Я вижу сразу несколько проблем.

Обычно я не использую флаг IPPROTO_UDP для создания сокета. Просто передайте 0 для параметра протокола в сокет.

SOCKET client = socket(PF_INET, SOCK_DGRAM, 0);
SOCKET server = socket(PF_INET, SOCK_DGRAM, 0);

Более важно. Вам необходимо вызвать «bind» на клиентском сокете так же, как вы делаете серверный сокет. Если вы хотите, чтобы ОС выбирала для вас произвольно доступный порт, вы можете использовать 0 в качестве значения порта и IPADDR_ANY для IP-адреса. Если вы хотите узнать, что ОС выбрала в качестве локального порта для вас, вы можете использовать getsockname. Примерно так:

void __cdecl client_thread(void* args) {
    SOCKET s = *(SOCKET*)args;

    sockaddr_in addr = address(inet_addr(DST_IP), htons(PORT));

    sockaddr_in localAddrBind = address(INADDR_ANY, 0);
    sockaddr_in localAddrActual = {};
    int length = sizeof(localAddrActual);
    int bindRet = bind(s, (sockaddr*)&localAddrBind, sizeof(localAddrBind));

    getsockname(s, (sockaddr*)&localAddrActual, &length);
    printf("Listening on port %d\n", ntohs(localAddrActual.sin_port));
...