开发者

Why sending datagram doesn't work if I don't create a TCP connection first?

The following c++ program should convert each line to uppercase using socket datagram to communicate between two threads.

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

The program as written works for me, however if I comment the bugfix() function call in the main the program wait indefinitely after the first line of input.

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

This happen on windows 7 with the last update as 10/04/2011 using the last 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 = { };
    开发者_StackOverflow社区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;
}


    int data_len = strlen(data);

Tony Hoare called his definition of a NULL pointer his billion dollar mistake. Having strings zero-terminated must be Dennnis Ritchie's ten billion dollar mistake. Add one.

Your program is otherwise an elaborate way to discover that UDP is not a reliable protocol. The network stack is allowed to arbitrarily make UDP packets disappear or reorder them. Which is okay as long as there's another protocol on top of it that detects this, like TCP. You are flying without such bandaids, bugfix() is not actually a workaround.

Use TCP, send the packet length first so that the receiver will know how many bytes are following so you're immune to stream behavior. But more to the point, exchanging data between threads through a socket is a really expensive way to avoid using an array with a mutex. Threads have unfettered access to memory in the process, you don't need an interprocess communication mechanism to get them to exchange data.


I see several problems right off the bat.

I normally don't use IPPROTO_UDP flag to create the socket. Just pass 0 for the protocol parameter to the socket.

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

More important. You need to call "bind" on the client socket in the same way that you do the server socket. If you want the OS to pick a randomly available port for you, you can use 0 as the port value and IPADDR_ANY for the IP address. If you want to know what the OS picked as a local port for you, you can use getsockname. Something like the following:

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));
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜