This is the second article in The Muduo network programming example series.
TCP Subcontracting
The protocols processed in the previous article "html"> five simple TCP Protocols do not involve subcontracting. the application layer subcontracting in the byte stream protocol such as TCP is the basic requirement for network programming. Packet forwarding refers to the process that allows the recipient to identify and intercept (Restore) a byte stream when a message or frame data occurs) messages are generated one by one. "Packet sticking problem" is a pseudo problem.
For short-connection TCP services, subcontracting is not a problem. As long as the sender closes the connection, it means that a message is sent completely, and the receiver reads () returns 0 to know the end of the message. For example, the daytime and time protocols in the previous article.
For TCP services with persistent connections, there are four subcontracting methods:
The message length is fixed. For example, the muduo's roundtrip example uses a fixed 16-byte message;
Special characters or strings are used as the message boundary. For example, the headers of HTTP uses the separator "" as the field;
Add a length field to the header of each message. This is probably the most common practice. This method is also used in the chat protocol in this article;
Use the format of the message itself to subcontract, such as the matching of... in an XML message or {...} In JSON format. The state machine is usually used to parse this message format.
In the subsequent code explanations, we will also carefully discuss common traps for subcontracting with length fields.
Chat service
The chat service implemented in this article is very simple and consists of a server program and a client program. The Protocol is as follows:
A new port listener (listen) connection in the server program;
The client initiates a connection to the server;
After the connection is established, the client is ready to receive messages from the server and display them on the screen;
The client accepts the keyboard input and uses the carriage return as the field to send the message to the server;
After receiving a message, the server sends it to each client connected to it in sequence. The client process that sent the message also receives the message;
A server process can serve multiple client processes at the same time. When a message arrives at the server, each client process receives the same message, and the server broadcasts any message in any order, not necessarily the client will receive the message first.
(Optional) If message A arrives at the server before message B, each client receives message A before receiving message B.
This is actually a simple TCP-based application layer broadcast protocol. The server is responsible for sending messages to each client connected to it. You can participate in "chat" by either a person or a program. In future articles, I will introduce a slightly more complex example of hub. It has the "chat room" function, and the client can register a specific topic (s ), and send messages to a topic. This code is more interesting.
Message format
The message format of this chat service is very simple. "message" is a string, each message has a 4-byte header, and stores the length of the string in network order. There is no gap between messages, and the string does not necessarily end. For example, if there are two messages "hello" and "chenshuo", the packaged byte stream is:
0x00, 0x00, 0x00, 0x05, h, e, l, l, o, 0x00, 0x00, 0x00, 0x00, 0x08, c, h, e, n, s, h, u, o
A total of 21 bytes.
Packaged code
This code packages const string & message as muduo: net: Buffer and sends it through conn.
1: void send (muduo: net: TcpConnection * conn, const string & message) 2: {3: muduo: net: Buffer buf; 4: buf. append (message. data (), message. size (); 5: int32_t len = muduo: net: sockets: hostToNetwork32 (static_cast (message. size (); 6: buf. prepend (& len, sizeof len); 7: conn-> send (& buf); 8:} muduo: Buffer has a good function, it reserves 8 bytes of space in the header, so that the prepend () Operation of 6th rows does not need to move the existing data, which is more efficient.
Subcontract code
Parsing data is often more complex than generating data, and packaging is no exception.
1: void onMessage (const muduo: net: TcpConnectionPtr & conn, 2: muduo: net: Buffer * buf, 3: muduo: Timestamp receiveTime) 4: {5: while (buf-> readableBytes ()> = kHeaderLen) 6: {7: const void * data = buf-> peek (); 8: int32_t tmp = * static_cast <const int32_t *> (data); 9: int32_t len = muduo: net: sockets: networkToHost32 (tmp); 10: if (len> 65536 | len <0) 11: {12: LOG_ERROR <"Invalid length" <Len; 13: conn-> shutdown (); 14:} 15: else if (buf-> readableBytes ()> = len + kHeaderLen) 16: {17: buf-> retrieve (kHeaderLen); 18: muduo: string message (buf-> peek (), len); 19: buf-> retrieve (len); 20: messageCallback _ (conn, message, receiveTime); // receives the complete message, notifying the user 21:} 22: else 23: {24: break; 25:} 26 :} 27:} in the above Code, row 7th uses the while loop to read data repeatedly until the data in the Buffer is not enough to have a complete message. Consider the consequences of changing to if (buf-> readableBytes ()> = kHeaderLen.
The byte stream of the two messages mentioned earlier is used as an example:
0x00, 0x00, 0x00, 0x05, h, e, l, l, o, 0x00, 0x00, 0x00, 0x00, 0x08, c, h, e, n, s, h, u, o
Assuming that all the data eventually arrives, onMessage () must at least be able to correctly process the order of arrival of the following data. In each case, messageCallback _ should be called twice:
Each time a byte of data is received, onMessage () is called 21 times;
The data arrives twice. The first time two bytes are received, the length of the message is insufficient;
The data arrives twice. The first time it receives four bytes, the length field is enough, but no body;
The data arrives twice. The first time it receives 8 bytes, the length is complete, but the body is incomplete;
The data is delivered twice. The first time the data is received, it contains 9 bytes, the length is complete, and the body is complete;
The data arrives twice. The first request receives 10 bytes. The length of the first message is complete, the body is complete, and the length of the second message is incomplete;
Please move the Split points to verify various situations;
Data arrives at all once. At this time, two messages must be read using the while loop. Otherwise, messages will accumulate.
Ask the reader to verify whether onMessage () has done the above. This example fully demonstrates that non-blocking read must be used with input buffer.
LengthHeaderCodec
Someone commented that Muduo's receiving buffer cannot set the trigger condition of the callback function. Whenever the socket is readable, TcpConnection of Muduo reads data and stores the data in the Input Buffer, and then calls back the user's function. However, a simple indirect layer can solve the problem, so that the user code only cares about "message arrival" rather than "Data arrival", as shown in this example by LengthHeaderCodec.
1: # ifndef limit 2: # define MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H 3: 4: # include 5: # include 6: # include 7: # include 8: 9: # include 10: # include 11: 12: using muduo: Logger; 13: 14: class LengthHeaderCodec: boost: noncopyable 15: {16: public: 17: typedef boost: function <void (const muduo :: net: TcpConnectionPtr &, 18: const muduo: string & message, 19: muduo: Timestamp)> StringMessageCallback; 20: 21: explicit callback (const StringMessageCallback & cb) 22:: messageCallback _ (cb) 23: {24:} 25: 26: void onMessage (const muduo: net: TcpConnectionPtr & conn, 27 :&