如何实现一个recv()回调
我想提高我的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;
}
一些额外的问题:
- 想象我送一个文件。我的函数如何知道当前数据与前一条消息有关?
- 我的课程设计和实施如何?我应该改变什么吗?
谢谢
如果“便携式”你的意思是在除了Windows之外的其他平台上运行则recv()
循环的工作线程是你唯一的便携式选择。在Windows而言,你有更多的选择:
分配一个隐藏的窗口,然后使用
WSAAsyncSelect()
接收FD_READ
通知。这需要一个消息循环,您可以将其放入工作线程中。使用
WSAEventSelect()
注册FD_READ
通知的等待事件,然后通过WSAWaitForMultipleEvents()
在线程中等待这些事件。使用带有I/O完成端口的
WSARecv()
。在线程中通过GetQueuedCompletionResult()
轮询IOCP。
至于你关于消息传递的问题,TCP是一个字节流,它没有消息的概念。你必须自己设计你的信息。您可以:
给每个消息一个包含消息长度的固定头。首先阅读标题,然后阅读它说的很多字节,然后阅读下一个标题,依此类推。
用一个独特的分隔符分隔每条消息,该分隔符不出现在消息数据中。阅读直到遇到该分隔符,然后阅读,直到下一个分隔符为止,依此类推。
如果您在专用线程中调用'recv()',只需让'recv()'块直到数据到达,那么使用poll()或select() –
@RemyLebeau:如果你的事件循环只需要处理一个套接字,并且只接收数据,那么这只是一个真正的选择。一个更一般的事件循环可能不得不处理传出流量和/或更多的套接字,也许还有其他的事情。这就是'select'和'poll'的优点 - 它们可以处理多个套接字和多种类型的事件。 –
@SanderDeDycker - OP专门询问TCP客户端 - 一个套接字。 –
你可以看看[Boost ASIO](http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio.html),看看它们是如何做到的。但总结一下:如果你想使它完全异步,那么你需要线程。 –