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 - 10038 错误 - Win2K3 服务器 - 令人费解的行为