tcp 服务器的奇怪行为(使用 winsock)

Posted

技术标签:

【中文标题】tcp 服务器的奇怪行为(使用 winsock)【英文标题】:weird behavior of tcp server (using winsock) 【发布时间】:2015-03-02 03:06:06 【问题描述】:

我正在为 tcp 服务器使用 winsock 和 c++ 11 线程。对于每个客户端,我创建一个新的 ReceiveThread 对象,它有一个 std::thread 对象。 Tcp 客户端是 Java 语言。我想创建一个简单的广播功能。 (如果有人发送消息,则服务器将其转发给每个人)。 我为客户端套接字使用了一个包装类,其中包括一个互斥锁。 (同步的 unordered_map)。每条消息都是结构化的。第一个字节是消息的长度,第二个字节表示类型,然后是实际数据。 (数据长度已知,第一个字节就是那个)

[编辑] 我现有的代码适用于一个客户。当第二个客户端连接时,他也可以发送消息并且两个客户端都可以得到它。 但是如果我用第一个客户端发送消息,服务器会在第二个线程上接收它(顺便说一句,消息正确到达),它属于第二个客户端。在此之后,服务器不会从第一个客户端接收任何内容。 (我去掉了'send forward to everyone'部分,因为问题出现在接收部分,我也编辑了void ReceiveThread::receive(),现在我只调用一次,稍后处理)

Server.cpp

#include "Server.h"
#include <thread>
#include <string>
#include <winsock2.h>
#include <iostream>
#include "ReceiveThread.h"


using namespace std;

Server::Server(string ip, int port):ip(ip),port(port)
    init();


int Server::init()

    //init the winsock library
    WSADATA wsaData;
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != NO_ERROR)
        cout << "Error WSAStartup!";
        return -1;
    

    // Create a SOCKET for listening for incoming connection requests.
    listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listenSocket == INVALID_SOCKET) 
        cout << "Error at socket(): " << WSAGetLastError();
        WSACleanup();
        return -1;
    

    //----------------------
    // The sockaddr_in structure specifies the address family,
    // IP address, and port for the socket that is being bound.
    sockaddr_in service;
    service.sin_family = AF_INET;
    service.sin_addr.s_addr = inet_addr(ip.c_str());
    service.sin_port = htons(port);

    if ( ::bind(listenSocket, (SOCKADDR*)&service, sizeof(service)) == SOCKET_ERROR) 
        cout << "Bind error.";
        closesocket(listenSocket);
        WSACleanup();
        return -1;
    

    //----------------------
    // Listen for incoming connection requests.
    // on the created socket
    if (listen(listenSocket, 1) == SOCKET_ERROR) 
        cout << "Error listening on socket.\n";
        closesocket(listenSocket);
        WSACleanup();
        return -1;
    

    // Accept connections
    acceptConnections();


void Server::acceptConnections()
    // Create a SOCKET for accepting incoming request.
    SOCKET acceptSocket;
    cout << "Waiting for clients.";
    int counter = 0;
    while (1)
        acceptSocket = accept(listenSocket, NULL, NULL);
        if (acceptSocket == INVALID_SOCKET)
            cout << "Accept error";
            closesocket(listenSocket);
            WSACleanup();
            return;
        
        else
            clientSockets.add(acceptSocket);
            cout << "Client connected.";
            // create a new receive thread object for every client
            counter++;
            ReceiveThread receiveThread(clientSockets, acceptSocket,counter);
        
    

ReceiveThread.cpp

#include "ReceiveThread.h"
#include <winsock2.h>
#include "Message.h"
#include <iostream>
#include <string>

using namespace std;

ReceiveThread::ReceiveThread(ClientSockList &clients, SOCKET &socket,int counter) :clients(clients), socket(socket),counter(counter)
    //cout << clients.getList().size();
    receiveThread = new thread(&ReceiveThread::receive, this);


void ReceiveThread::terminateThread()
    terminated = true;

void ReceiveThread::receive()
    int res;
    while (!terminated)

        char recvbuf[BUF_SIZE]; // BU_SIZE = 1024
        int recv_len = 0;
        res = recv(socket, recvbuf + recv_len, BUF_SIZE - recv_len, 0);
        if (!checkSocket(res)) break;
        cout << "[" << counter << "] ";
        for (int i = 0; i < res; ++i)
            cout << recvbuf[i];
        
        cout << endl;
    
    //delete receiveThread;


bool ReceiveThread::checkSocket(int res)
    if (res == SOCKET_ERROR || res == 0)
        terminated = true;
        cout << endl << "Terminated" << endl;
        clients.remove(socket);
        closesocket(socket);
        return false;
    
    else
        return true;
    

这是我从客户端发送消息的方式:

public void sendMessageForBroadcast(String message) throws IOException 
    //String m = buildMessage(message,Message.TYPE_BROADCAST);
    StringBuffer buff = new StringBuffer();
    buff.append(Character.toChars(message.length()));
    buff.append(Character.toChars(1));
    buff.append(message);

    //System.out.println("Sending message: " + m + "["+m.length()+"]");
    outputStream.write(buff.toString().getBytes("UTF8"));
    outputStream.flush();

[编辑]场景:

    与client1连接 用client1发送消息 在服务器上接收消息(线程 1) 与client2连接 用client2发送消息 在服务器上接收消息(线程 2) 用client1发送消息 在服务器 (线程 2)上接收消息 从现在起,服务器不会收到来自 client1 的任何内容

【问题讨论】:

要阅读的代码相当多,请尝试使用调试器缩小范围。当代码似乎被锁定时,使用调试器将其中断以查看锁定的位置,如果它处于阻塞函数或无限循环中。 注意:我没有看到任何迹象表明Message 是三规则安全的。而且您确实意识到您的ReceiveThread receiveThread(clientSockets, acceptSocket,counter); 逻辑通过引用一个对象(this)的成员函数调用未定义的行为,该对象一旦超出范围(即在构造之后立即)就已被销毁。在没有中间测试和验证的情况下,这是一个相当大数量的代码块。我强烈建议您后退六步,重新考虑您真正想要做什么。 我刚刚编辑了内容。我删除了Message 类,并编辑了ReceiveThread::receive() 方法。只剩下最少的代码,问题仍然存在。顺便说一句,我怎样才能避免这种未定义的行为?或者我怎样才能以更好的方式启动线程?整体设计不好吗?能给点建议吗? 【参考方案1】:

你有一个很严重的undefined behavior

一切都从这一行开始:

ReceiveThread receiveThread(clientSockets, acceptSocket,counter);

这会创建一个ReceiveThread 对象(其构造函数会创建引用this 的线程)。问题是,一旦进行了声明,声明变量的代码块就结束了,这使得变量超出范围并破坏了对象。

一旦对象被销毁,任何取消引用先前对象this 指针的代码(即所有使用对象非静态成员变量或函数的代码)都将取消引用被销毁对象的指针,从而导致所述未定义行为.

我建议你保留一个指向线程对象的指针集合,既可以将对象保持在范围内,直到它需要被销毁,也可以保留对对象的引用,如果你以后需要从其他线程使用它。


还有另一个可能的未定义行为来源,因为我没有看到你初始化成员变量terminated,这意味着当你在线程中引用它时它的值将是不确定的。 p>

【讨论】:

以上是关于tcp 服务器的奇怪行为(使用 winsock)的主要内容,如果未能解决你的问题,请参考以下文章

windows下在非阻塞TCP套接字上使用SO_SNDBUF的奇怪行为

VB6 Winsock 多个 TCP 连接 > DoEvents 问题

WinSock TCP 长连接(while循环)

Winsock - 10038 错误 - Win2K3 服务器 - 令人费解的行为

Winsock2 tcp/ip - 一些数据包被忽略可能是由于前一个数据包的空终止符

winsock控件的TCP和UDP协议的判断问题