【发布时间】:2012-07-03 11:34:50
【问题描述】:
我正在尝试创建一个 IOCP TCP 客户端,我的代码如下所示:
TCPClient.h:
#pragma once
typedef struct
{
WSAOVERLAPPED Overlapped;
SOCKET Socket;
WSABUF wsaBuf;
char Buffer[1024];
DWORD Flags;
DWORD BytesSent;
DWORD BytesToSend;
} PER_IO_DATA, * LPPER_IO_DATA;
class TCPClient
{
public:
TCPClient();
~TCPClient();
bool Connect(const std::string strIpAddress, UINT32 uPort);
bool Disconnect();
bool SendCommand(const std::string strCommandName);
bool ReceiveResponse();
private:
static DWORD WINAPI ClientWorkerThread(LPVOID lpParameter);
private:
SOCKET m_socket;
PER_IO_DATA *m_pPerIoData;
};
TCPClient.cpp:
#include "StdAfx.h"
#include "TCPClient.h"
TCPClient::TCPClient() :
m_pPerIoData(NULL)
{
}
TCPClient::~TCPClient()
{
}
bool TCPClient::Connect(const std::string strIpAddress, UINT32 uPort)
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR)
return false;
HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (!hCompletionPort)
return false;
SYSTEM_INFO systemInfo;
GetSystemInfo(&systemInfo);
for (DWORD dwIndex = 0; dwIndex < systemInfo.dwNumberOfProcessors; dwIndex++)
{
HANDLE hThread = CreateThread(NULL, 0, ClientWorkerThread, hCompletionPort, 0, NULL);
CloseHandle(hThread);
}
m_socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (m_socket == INVALID_SOCKET)
{
WSACleanup();
return false;
}
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(strIpAddress.c_str());
server.sin_port = htons(uPort);
CreateIoCompletionPort((HANDLE)m_socket, hCompletionPort, 0, 0);
if (WSAConnect(m_socket, (LPSOCKADDR)&server, sizeof(server), NULL, NULL, NULL, NULL) == SOCKET_ERROR)
{
WSACleanup();
return false;
}
return true;
}
bool TCPClient::Disconnect()
{
if (m_socket)
closesocket(m_socket);
WSACleanup();
return true;
}
bool TCPClient::SendCommand(const std::string strCommandName)
{
m_pPerIoData = new PER_IO_DATA;
ZeroMemory(m_pPerIoData, sizeof(PER_IO_DATA));
strcpy(m_pPerIoData->Buffer, strCommandName.c_str());
m_pPerIoData->Overlapped.hEvent = WSACreateEvent();
m_pPerIoData->Socket = m_socket;
m_pPerIoData->wsaBuf.buf = m_pPerIoData->Buffer;
m_pPerIoData->wsaBuf.len = strlen(m_pPerIoData->Buffer);
m_pPerIoData->BytesToSend = m_pPerIoData->wsaBuf.len;
DWORD dwNumSent;
if (WSASend(m_socket, &(m_pPerIoData->wsaBuf), 1, &dwNumSent, 0, &(m_pPerIoData->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
delete m_pPerIoData;
return 0;
}
}
while (TRUE)
Sleep(1000);
return true;
}
bool TCPClient::ReceiveResponse()
{
return true;
}
DWORD WINAPI TCPClient::ClientWorkerThread(LPVOID lpParameter)
{
HANDLE hCompletionPort = (HANDLE)lpParameter;
DWORD NumBytesRecv = 0;
ULONG CompletionKey;
LPPER_IO_DATA PerIoData;
while (GetQueuedCompletionStatus(hCompletionPort, &NumBytesRecv, &CompletionKey, (LPOVERLAPPED*)&PerIoData, INFINITE))
{
if (!PerIoData)
continue;
if (NumBytesRecv == 0)
{
std::cout << "Server disconnected!\r\n\r\n";
}
else
{
// use PerIoData->Buffer as needed...
std::cout << std::string(PerIoData->Buffer, NumBytesRecv);
PerIoData->wsaBuf.len = sizeof(PerIoData->Buffer);
PerIoData->Flags = 0;
if (WSARecv(PerIoData->Socket, &(PerIoData->wsaBuf), 1, &NumBytesRecv, &(PerIoData->Flags), &(PerIoData->Overlapped), NULL) == 0)
continue;
if (WSAGetLastError() == WSA_IO_PENDING)
continue;
}
closesocket(PerIoData->Socket);
delete PerIoData;
}
return 0;
}
main.cpp:
#include "stdafx.h"
#include "TCPClient.h"
int main()
{
TCPClient client;
client.Connect("127.0.0.1", 8888);
client.SendCommand("Hello command\r\n");
return 0;
}
我对“m_pPerIoData”的使用显然是错误的,因为我每次执行 SendCommand() 时都在更新,并且没有正确删除它。
- 第一季度。我应该在哪里做 m_pPerIoData = new PER_IO_DATA?
- 第二季度。将指向 PER_IO_DATA 的指针作为成员变量有意义吗?
编辑 2:
我已经对上面的现有代码进行了一些重命名(客户端 -> 连接),因为这让我感到困惑。
一些背景:
- 我正在创建一个 DLL,用于控制通过 LAN(或串行端口)连接的 ECR(电子收银机)设备。
- DLL 提供易于使用的接口,例如 Connect()、Disconnect() 以及一些 ECR 特定命令,包括 Logon()、Logoff()、ReadCard() 等。
- (可能对我的应用程序来说有点矫枉过正,但是...)我想在我的 DLL 中使用 IOCP 来异步向 ECR 发送/接收数据。
我的顶级课程看起来像这样:
#pragma once
#include "Connection.h"
#include "Uncopyable.h"
#include "ConnectionFactory.h"
#include "CommandName.h"
class Ecr : private Uncopyable
{
public:
Ecr(const std::string& rstrConnectionInfo)
: m_spConnection(ConnectionFactory::CreateConnection(rstrConnectionInfo))
{
//Initialise();
}
~Ecr()
{
//Shutdown();
}
bool Initialise()
{
if (!m_spConnection)
return false;
m_spConnection->Initialise();
return true;
}
bool Shutdown()
{
if (!m_spConnection)
return false;
m_spConnection->Shutdown();
return true;
}
bool Connect()
{
if (!m_spConnection)
return false;
if (!m_spConnection->Connect())
return false;
return true;
}
bool Disconnect()
{
m_spConnection->Disconnect();
return true;
}
bool Logon(const std::vector<BYTE>& rvecCommandOptions)
{
m_spConnection->SendCommand(CommandName::Logon(), rvecCommandOptions);
return true;
}
bool Logoff()
{
m_spConnection->SendCommand(CommandName::Logoff());
return true;
}
// ... more commands follow.
private:
ConnectionPtr m_spConnection;
};
我有 TcpConnection 类,它是由 ECR 类创建的,可以完成所有的艰苦工作。 Ecr 和 Connection 类都是不可复制的。
Connection.h:
#pragma once
#include "Uncopyable.h"
class CommandName;
class Connection : private Uncopyable
{
public:
Connection(const std::string& rstrConnectionInfo);
virtual ~Connection();
virtual bool Initialise() = 0;
virtual bool Shutdown() = 0;
virtual bool Connect() = 0;
virtual bool Disconnect() = 0;
virtual bool SendCommand(const CommandName& rCommandName, const std::vector<BYTE>& rvecCommandOptions) = 0;
virtual bool ReceiveResponse() = 0;
bool SendCommand(const CommandName& rCommandName);
private:
std::string m_strConnectionInfo;
};
typedef std::tr1::shared_ptr<Connection> ConnectionPtr;
TcpConnection.h:
#pragma once
#include "connection.h"
class TcpConnection : public Connection
{
public:
TcpConnection(const std::string& rstrConnectionInfo);
~TcpConnection();
// Connection
bool Initialise();
bool Shutdown();
bool Connect();
bool Disconnect();
bool SendCommand(const CommandName& rCommandName, const std::vector<BYTE>& rvecCommandOptions);
bool ReceiveResponse();
static DWORD WINAPI WorkerThread(LPVOID lpParam);
private:
SOCKET m_socket;
HANDLE m_hIocp;
};
TcpConnection.cpp:
#include "StdAfx.h"
#include "TcpConnection.h"
#include "CommandBuilderTcp.h"
TcpConnection::TcpConnection(const std::string& rstrConnectionInfo)
: Connection(rstrConnectionInfo)
, m_socket(INVALID_SOCKET)
, m_hIocp(INVALID_HANDLE_VALUE)
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
m_socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
}
TcpConnection::~TcpConnection()
{
}
bool TcpConnection::Initialise()
{
// Set up threads for using IOCP.
m_hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
SYSTEM_INFO systemInfo;
GetSystemInfo(&systemInfo);
for (DWORD dwIndex = 0; dwIndex < systemInfo.dwNumberOfProcessors; dwIndex++)
{
HANDLE hThread = CreateThread(NULL, 0, WorkerThread, m_hIocp, 0, NULL);
CloseHandle(hThread);
}
CreateIoCompletionPort((HANDLE)m_socket, m_hIocp, 0, 0);
return true;
}
bool TcpConnection::Shutdown()
{
// Release threads.
return true;
}
bool TcpConnection::Connect()
{
if (m_socket)
return true;
// Hard-coding IP address and port number for now.
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("10.0.9.80");
server.sin_port = htons(22000);
WSAConnect(m_socket, (LPSOCKADDR)&server, sizeof(server), NULL, NULL, NULL, NULL);
return true;
}
bool TcpConnection::Disconnect()
{
if (m_socket)
closesocket(m_socket);
WSACleanup();
return true;
}
bool TcpConnection::SendCommand(const CommandName& rCommandName, const std::vector<BYTE>& rvecCommandOptions)
{
// Build full command from rCommandName and rvecCommandOptions and send to server.
return true;
}
bool TcpConnection::ReceiveResponse()
{
return true;
}
DWORD WINAPI TcpConnection::WorkerThread(LPVOID lpParam)
{
// Call GetQueuedCompletionStatus in a loop
return 0;
}
然后主机应用程序为每个 ECR 设备创建一个 DLL 实例:
Ecr ecr("ipaddress+port");
ecr.Initialise(); // Or do this in Ecr's ctor?
ecr.Connect();
BYTE arrCommandOptions[] = {0x00, 0x00, 0x00, 0x01, 0x18, 0xA6, 0x00, 0x01, 0x49, 0x08, 0x26};
std::vector<BYTE> vecCommandOptions(arrCommandOptions, arrCommandOptions + sizeof(arrCommandOptions) / sizeof(arrCommandOptions[0]));
ecr.Logon(vecCommandOptions);
ecr.Logoff();
ecr.Disconnect();
ecr.Shutdown(); // Or do this in Ecr's dtor?
return 0;
我想要 TcpConnection 级别的所有 IOCP 相关内容,而不是 Ecr 级别,因为我不希望 Ecr 关心数据传输是如何在下面完成的。
我的想法行不通吗?
【问题讨论】:
-
您正在为每个 CPU为每个客户端创建一个线程??这是非常低效和不方便的。如果你真的想要一个并行服务器,创建与 CPU 一样多的工作线程(全局,not 为每个客户端),而不是让它们处理 any 客户端的事件,通过单个完成端口。如果您不需要并行性,请使用单线程,您的生活会更加轻松。另外,请阅读msdn.microsoft.com/en-us/library/aa365198(VS.85).aspx
-
感谢您的回复。 RE:“使用单线程,你的生活会更轻松。”我认为 IOCP 需要创建工作线程。不是这样吗?
-
IOCP 不需要线程,但线程确实有帮助。您只是没有有效地使用线程。
-
@RemyLebeau 线程总是使开发更难,而不是更容易。只有一个线程,您不必担心竞争条件和死锁等问题。
-
当然,如果您不希望代码向上扩展。在单个线程中使用循环可以处理中等数量的连接,但迟早您将不得不引入更多线程来处理更高的连接数。