I’m trying to create an IOCP TCP client and my code looks as follows:
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;
}
My use of “m_pPerIoData” is clearly wrong because I’m newing every time I do SendCommand() and not deleting it properly.
- Q1. Where should I do m_pPerIoData = new PER_IO_DATA?
- Q2. Does it make sense to have a pointer to PER_IO_DATA as a member variable?
Edit 2:
I’ve done some renaming (Client -> Connection) to my existing code above because it was confusing me.
Some background:
- I’m creating a DLL that is used to control an ECR (Electronic Cash Register) device connected via LAN (or serial port).
- The DLL provides an easy-to-use interfaces such as Connect(), Disconnect() and also some ECR specific commands including Logon(), Logoff(), ReadCard() etc.
- (Probably overkill for my application but…) I’d like to make use of IOCP in my DLL to send/receive data to/from ECR asynchronously.
My top level class looks something like this:
#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;
};
And I’ve got TcpConnection class which is created by the ECR class and does all the hard work. Both Ecr and Connection classes are uncopyable.
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;
}
A host application then creates a DLL instance per ECR device:
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;
I want all the IOCP related stuff at the TcpConnection level, not the Ecr level because I don’t want Ecr to care how data transmission is done underneath.
Is my idea not going to work?
What you showed is based on the IOCP code I gave you in another question. However, that code was designed to re-use a single
PER_IO_DATAinstance across multiple IOCP operations on the same socket, because you were only reading in the client and only writing in the server. Now you are mixing both reading and writing in the client, so you need to adjust the worker thread to support both types of IOCP operations by merging all of the code I gave you earlier, at then add an additional flag in thePER_IO_DATAso a completed operation knows whether it was a read operation or a write operation.As for your questions:
It doesn’t make much sense to use a class-wide
m_pPerIoDatamember at all.SendCommand()is creating a new PER_IO_DATA, which is fine, you would just need todeleteit when a worker thread has detected that all ofPER_IO_DATA::Bufferdata has finished being sent or the socket has been closed.Not for the write operations, no. You could have a single class member for reading, though. Unless
SendCommand()creates a newPER_IO_DATAwhen it is ready to read a single response. The worker thread would then need todeleteit when the full response has been received or the socket has been closed.