Winsock2,客户端-服务器通信——轮流发送/接收

Posted

技术标签:

【中文标题】Winsock2,客户端-服务器通信——轮流发送/接收【英文标题】:Winsock2, client-server communication - send/recv in turns 【发布时间】:2020-10-09 23:42:13 【问题描述】:

我想编写一个客户端/服务器应用程序,客户端和服务器可以在其中交换消息。

Client site communication:

send
recv
send 
recv

Server site communication:

recv
send
recv
send

但是,我有一个问题,因为只有一条消息是发送/接收。之后,套接字关闭,不再发送更多消息。出了什么问题以及如何解决这个问题?谢谢。

服务器代码:

#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment (lib, "Ws2_32.lib")
#define DEFAULT_PORT "27501"
#define SIZE 1024

int SendAllToClient(SOCKET ClientSocket, char *buffer)

    int iSendResult;


    int total = 0, len = 1024;
    int bytesleft = 1024;

    while( total < len )
    
        iSendResult = send( ClientSocket, buffer, 1024, NULL);

        if (iSendResult == SOCKET_ERROR)
        
            printf("send failed with error: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        
        total += iSendResult;
        bytesleft -= iSendResult;
    
    printf("Bytes sent: %d\n", iSendResult);

    return total<len?- 1: 1;


char *ReadFromClient(SOCKET ConnectSocket)

    int iResult;
    char *buffer = new char[1024];
    memset(buffer, 0, 1024);

    do
    
        iResult = recv(ConnectSocket, buffer, 1024, 0);
        if ( iResult > 0 )
            printf("Bytes received: %d\n", iResult);
        else if ( iResult == 0 )
            printf("Connection closed\n");

        else
            printf("recv failed with error: %d\n", WSAGetLastError());

    
    while( iResult > 0 );
    return buffer;


int main(int argc , char *argv[])

    WSADATA wsaData;
    char *buffer;

    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET ClientSocket = INVALID_SOCKET;

    struct addrinfo *result = NULL;
    struct addrinfo hints;

    int sessionID;
    int iResult;

   // Datagram d1,d2,d3;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0)
    
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // Resolve the server address and port
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if ( iResult != 0 )
    
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    

    // Create a SOCKET for connecting to server
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET)
    
        printf("socket failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    

    // Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR)
    
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    

    freeaddrinfo(result);

    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR)
    
        printf("listen failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    

    printf("Server is listening on localhost:%s ...\n", DEFAULT_PORT);

    // Accept a client socket
    SOCKADDR_IN addr;
    int addrlen = sizeof(addr);

    ClientSocket = accept(ListenSocket, (SOCKADDR*)&addr, &addrlen);
    if (ClientSocket == INVALID_SOCKET)
    
        printf("accept failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    

    char *ip = inet_ntoa(addr.sin_addr);
    int port = addr.sin_port;
    printf("\nClient %s:%d connected to server\n", ip, port);

    // No longer need server socket
    closesocket(ListenSocket);

    // shutdown the connection since we're done
    iResult = shutdown(ClientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR)
    
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    

    //read
    buffer = ReadFromClient(ClientSocket);
    printf("FROM CLIENT: %s\n", buffer);
    //send
    memset(buffer, 0, 1024);
    memcpy(buffer, "Hi client, how are you?", strlen("Hi client, how are you?"));
    SendAllToClient(ClientSocket, buffer);
    //read
    //send

    // cleanup
    closesocket(ClientSocket);
    WSACleanup();

    return 0;

客户端代码:

#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment (lib, "Ws2_32.lib")
#define DEFAULT_PORT "27501"
#define SIZE 1024

int SendAllToServer(SOCKET ServerSocket, char *buffer)

    int iSendResult;

    int total = 0, len = 1024;
    int bytesleft = 1024;

    while( total < len )
    
        iSendResult = send( ServerSocket, buffer, 1024, NULL);

        if (iSendResult == SOCKET_ERROR)
        
            printf("send failed with error: %d\n", WSAGetLastError());
            closesocket(ServerSocket);
            WSACleanup();
            return 1;
        
        total += iSendResult;
        bytesleft -= iSendResult;
    
    printf("Bytes sent: %d\n", iSendResult);

    return total<len?- 1: 1;


char *ReadFromServer(SOCKET ConnectSocket)

    int iResult;
    char *buffer = new char[1024];
    memset(buffer, 0, 1024);

    do 
        iResult = recv(ConnectSocket, buffer, 1024, 0);
        if ( iResult > 0 )
            printf("Bytes received: %d\n", iResult);
        else if ( iResult == 0 )
            printf("Connection closed\n");

        else
            printf("recv failed with error: %d\n", WSAGetLastError());

     while( iResult > 0 );
    return buffer;


int main(int argc , char *argv[])

    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo *result = NULL,
                    *ptr = NULL,
                    hints;
    char* buffer;
    int sessionID;
    int iResult;
   // Datagram d1,d2;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) 
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    

    ZeroMemory( &hints, sizeof(hints) );
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Resolve the server address and port
    iResult = getaddrinfo("127.0.0.1", DEFAULT_PORT, &hints, &result);
    if ( iResult != 0 ) 
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    

    // Attempt to connect to an address until one succeeds
    for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) 

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) 
            printf("socket failed with error: %d\n", WSAGetLastError());
            WSACleanup();
            return 1;
        

        // Connect to server.
        iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) 
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        
        break;
    

    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) 
        printf("Unable to connect to server!\n");
        WSACleanup();
        return 1;
    

    //send
    buffer = new char[1024];
    memset(buffer, 0, 1024);
    memcpy(buffer, "Hi server", strlen("Hi server"));
    SendAllToServer(ConnectSocket, buffer);
    //read
    memset(buffer, 0, 1024);
    buffer = ReadFromServer(ConnectSocket);
    printf("FROM SERVER: %s\n", buffer);
    //send
    //read

    // cleanup
    closesocket(ConnectSocket);
    WSACleanup();

    return 0;

编辑

服务器

#include "data_types.h"
#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <iostream>
#pragma comment (lib, "Ws2_32.lib")
#define DEFAULT_PORT "27015"
#define SIZE 1024

int SendAllToClient(SOCKET ClientSocket)

    char *buffer = new char[SIZE];
    int iSendResult;
    memset(buffer, 0, SIZE);

    int total = 0, len = SIZE;
    int bytesleft = SIZE;

    while( total < len )
    
        iSendResult = send( ClientSocket, buffer + total, bytesleft, NULL);

        if (iSendResult == SOCKET_ERROR)
        
            printf("send failed with error: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        
        total += iSendResult;
        bytesleft -= iSendResult;
    
    printf("Bytes sent: %d\n", iSendResult);
    delete buffer;

    return total<len?- 1: 1;


char* ReadFromClient(SOCKET ClientSocket)

    std::string data = "";
    char *all = NULL;
    char buffer[1];
    int total = 0, len = SIZE;
    int bytesleft = SIZE;
    int iResult;
    memset(buffer, 0, 1);

    while(total < len)
    
        if ((iResult = recv(ClientSocket, buffer, 1, 0)) == 0)
        
            if (errno != 0)
            
                // cleanup
                closesocket(ClientSocket);
                WSACleanup();
                exit(1);
            
        
        data = data + std::string(buffer);
        total += iResult;
        bytesleft -= iResult;
        memset(buffer, 0, 1);
    
    all = new char[data.length() + 1];
    strcpy(all, data.c_str());

    return all;


int main(int argc , char *argv[])

    WSADATA wsaData;
    char *buffer;

    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET ClientSocket = INVALID_SOCKET;

    struct addrinfo *result = NULL;
    struct addrinfo hints;

    int sessionID;
    int iResult;

   // Datagram d1,d2,d3;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0)
    
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // Resolve the server address and port
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if ( iResult != 0 )
    
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    

    // Create a SOCKET for connecting to server
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET)
    
        printf("socket failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    

    // Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR)
    
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    

    freeaddrinfo(result);

    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR)
    
        printf("listen failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    

    printf("Server is listening on localhost:%s ...\n", DEFAULT_PORT);

    // Accept a client socket
    SOCKADDR_IN addr;
    int addrlen = sizeof(addr);

    ClientSocket = accept(ListenSocket, (SOCKADDR*)&addr, &addrlen);
    if (ClientSocket == INVALID_SOCKET)
    
        printf("accept failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    

    char *ip = inet_ntoa(addr.sin_addr);
    int port = addr.sin_port;
    printf("\nClient %s:%d connected to server\n", ip, port);

    // No longer need server socket
    closesocket(ListenSocket);

    // shutdown the connection since we're done
    iResult = shutdown(ClientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR)
    
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    

    //read
    buffer = new char[1024];
    memset(buffer, 0, 1024);
    buffer = ReadFromClient(ClientSocket);
    printf("FROM CLIENT: %s\n", buffer);

    //send
    memset(buffer, 0, 1024);
    memcpy(buffer, "Hi client, how are you?", strlen("Hi client, how are you?"));
    // Send an initial buffer
    iResult = send( ClientSocket, buffer, (int)strlen(buffer), 0 );
    if (iResult == SOCKET_ERROR) 
        wprintf(L"send failed with error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    
    //read
    //send

    // cleanup
    closesocket(ClientSocket);
    WSACleanup();

    return 0;

客户

#include "data_types.h"
#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <iostream>
#pragma comment (lib, "Ws2_32.lib")
#define DEFAULT_PORT "27015"
#define SIZE 1024

int SendAllToServer(SOCKET ClientSocket)

    char *buffer = new char[SIZE];
    memset(buffer, 0, SIZE);

    int total = 0, len = SIZE;
    int bytesleft = SIZE, iSendResult;

    while( total < len )
    
        iSendResult = send( ClientSocket, buffer + total, bytesleft, NULL);

        if (iSendResult == SOCKET_ERROR)
        
            printf("send failed with error: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        
        total += iSendResult;
        bytesleft -= iSendResult;
    
    printf("Bytes sent: %d\n", iSendResult);
    delete buffer;

    return total<len?- 1: 1;


char *ReadFromServer(SOCKET ClientSocket)

    std::string data = "";
    char *all = NULL;
    char buffer[1];
    int total = 0, len = SIZE;
    int bytesleft = SIZE;
    int iResult;
    memset(buffer, 0, 1);

    while(total < len)
    
        if ((iResult = recv(ClientSocket, buffer, 1, 0)) == 0)
        
            if (errno != 0)
            
                // cleanup
                closesocket(ClientSocket);
                WSACleanup();
                exit(1);
            
        
        data = data + std::string(buffer);
        total += iResult;
        bytesleft -= iResult;
        memset(buffer, 0, 1);
    
    all = new char[data.length() + 1];
    strcpy(all, data.c_str());

    return all;


int main(int argc , char *argv[])

    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo *result = NULL,
                    *ptr = NULL,
                    hints;
    char* buffer;
    int sessionID;
    int iResult;
   // Datagram d1,d2;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) 
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    

    ZeroMemory( &hints, sizeof(hints) );
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Resolve the server address and port
    iResult = getaddrinfo("127.0.0.1", DEFAULT_PORT, &hints, &result);
    if ( iResult != 0 ) 
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    

    // Attempt to connect to an address until one succeeds
    for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) 

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) 
            printf("socket failed with error: %d\n", WSAGetLastError());
            WSACleanup();
            return 1;
        

        // Connect to server.
        iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) 
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        
        break;
    

    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) 
        printf("Unable to connect to server!\n");
        WSACleanup();
        return 1;
    

    //send
    buffer = new char[1024];
    memset(buffer, 0, 1024);
    memcpy(buffer, "Hi server, how are you?", strlen("Hi server, how are you?"));
    // Send an initial buffer
    iResult = send( ConnectSocket, buffer, (int)strlen(buffer), 0 );
    if (iResult == SOCKET_ERROR) 
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    
    //read
    memset(buffer, 0, 1024);
    // Receive until the peer closes the connection
    do 

        iResult = recv(ConnectSocket, buffer, 1024, 0);
        if ( iResult > 0 )
            wprintf(L"Bytes received: %d\n", iResult);
        else if ( iResult == 0 )
            wprintf(L"Connection closed\n");
        else
            wprintf(L"recv failed with error: %d\n", WSAGetLastError());

     while( iResult > 0 );
    printf("FROM SERVER: %s\n", buffer);
    //send
    //read

    // cleanup
    closesocket(ConnectSocket);
    WSACleanup();

    return 0;

【问题讨论】:

您能否将您的手指指向您声称的应该在发送和接收之间循环的实际代码行?在任何地方都找不到这样的代码行。此外,在处理部分发送和接收方面,发送和接收功能都被完全破坏了。 ReadFromServer 未能完全实现。 SendAllToServer 努力更新totalbytesleft,但未能更新buffer,因此部分发送的消息将导致它再次重新发送消息的开头。您也需要解决这些问题。 最后,socket的两边都由同一个进程处理。尽管您的操作系统套接字缓冲区很可能足够大,可以在内部缓冲 1024 字节,但您无法保证这种效果,因此显示的代码最终可能会自行死锁。 @SamVarshavchik:但是,如果这些功能能够正确实现,是否有可能实现这样的通信? 当然,在 C++ 中一切皆有可能。 ***.com 并不是一个真正的 C++ 教程网站,但这就是你做这件事的方式。只需拿出一张白纸。用简单的英语用简短、简单的句子写下一个逐步的过程。完成后,call your rubber duck for an appointment。在您的橡皮鸭批准您提出的行动计划后,只需将您写下的内容直接翻译成 C++。任务完成! 【参考方案1】:

第一期:

    iSendResult = send( ClientSocket, buffer, 1024, NULL);

那个 1024 应该是bytesleft。如果您已经读取了 512 个字节,那么您不想再读取 512 个字节。

buffer 应该是 buffer + total。如果您已经发送了 512 字节,您不想再次发送相同的 512 字节,您想发送 其他 512 字节。

第二期:

您的ReadFromServer 函数完全被破坏并且没有遵循正确的逻辑。它只会返回致命错误,甚至不会尝试准确读取 1,024 个字节。它最多只能读取 1,024 个字节,然后,无论它实际读取了多少字节,都会尝试再次读取 1,024 个字节——并在同一地址覆盖它已经读取的消息的任何部分!

它应该像 send 函数一样工作,首先尝试接收 1,024 字节,如果接收不到,则循环,直到它恰好接收到 1,024 字节或出现致命错误。

第三期:

    buffer = ReadFromServer(ConnectSocket);
    printf("FROM SERVER: %s\n", buffer);

不要这样做。假设服务器是恶意的,并向您发送了 1,024 个字节,这些字节不是合法的 C 样式字符串。通过%s 将其传递给printf 可能会导致客户端崩溃或行为不端。始终将接收自网络的数据视为不受信任且可能具有恶意。虽然你的代码不修复这个问题就可以工作,但有一天这样做会以某种可怕的方式咬你,这不是一个好习惯。

您还应该在使用完缓冲区后delete[]。但是你为什么要返回一个原始指针呢?您可以返回 std::stringstd::vector 或许多其他更好的机制来避免泄漏风险并确保副本安全。

【讨论】:

谢谢你。我仍然有问题。你能看看吗?我将代码添加到原始问题 你还有什么问题?我注意到你有一个delete,你应该有一个delete[],而且你没有正确处理发送或接收返回零。 哦,你也这样做:data = data + std::string(buffer); 这不可能。它怎么知道要添加多少字节? (考虑如果一个调用接收到 40 个字节,而下一个调用接收到 10 个字节。第二次接收到 10 个字节末尾的零字节如何?)【参考方案2】:

事实上你正在关闭监听套接字,所以一旦有一个接受的连接它就不会再接受了:

ClientSocket = accept(ListenSocket, (SOCKADDR*)&addr, &addrlen);
if (ClientSocket == INVALID_SOCKET)

    printf("accept failed with error: %d\n", WSAGetLastError());
    closesocket(ListenSocket);
    WSACleanup();
    return 1;


char *ip = inet_ntoa(addr.sin_addr);
int port = addr.sin_port;
printf("\nClient %s:%d connected to server\n", ip, port);

// No longer need server socket
closesocket(ListenSocket);

关闭侦听器会阻止进一步的连接。 此外,您在处理之前关闭客户端套接字:

// shutdown the connection since we're done
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR)

    printf("shutdown failed with error: %d\n", WSAGetLastError());
    closesocket(ClientSocket);
    WSACleanup();
    return 1;


//read
buffer = new char[1024];
memset(buffer, 0, 1024);
buffer = ReadFromClient(ClientSocket);
printf("FROM CLIENT: %s\n", buffer);

//send
memset(buffer, 0, 1024);
memcpy(buffer, "Hi client, how are you?", strlen("Hi client, how are you?"));
// Send an initial buffer
iResult = send( ClientSocket, buffer, (int)strlen(buffer), 0 );

如果你想多次发送和接收,你应该在一个循环中读/写。你只做了一次(而且做错了):所以只有你在问题中描述的一条消息。

【讨论】:

以上是关于Winsock2,客户端-服务器通信——轮流发送/接收的主要内容,如果未能解决你的问题,请参考以下文章

winsock 客户端和服务器通信

客户端服务器通信c ++中收到的垃圾值

WinSock2的简单通信例子

WebSocket客户端和WinSock2服务器,可以吗?

winsock2:服务器端代码调用`accept()`后如何获取已连接客户端的ipv4/ipv6地址

C ++ Winsock发送文件[关闭]