2014-03-25 34 views
1

我想提高我的OOP知识,并决定创建一个简单的类来简化套接字编程。 这是一个学习实验,所以我不想使用boost或其他库。如何实现一个recv()回调

我想实现一个事件驱动的 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. 我的课程设计和实施如何?我应该改变什么吗?

谢谢

+2

你可以看看[Boost ASIO](http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio.html),看看它们是如何做到的。但总结一下:如果你想使它完全异步,那么你需要线程。 –

回答

3

如果“便携式”你的意思是在除了Windows之外的其他平台上运行则recv()循环的工作线程是你唯一的便携式选择。在Windows而言,你有更多的选择:

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

  2. 使用WSAEventSelect()注册FD_READ通知的等待事件,然后通过WSAWaitForMultipleEvents()在线程中等待这些事件。

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

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

  1. 给每个消息一个包含消息长度的固定头。首先阅读标题,然后阅读它说的很多字节,然后阅读下一个标题,依此类推。

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

2

让你的事件循环调用或者pollselect,以确定是否存在可在插座(S)上读取数据。然后阅读它,并调用适当的回调函数。

+0

如果您在专用线程中调用'recv()',只需让'recv()'块直到数据到达,那么使用poll()或select() –

+0

@RemyLebeau:如果你的事件循环只需要处理一个套接字,并且只接收数据,那么这只是一个真正的选择。一个更一般的事件循环可能不得不处理传出流量和/或更多的套接字,也许还有其他的事情。这就是'select'和'poll'的优点 - 它们可以处理多个套接字和多种类型的事件。 –

+0

@SanderDeDycker - OP专门询问TCP客户端 - 一个套接字。 –