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)