如何实现recv()回调

时间:2014-03-25 15:53:48

标签: c++ sockets winsock2

我正在努力提高我对OOP的了解,并决定创建一个简单的类来简化套接字编程。 这是一个学习实验,因此我不想使用boost或其他库。

我想实现一个事件驱动的 recv()。意思是,每当有新数据进入时,都应该调用我的函数。

我想我需要创建一个线程来运行一个recv()循环,然后每当有新数据时调用我的函数。还有其他方法可以使用线程吗?我希望我的代码可以移植。

这是我的简单类和示例代码:

class.h:

#ifndef _SOCKETSCLASS_H
#define _SOCKETSCLASS_H

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
    #define W32
    #include <WinSock2.h>
    #pragma comment(lib, "ws2_32.lib")
#else
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netdb.h>

    #define SOCKET int
#endif
#include <string>
#include<ctime>
#include <stdio.h>
#include <stdarg.h>
#include <varargs.h>
#include <tchar.h>

using namespace std;

#ifdef _DEBUG
    #define DEBUG(msg) XTrace(msg)
#else
    #define DEBUG(msg, params)
#endif

struct TCP_Client_opts
{
    BOOL    UseSCprotocol;
    BOOL    UseEncryption;
    BOOL    UseCompression;
    int     CompressionLevel;
    void    *Callback;
    BOOL    async;
};

struct TCP_Stats
{
    unsigned long int   upload; //bytes
    unsigned long int   download;//bytes
    time_t              uptime; //seconds
};

class TCP_Client
{
    public:
        TCP_Client();
        TCP_Client(TCP_Client_opts opts_set);
        ~TCP_Client();
        SOCKET          GetSocket();
        void            SetOptions(TCP_Client_opts opts_set);
        TCP_Client_opts GetOptions();
        BOOL            Connect(string server, int port);
        int             Send(string data);
        int             Recv(string *data);
        BOOL            IsConnected();
        int             Disconnect();
        TCP_Stats       GetStats();
    private:
        SOCKET          s = SOCKET_ERROR;
        TCP_Client_opts opts;
        TCP_Stats       stats;
        BOOL            connected = FALSE;
        time_t          starttime;
};
#endif

class.cpp:

#include "SocketsClass.h"

void XTrace(LPCTSTR lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);
    int nBuf;
    TCHAR szBuffer[512]; // get rid of this hard-coded buffer
    nBuf = _vsnwprintf_s(szBuffer, 511, lpszFormat, args);
    ::OutputDebugString(szBuffer);
    va_end(args);
}


TCP_Client::TCP_Client(TCP_Client_opts opts_set)
{
    SetOptions(opts_set);
}

TCP_Client::~TCP_Client()
{
    Disconnect();
}

TCP_Client::TCP_Client()
{
}

void TCP_Client::SetOptions(TCP_Client_opts opts_set)
{
    opts = opts_set;
}

TCP_Client_opts TCP_Client::GetOptions()
{
    return opts;
}

SOCKET TCP_Client::GetSocket()
{
    return s;
}

BOOL TCP_Client::IsConnected()
{
    return connected;
}

int TCP_Client::Disconnect()
{
    connected = FALSE;
    stats.uptime = time(0) - starttime;
    return shutdown(s, 2);
}

BOOL TCP_Client::Connect(string server, int port)
{
    struct sockaddr_in RemoteHost;

#ifdef W32
    WSADATA       wsd;
    if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
    {
        DEBUG(L"Failed to load Winsock!\n");
        return FALSE;
    }
#endif

    //create socket if it is not already created
    if (s == SOCKET_ERROR)
    {
        //Create socket
        s = socket(AF_INET, SOCK_STREAM, 0);
        if (s == SOCKET_ERROR)
        {
            DEBUG(L"Could not create socket");
            return FALSE;
        }
    }

    //setup address structure
    if (inet_addr(server.c_str()) == INADDR_NONE)
    {
        struct hostent *he;

        //resolve the hostname, its not an ip address
        if ((he = gethostbyname(server.c_str())) == NULL)
        {
            //gethostbyname failed
            DEBUG(L"gethostbyname() - Failed to resolve hostname\n");
            return FALSE;
        }
    }   
    else//plain ip address
    {
        RemoteHost.sin_addr.s_addr = inet_addr(server.c_str());
    }

    RemoteHost.sin_family = AF_INET;
    RemoteHost.sin_port = htons(port);

    //Connect to remote server
    if (connect(s, (struct sockaddr *)&RemoteHost, sizeof(RemoteHost)) < 0)
    {
        DEBUG(L"connect() failed");
        return FALSE;
    }

    connected = TRUE;
    starttime = time(0);
    stats.download = 0;
    stats.upload = 0;
    return TRUE;
}

TCP_Stats TCP_Client::GetStats()
{
    if (connected==TRUE)
        stats.uptime = time(0)-starttime;
    return stats;
}

int TCP_Client::Send(string data)
{
    stats.upload += data.length();
    return send(s, data.c_str(), data.length(), 0);

}

int TCP_Client::Recv(string *data)
{
    int ret = 0;
    char buffer[512];

    ret = recv(s, buffer, sizeof(buffer), 0);
    data->assign(buffer);
    data->resize(ret);
    stats.download += data->length();

    return ret;
}

main.cpp中:

#include <stdio.h>
#include <string.h>
#include "SocketsClass.h"

using namespace std;


int main(int argc, char *argv)
{
    TCP_Client tc;
    tc.Connect("127.0.0.1", 9999);
    tc.Send("HEllo");
    string data;
    tc.Recv(&data);
    puts(data.c_str());
    tc.Disconnect();
    printf("\n\nDL: %i\nUP: %i\nUptime: %u\n", tc.GetStats().download, tc.GetStats().upload, tc.GetStats().uptime);
    return 0;
}

一些额外的问题:

  1. 想象一下,我正在发送一个文件。我的函数如何知道当前数据与上一条消息有关?
  2. 我的班级如何设计和实施?我应该改变什么吗?
  3. 谢谢

2 个答案:

答案 0 :(得分:3)

如果“可移植”是指在Windows以外的其他平台上运行,那么工作线程中的recv()循环是您唯一的便携式选项。特别是在Windows上,您还有一些其他选择:

  1. 分配隐藏窗口,然后使用WSAAsyncSelect()接收FD_READ次通知。这需要一个消息循环,您可以将其放入工作线程中。

  2. 使用WSAEventSelect()FD_READ通知注册一个可等待的事件,然后在一个帖子中通过WSAWaitForMultipleEvents()等待这些事件。

  3. WSARecv()与I / O完成端口一起使用。通过线程中的GetQueuedCompletionResult()轮询IOCP。

  4. 关于有关消息传递的问题,TCP是一个字节流,它没有消息的概念。您必须自己构建消息。你可以:

    1. 为每条消息提供一个包含消息长度的固定标头。首先读取标题,然后读取它所说的许多字节,然后读取下一个标题,依此类推。

    2. 使用未出现在消息数据中的唯一分隔符分隔每条消息。阅读,直到遇到该分隔符,然后读到下一个分隔符,依此类推。

答案 1 :(得分:2)

让事件循环调用pollselect以确定是否有可在套接字上读取的数据。然后阅读它,并调用适当的回调函数。