|
Typically, you send binary data, and you send it as messages that you can parse out based on the data. Either each message has a known size, and you know what to do from the message code, or the message has a length field prepended before the actual data. A simple example might be:
enum MessageCode {
DoNothing,
SaySomething,
MoveToPoint,
};
struct MessageHeader {
short messageCode;
short messageSize;
};
struct MessageSaySomething {
char what[256];
};
struct MessageMoveToPoint {
float x;
float y;
};
struct ActualMessage {
MessageHeader header;
union {
MessageSaySomething saySomething;
MessageMoveToPoint moveToPoint;
};
};
int sendSaySomething( SOCKET s, std::string const & text ) {
ActualMessage msg;
if( text.size() >= sizeof( msg.saySomething.what ) ) {
throw BadDataException();
}
memcpy( msg.saySomething.what, text.c_str(), text.size() );
msg.header.messageCode = MessageSaySomething;
msg.header.messageSize = sizeof( MessageHeader ) + text.size();
return ::send( s, (char const *)&msg, msg.header.messageSize, 0 );
}
int sendMoveToPoint( SOCKET s, float x, float y ) {
ActualMessage msg;
msg.header.messageCode = MessageMoveToPoint;
msg.header.messageSize = sizeof( MessageHeader ) + sizeof( msg.moveToPoint );
msg.moveToPoint.x = x;
msg.moveToPoint.y = y;
return ::send( s, (char const *)&msg, msg.header.messageSize, 0 );
}
// Really, you'll be using asynchronous receives and do some
// input buffering to avoid blocking the thread, but this is
// for illustration only.
int receiveMessage( SOCKET s, ActualMessage * outMessage ) {
int r = ::recv( s, (char *)outMessage, sizeof( outMessage->header ), 0 );
if( r != sizeof( outMessage->header ) ) {
throw ProtocolDesyncError();
}
if( outMessage->header.messageSize > sizeof( *outMessage ) - sizeof( outMessage->header ) ) {
throw BadNetworkDataError();
}
r = ::recv( s, (char *)&(&outMessage->header)[1], outMessage->header.messageSize, 0 );
return outMessage->header.messageCode;
}
You only need one receiveMessage() function, but you need one sendMessage function per message kind you want to send. Typically, in a larger system, you'll use some data-driven description of the message layouts to avoid having to write error-prone custom marshalling code for each message kind. (Code generation is also sometimes used) |