以下是代码清单:
NetworkServer.h
#pragma once
#include "Constants.h"
#include "locked_queue.h"
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bimap.hpp>
#include <boost/thread.hpp>
#include <string>
#include <array>
using boost::asio::ip::udp;
typedef boost::bimap<__int64, udp::endpoint> ClientList;
typedef ClientList::value_type Client;
typedef std::pair<std::string, __int64> ClientMessage;
class NetworkServer {
public:
NetworkServer(unsigned short local_port);
~NetworkServer();
bool HasMessages();
ClientMessage PopMessage();
void SendToClient(std::string message, unsigned __int64 clientID, bool guaranteed = false);
void SendToAllExcept(std::string message, unsigned __int64 clientID, bool guaranteed = false);
void SendToAll(std::string message, bool guaranteed = false);
inline unsigned __int64 GetStatReceivedMessages() {return receivedMessages;};
inline unsigned __int64 GetStatReceivedBytes() {return receivedBytes;};
inline unsigned __int64 GetStatSentMessages() {return sentMessages;};
inline unsigned __int64 GetStatSentBytes() {return sentBytes;};
private:
// Network send/receive stuff
boost::asio::io_service io_service;
udp::socket socket;
udp::endpoint server_endpoint;
udp::endpoint remote_endpoint;
std::array<char, NetworkBufferSize> recv_buffer;
boost::thread service_thread;
void start_receive();
void handle_receive(const boost::system::error_code& error, std::size_t bytes_transferred);
void handle_send(std::string /*message*/, const boost::system::error_code& /*error*/, std::size_t /*bytes_transferred*/) {}
void run_service();
unsigned __int64 get_client_id(udp::endpoint endpoint);
void send(std::string pmessage, udp::endpoint target_endpoint);
// Incoming messages queue
locked_queue<ClientMessage> incomingMessages;
// Clients of the server
ClientList clients;
unsigned __int64 nextClientID;
NetworkServer(NetworkServer&); // block default copy constructor
// Statistics
unsigned __int64 receivedMessages;
unsigned __int64 receivedBytes;
unsigned __int64 sentMessages;
unsigned __int64 sentBytes;
};
NetworkServer.cpp
#define _WIN32_WINNT 0x0501
#include <boost/bind.hpp>
#include "NetworkServer.h"
#include "Logging.h"
NetworkServer::NetworkServer(unsigned short local_port) :
socket(io_service, udp::endpoint(udp::v4(), local_port)),
nextClientID(0L),
service_thread(std::bind(&NetworkServer::run_service, this))
{
LogMessage("Starting server on port", local_port);
};
NetworkServer::~NetworkServer()
{
io_service.stop();
service_thread.join();
}
void NetworkServer::start_receive()
{
socket.async_receive_from(boost::asio::buffer(recv_buffer), remote_endpoint,
boost::bind(&NetworkServer::handle_receive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void NetworkServer::handle_receive(const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (!error)
{
try {
auto message = ClientMessage(std::string(recv_buffer.data(), recv_buffer.data() + bytes_transferred), get_client_id(remote_endpoint));
if (!message.first.empty())
incomingMessages.push(message);
receivedBytes += bytes_transferred;
receivedMessages++;
}
catch (std::exception ex) {
LogMessage("handle_receive: Error parsing incoming message:",ex.what());
}
catch (...) {
LogMessage("handle_receive: Unknown error while parsing incoming message");
}
}
else
{
LogMessage("handle_receive: error: ", error.message());
}
start_receive();
}
void NetworkServer::send(std::string message, udp::endpoint target_endpoint)
{
socket.send_to(boost::asio::buffer(message), target_endpoint);
sentBytes += message.size();
sentMessages++;
}
void NetworkServer::run_service()
{
start_receive();
while (!io_service.stopped()){
try {
io_service.run();
} catch( const std::exception& e ) {
LogMessage("Server network exception: ",e.what());
}
catch(...) {
LogMessage("Unknown exception in server network thread");
}
}
LogMessage("Server network thread stopped");
};
unsigned __int64 NetworkServer::get_client_id(udp::endpoint endpoint)
{
auto cit = clients.right.find(endpoint);
if (cit != clients.right.end())
return (*cit).second;
nextClientID++;
clients.insert(Client(nextClientID, endpoint));
return nextClientID;
};
void NetworkServer::SendToClient(std::string message, unsigned __int64 clientID, bool guaranteed)
{
try {
send(message, clients.left.at(clientID));
}
catch (std::out_of_range) {
LogMessage("Unknown client ID");
}
};
void NetworkServer::SendToAllExcept(std::string message, unsigned __int64 clientID, bool guaranteed)
{
for (auto client: clients)
if (client.left != clientID)
send(message, client.right);
};
void NetworkServer::SendToAll(std::string message, bool guaranteed)
{
for (auto client: clients)
send(message, client.right);
};
ClientMessage NetworkServer::PopMessage() {
return incomingMessages.pop();
}
bool NetworkServer::HasMessages()
{
return !incomingMessages.empty();
};
NetworkClient.h
#pragma once
#include "locked_queue.h"
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <memory>
#include <array>
#include "Constants.h"
using boost::asio::ip::udp;
class NetworkClient {
public:
NetworkClient(std::string host, std::string server_port, unsigned short local_port = 0);
~NetworkClient();
void Send(std::string message);
inline bool HasMessages() {return !incomingMessages.empty();};
inline std::string PopMessage() { if (incomingMessages.empty()) throw std::logic_error("No messages to pop"); return incomingMessages.pop(); };
inline unsigned __int32 GetStatReceivedMessages(){return receivedMessages;};
inline unsigned __int64 GetStatReceivedBytes(){return receivedBytes;};
inline unsigned __int32 GetStatSentMessages(){return sentMessages;};
inline unsigned __int64 GetStatSentBytes(){return sentBytes;};
private:
// Network send/receive stuff
boost::asio::io_service io_service;
udp::socket socket;
udp::endpoint server_endpoint;
udp::endpoint remote_endpoint;
std::array<char, NetworkBufferSize> recv_buffer;
boost::thread service_thread;
// Queues for messages
locked_queue<std::string> incomingMessages;
void start_receive();
void handle_receive(const boost::system::error_code& error, std::size_t bytes_transferred);
void run_service();
NetworkClient(NetworkClient&); // block default copy constructor
// Statistics
unsigned __int32 receivedMessages;
unsigned __int64 receivedBytes;
unsigned __int32 sentMessages;
unsigned __int64 sentBytes;
};
NetworkClient.cpp
#define _WIN32_WINNT 0x0501
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include "NetworkClient.h"
#include "Logging.h"
NetworkClient::NetworkClient(std::string host, std::string server_port, unsigned short local_port) :
socket(io_service, udp::endpoint(udp::v4(), local_port)),
service_thread(std::bind(&NetworkClient::run_service, this))
{
receivedBytes = sentBytes = receivedMessages = sentMessages = 0;
udp::resolver resolver(io_service);
udp::resolver::query query(udp::v4(), host, server_port);
server_endpoint = *resolver.resolve(query);
Send("");
}
NetworkClient::~NetworkClient()
{
io_service.stop();
service_thread.join();
}
void NetworkClient::start_receive()
{
socket.async_receive_from(boost::asio::buffer(recv_buffer), remote_endpoint,
boost::bind(&NetworkClient::handle_receive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)
);
}
void NetworkClient::handle_receive(const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (!error)
{
std::string message(recv_buffer.data(), recv_buffer.data() + bytes_transferred);
incomingMessages.push(message);
receivedBytes += bytes_transferred;
receivedMessages++;
}
else
{
LogMessage("NetworkClient::handle_receive:",error);
}
start_receive();
}
void NetworkClient::Send(std::string message)
{
socket.send_to(boost::asio::buffer(message), server_endpoint);
sentBytes += message.size();
sentMessages++;
}
void NetworkClient::run_service()
{
LogMessage("Client network thread started\n");
start_receive();
LogMessage("Client started receiving\n");
while (!io_service.stopped()) {
try {
io_service.run();
}
catch (const std::exception& e) {
LogMessage("Client network exception: ", e.what());
}
catch (...) {
LogMessage("Unknown exception in client network thread");
}
}
LogMessage("Client network thread stopped");
}
locked_queue
类(#include "locked_queue.h"
)是std::queue
的简单包装,使用boost::mutex
锁定了访问功能。供参考:#pragma once
#include <boost/thread/mutex.hpp>
#include <queue>
#include <list>
template<typename _T> class locked_queue
{
private:
boost::mutex mutex;
std::queue<_T> queue;
public:
void push(_T value)
{
boost::mutex::scoped_lock lock(mutex);
queue.push(value);
};
_T pop()
{
boost::mutex::scoped_lock lock(mutex);
_T value;
std::swap(value,queue.front());
queue.pop();
return value;
};
bool empty() {
boost::mutex::scoped_lock lock(mutex);
return queue.empty();
}
};
在MIT许可证的条件下可以随意使用此代码。
更新:该代码已根据审查进行了更新有关建议,请访问GitHub。
#1 楼
异常安全您的
locked_queue
并非异常安全。特别是: queue.pop();
return value;
如果
_T
的复制(或移动)构造函数抛出异常,则可能已经从队列中弹出了该项目,则构造函数在返回时抛出异常值,那么该值将丢失并且无法恢复。这就是为什么标准库将检索值与从集合中删除值分开的原因-您首先复制,然后(只有在成功的情况下)才将其从集合中删除。如果您确定永远不会将其与复制/移动ctor可以抛出的类型一起使用,但是您所做的应该没问题。不幸的是,您将其与
std::string
一起使用,该bool
具有可抛出的复制ctor。YAGNI
您有一些可能被称为“违反YAGNI的行为”。例如:
void NetworkServer::SendToAll(std::string message, bool guaranteed)
{
for (auto client: clients)
send(message, client.right);
};
在这里,您要向函数传递
bool
,显然是为了表明它是否应尝试保证传递,但是该函数完全忽略了该值。至少在正常使用中,这样的保证基本上是在创建初始连接时指定的(TCP保证传递,而UDP没有)。布尔参数
以非显而易见的方式使用布尔作为参数。通常我通常建议不要使用
SendToAll("whatever", true)
参数。有一些例外,但是在这种情况下,SendToAll("whatever", false)
和bool
的区别以及该参数的含义并不明显(尽管如上所述,在这种情况下,它绝对没有任何意义)。假设您修复了代码,使参数有意义,那么将
enum
替换为enum class
可能会更好,这样可以在代码中直接看到其意图,例如:enum { ATTEMPT, GUARANTEE };
SendToAll("whatever", ATTEMPT);
...或者给定C ++ 11,您可能想改用
SendToAllExcept
:enum class delivery { ATTEMPT, GUARANTEE };
SendToAll("whatever", delivery::ATTEMPT);
相同的注释也适用于
sentBytes
。 /> 成员初始化列表
首选成员初始化列表进行初始化。一个明显的例子是:
receivedBytes = sentBytes = receivedMessages = sentMessages = 0;
(在ctor的内部)。在这种情况下,最好使用类似以下的方法:
NetworkClient::NetworkClient(std::string host, std::string server_port, unsigned short local_port) :
socket(io_service, udp::endpoint(udp::v4(), local_port)),
service_thread(std::bind(&NetworkClient::run_service, this)),
receivedBytes(0),
sentBytes(0),
receivedMessages(0),
sentMessages(0)
{
udp::resolver resolver(io_service);
udp::resolver::query query(udp::v4(), host, server_port);
server_endpoint = *resolver.resolve(query);
Send("");
}
或者,您可能至少要考虑一个专门用于计数器的小型类型:
class counter {
size_t count;
public:
counter &operator=(size_t val) { count = val; return *this; }
counter(size_t count=0) : count(count) {}
operator size_t() { return count; }
count &operator++() { ++count; return *this; }
count operator++(int) { counter ret(count); ++count; return ret; }
bool operator==(counter const &other) { return count == other.count; }
bool operator!=(counter const &other) { return count != other.count; }
};
只需将
receivedBytes
,counter
等定义为unsigned __int64
类的对象,就不可能创建未初始化类型的对象。 > 尽可能使用可移植性
举一个明显的例子,您在许多地方都使用了
__int64
。 unsigned long long
特定于VC ++。由于没有其他理由,我宁愿使用long long
,它在VC ++上也能很好地工作,但也可以与其他任何符合标准的实现(C ++ 11)一起工作。与此相对的是,在C ++ 11之前,某些编译器(最著名的是VC ++)不支持ulonglong
。如果这是一个问题,我将使用中间的typedef:#ifdef _MSC_VER
typedef unsigned __int64 ulonglong;
#else
typedef unsigned long long ulonglong;
#endif
...然后其余代码将使用
typedef
。如果您需要比上述更多的特定于编译器的代码,您仍然可以通过为该特定编译器添加一个const
来在一处更改它,(希望)无需对其余代码进行任何更改。常量正确性
例如,您具有四个功能:
inline unsigned __int32 GetStatReceivedMessages(){return receivedMessages;};
inline unsigned __int64 GetStatReceivedBytes(){return receivedBytes;};
inline unsigned __int32 GetStatSentMessages(){return sentMessages;};
inline unsigned __int64 GetStatSentBytes(){return sentBytes;};
由于这些功能不应修改对象的状态,因此它们应该是
stats
成员函数:inline unsigned __int32 GetStatReceivedMessages() const {return receivedMessages;};
inline unsigned __int64 GetStatReceivedBytes() const {return receivedBytes;};
inline unsigned __int32 GetStatSentMessages() const {return sentMessages;};
inline unsigned __int64 GetStatSentBytes() const {return sentBytes;};
考虑更多数据分组
对于与上述相同的四个功能,我更喜欢创建一个
operator<<
类。父级将返回该类的对象,然后您可以在该类中查询您关心的特定细节:class stats {
inline unsigned GetReceivedMessages() const { return receivedMessages; }
// ...
};
class NetworkClient {
stats s;
public:
stats Stats() const { return s; }
};
// ... and in client code, something like:
NetworkClient c;
std::cout << c.Stats().GetReceivedMessages();
根据情况,您可能还希望重载该类型的q4312079q,以便更轻松地打印统计信息:
std::cout << c.Stats();
避免不必要的语法
尽管您可以在成员函数的主体后插入分号,但这是不必要的。 IMO,最好将其省略,因此(例如):
inline unsigned __int32 GetStatReceivedMessages() const {return receivedMessages;};
...成为:
inline unsigned __int32 GetStatReceivedMessages() const { return receivedMessages; }
虽然我(比很多人)对此不太坚持,但我确实认为在这种情况下,额外的空格也有助于提高可读性。
评论
\ $ \ begingroup \ $
Bool参数是后来的功能(保证交付)的存根,但我的错在这里-应该表明了这一点。所有有效点,谢谢。
\ $ \ endgroup \ $
– DarkWanderer
2014年5月28日下午5:33
\ $ \ begingroup \ $
@DarkWanderer:我可能没有像我应该强调的那样强调它,但是我主要关心的是从bool变为枚举,并将其作为参数传递给正确的函数-确实需要最初是在建立连接时传递的,而不是在发送消息时传递的(除非您要通过重新发送UDP数据包以确保传递来进行重新发明TCP之类的操作,对此我会认真考虑)。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2014年5月28日下午6:58
\ $ \ begingroup \ $
NP,我只是想证明为什么它排在第一位。至于TCP与UDP之间的关系-有一篇文章证明即使为保证消息也选择UDP是合理的,我倾向于同意那里提到的观点-由于丢失一个数据包而停止整个保证消息队列是没有好处的。这对于实时游戏而言将是致命的(这里就是这种情况)。无论如何,数据的排序在这里并不是什么大问题。
\ $ \ endgroup \ $
– DarkWanderer
2014年5月28日7:30
\ $ \ begingroup \ $
并不是说我认为UDP是灵丹妙药,我只是认为在我的特定情况下它更合适。
\ $ \ endgroup \ $
– DarkWanderer
2014年5月28日在9:11
\ $ \ begingroup \ $
@DarkWanderer:可能是。请注意,我并不是说这不一定是一个坏主意,只是我必须认真考虑并确保该工作对应用程序合理的事情。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2014年5月28日上午9:19
评论
您是否真的需要使用Boost线程API?您不能用std代替它来减少外部库的数量吗?std :: thread的副作用是,它无法在C ++ / CLI模式下编译(至少从Visual Studio 2013开始)。我个人喜欢VS内置托管测试:)
哎呀,那真不幸:/
没有其他问题吗?我开始认为我是C ++天才:-P让我们添加一些赏金...
我对网络和/或并发编程了解不多。也许其他人可以添加一些东西:)