1

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() を実行するたびに新しくなり、適切に削除していないからです。

  • Q1. m_pPerIoData = new PER_IO_DATA はどこで実行すればよいですか?
  • Q2. メンバ変数として PER_IO_DATA へのポインタを持つことは理にかなっていますか?

編集2:

混乱していたので、上記の既存のコードの名前を変更しました (クライアント -> 接続)。

背景:

  • LAN (またはシリアル ポート) 経由で接続された ECR (電子キャッシュ レジスタ) デバイスを制御するために使用される DLL を作成しています。
  • DLL は、Connect()、Disconnect() などの使いやすいインターフェイスと、Logon()、Logoff()、ReadCard() などの ECR 固有のコマンドを提供します。
  • (おそらく私のアプリケーションではやり過ぎですが...) 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;
};

そして、ECR クラスによって作成され、すべてのハードワークを行う TcpConnection クラスを取得しました。Ecr クラスと 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;

Ecr が下でデータ転送がどのように行われるかを気にしたくないので、Ecr レベルではなく、TcpConnection レベルですべての IOCP 関連のものを必要とします。

私の考えはうまくいきませんか?

4

2 に答える 2

3

I/O ごとのデータは、問題の I/O 操作の存続期間中存在する必要があります。したがって、おそらく動的に割り当て、完了したら再利用するためにプールする必要があります。私は参照カウント システムを使用していますが、これは、I/O ごとのデータを 1 回の I/O 操作よりも長く維持できるためです。I/O 固有のデータの有効期間は次のとおりです。

  • I / O操作を発行する前に割り当てます(明らかに)
  • 操作が正常に完了したとき、またはエラーが発生したときに解放します。

I/O操作によって「所有」されているため、I/Oごとをデータメンバーとして持つことは本当に意味がありません...これらのオブジェクトを割り当てることができ、 I/O 操作が完了したら、それらの所有権を取得します (再利用のためにプールします)。

これを行う (そしてスケーラブルな方法で物事のスレッド化側を処理する) IOCP コードをいくつか見てみたいと思うかもしれません。こちらを参照してください。

于 2012-07-04T06:26:36.503 に答える
2

あなたが示したものは、私が別の質問であなたに与えた IOCP コードに基づいています。PER_IO_DATAただし、そのコードは、同じソケットでの複数の IOCP 操作で単一のインスタンスを再利用するように設計されています。これは、クライアントで読み取りのみを行い、サーバーで書き込みのみを行うためです。クライアントで読み取りと書き込みの両方を混在させているため、ワーカー スレッドを調整して、前に提供したすべてのコードをマージし、両方のタイプの IOCP 操作をサポートする必要がありますPER_IO_DATA。読み取り操作か書き込み操作かがわかります。

あなたの質問について:

m_pPerIoData = new PER_IO_DATA はどこで実行すればよいですか?

m_pPerIoDataクラス全体のメンバー を使用してもあまり意味がありません。SendCommand()新しい PER_IO_DATA を作成していますが、これは問題ありません。すべてのデータの送信が終了したこと、またはソケットが閉じられたdeleteことをワーカー スレッドが検出したときに必要になります。PER_IO_DATA::Buffer

メンバ変数として PER_IO_DATA へのポインタを持つことは理にかなっていますか?

書き込み操作ではありません。ただし、読み取り用のクラス メンバーを 1 つ持つこともできます。単一の応答を読み取る準備ができたときにSendCommand()新しいを作成しない限り。PER_IO_DATAワーカー スレッドはdelete、完全な応答が受信されたとき、またはソケットが閉じられたときにそれを必要とします。

于 2012-07-03T21:37:31.027 に答える