使用 WinSock 客户端/服务器应用程序重用套接字

Posted

技术标签:

【中文标题】使用 WinSock 客户端/服务器应用程序重用套接字【英文标题】:reusing sockets with WinSocket client/server application 【发布时间】:2015-05-13 15:20:22 【问题描述】:

我有 2 个简单的应用程序,一个客户端和一个服务器。客户端连接到服务器,服务器向客户端发送数据。在客户端应用程序的主循环中,每次我想接收另一条信息时,我都会关闭套接字并创建一个新的。它有效,但对我来说看起来不正确。这是正确的吗?我真的需要每次都关闭并重新打开一个套接字,还是有其他方法可以做到这一点?

客户:

    int main()
    DWORD id;
    SOCKET s;
    struct sockaddr_in sa;
    WSADATA wsas;
    WORD wersja;
    int recv_size;
    LPTHREAD_START_ROUTINE WINAPI funkcja = getCommend;

    watek = CreateThread(NULL, 0, funkcja, 0, 0, &id);



    wersja = MAKEWORD(2, 0);
    WSAStartup(wersja, &wsas);


    memset((void *)(&sa), 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(PORT);
    sa.sin_addr.s_addr = inet_addr("25.62.229.110");

    int result;
    s = socket(AF_INET, SOCK_STREAM, 0);
    result = connect(s, (struct sockaddr FAR *) &sa, sizeof(sa));
    if (result == SOCKET_ERROR)
    
        printf("\nBłąd połączenia!");
        return 1;
    

    int a;
    if ((recv_size = recv(s, server_reply, SIZE, 0)) == SOCKET_ERROR)
    
        printf("recv failed\n");
    
    else
        while (result != SOCKET_ERROR)
            //printf("%s\n", server_reply);     
            closesocket(s);
            Sleep(1000);
            s = socket(AF_INET, SOCK_STREAM, 0);
            result = connect(s, (struct sockaddr FAR *) &sa, sizeof(sa));
            if (result == SOCKET_ERROR)
                break;
            
            result = recv_size = recv(s, server_reply, SIZE, 0);
        
    
    getchar();
    closesocket(s);
    TerminateThread(watek, NULL);
    WSACleanup();
    return 1;

服务器:

int main()
DWORD id;
SOCKET s, new_socket;
LPTHREAD_START_ROUTINE WINAPI funkcja = getCommend;
struct sockaddr_in sa, sc;
WSADATA wsas;
int c;
watek = CreateThread(NULL,0,funkcja,0,0,&id);

WSAStartup(MAKEWORD(1, 1), &wsas);

s = socket(AF_INET, SOCK_STREAM, 0);

//memset((void *)(&sa), 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(PORT);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
bind(s, (struct sockaddr FAR*)&sa, sizeof(sa));
listen(s, 5);
puts("Czekam na polaczenie...");
c = sizeof(struct sockaddr_in);
while ((new_socket = accept(s, (struct sockaddr *)&sc, &c)) != INVALID_SOCKET)

    //puts("polaczenie zaakceptowane");
    send(new_socket, message, strlen(message), 0);

if (new_socket == INVALID_SOCKET)

    printf("Nie udane połaczenie, blad nr : %d", WSAGetLastError());
    return 1;

TerminateThread(watek, NULL);   
getchar();

【问题讨论】:

呃……你不关闭socket继续使用? "closesocket(s);"嗯? 我说更好的方法是:不要关闭套接字并继续使用它。是什么让您认为您需要关闭套接字?即使在接收到一组数据后,它与服务器的连接也会保持。每次成功调用 recv() 后没有理由关闭它。 你的两个代码都错了,但主要是你的服务器。您的服务器在发送新消息之前接受新连接,并且不会关闭先前的连接。因此,您的客户端每次都必须重新连接。您需要更改双方以重用他们已经拥有的现有连接。而且您需要对消息进行框架化,以便客户端知道一条消息在哪里结束以及下一条消息从哪里开始。您不能假设在 TCP 中 send()recv() 之间存在一对一的关系,只能在 UDP 中。 【参考方案1】:

嗯,这里有些东西不是很常见。

首先,您通常用英文命名变量,而不是用拉丁字母书写的其他语言。我可能会因此而被否决,但这是普遍做法是有原因的。

其次,我更喜欢使用 C++ 标准而不是操作系统特定的 API。 为什么不使用std::thread 而不是 WinApi 线程?它更加简单、简短和优雅。

第三,你的代码是 100% 程序化的。你没有标准的 C++ 套接字类,但这并不意味着你不能(或不应该)编写自己的 Socket 类,它以优雅的方式处理创建、绑定、发送和接收功能。

第四,s 不是变量的好名称,即使 MSDN 示例如此。 acceptingSocket 可能是。

关于您的实际问题: 这里有很多选择,我能想到的 3 个: 1) 不要关闭套接字并继续在 while 循环内接收数据。 优点:容易写。当服务器有新数据时,客户端会立即得到它。 缺点:当您的服务器每次都有更多的连接时,这不是一个好方法,因为那样它会崩溃。

2) 让服务器打包数据。然后客户端以预定义的时间间隔打开一个连接,一次请求所有累积的数据并接收它。 优点:三个中最节省的 CPU 资源 选项。 缺点:您的客户不会立即获取数据,而是在预定义的时间间隔内获取数据

3) 让客户端在每个会话开始时将本地 IP 和端口发送到服务器。然后让客户端在本地主机上保持一个有界套接字等待连接。每次服务器有新的东西要告诉客户端 - 它只是使用保存的 IP 和端口连接到客户端,发送数据并关闭连接。然后客户端返回等待新连接。这样 优点:在这种情况下,服务器每次都会有尽可能少的连接,如果您打算让服务器为许多客户端提供服务,这是一件好事。 缺点:重量实际上落在了客户身上,这在很多情况下并不是最好的做法。

【讨论】:

【参考方案2】:

通常的单连接方法是:

Create socket
bind socket
Listen on socket
newsocket = accept on socket
while no accept errors
    while newsocket connected and error free
        process newsocket
    newsocket = accept on socket

可以通过多种方式处理多个连接。

多线程:

create thread pool
Create socket
bind socket
Listen on socket
while no errors
    newsocket = accept on socket
    hand newsocket off to thread pool for processing

基于select 的单线程

Create socket
bind socket
Listen on socket
add socket to readFD  list
while no errors
    select on readFD list
    if socket selected
        newsocket = accept on socket
        add newsocket to readFD  list
    while other sockets selected
        process other sockets

【讨论】:

以上是关于使用 WinSock 客户端/服务器应用程序重用套接字的主要内容,如果未能解决你的问题,请参考以下文章

需要在winsock控件中添加功能

如何使用 C++ 在 Winsock 中将大字符串从客户端发送到服务器

winsock:蓝牙客户端-服务器未连接

Winsock 仅在程序关闭时发送数据

UDP 打孔 (c++/winsock)

Winsock 绑定到同一个端口