如果我不首先创建TCP连接,为什么发送数据报不起作用?

时间:2011-04-10 10:39:30

标签: c++ windows sockets winsock

以下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 个答案:

答案 0 :(得分:2)

    int data_len = strlen(data);

Tony Hoare称他的NULL指针定义为十亿美元的错误。字符串零终止必须是Dennnis Ritchie的100亿美元错误。添加一个。

您的程序是另一种发现UDP不是可靠协议的复杂方法。允许网络堆栈任意使UDP数据包消失或重新排序。只要在其上面有另一个协议来检测这个,就像TCP一样,这是可以的。你没有这样的绑带飞行,bugfix()实际上并不是一种解决方法。

使用TCP,首先发送数据包长度,以便接收器知道跟随的字节数,这样您就不会受到流行为的影响。但更重要的是,通过套接字在线程之间交换数据是一种真的昂贵的方法,以避免使用带有互斥的数组。线程在进程中不受限制地访问内存,您不需要进程间通信机制来让它们交换数据。

答案 1 :(得分:0)

我发现了几个问题。

我通常不使用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));
相关问题