About TCP packets, sticky packets, half packets

About TCP packets, sticky packets, half packets

About Tcp packets

Many friends have done a lot of research on this, and also spent a lot of effort to write the implementation code and blog documents. Of course, it is also full of comments of all kinds. I read it for myself and summarized some experiences.

First of all, let s learn from the experiences of these friends. They are:

blog.csdn.net/stamhe/arti...

www.cppblog.com/tx7do/archi...

//

Of course there are too many, many things stick to the sticky area and I don t know whose original work it is

Looking at the blogs of these friends is that I suggest to personally take a look at the relevant content in the TCP-IP Detailed Explanation Volume 1 [principal content must be read].

Introduction to the general working principle of TCP:

working principle

TCP-IP Detailed Volume 1 Chapter 17 Section 17.2 gives a concise introduction to TCP service principles (the following blue font is taken from "TCP-IP Detailed Volume 1 Chapter 17 Section 17.2"):

Although both TCP and UDP use the same network layer (IP), TCP provides completely different services to the application layer than UDP. TCP provides a connection-oriented, reliable byte stream service.

Connection-oriented means that two applications that use TCP (usually a client and a server) must establish a TCP connection before exchanging data with each other. This process is very similar to making a call. 1. dial the phone and ring, wait for the other party to pick up the phone and say "Hello", then explain who it is. In Chapter 18, we will see how a TCP connection is established and how to disconnect when one party has finished communicating.

In a TCP connection, only two parties communicate with each other. The broadcast and multicast introduced in Chapter 12 cannot be used for TCP.

TCP provides reliability in the following ways:

Application data is divided into data blocks that TCP considers the most suitable for sending. This is completely different from UDP, and the length of the datagram generated by the application will remain unchanged. The unit of information delivered by TCP to IP is called a message segment or segment (see Figure 1-7). In Section 18.4 we will see how TCP determines the length of a message segment.

When TCP sends a segment, it starts a timer and waits for the destination to confirm receipt of this segment. If an acknowledgment cannot be received in time, the segment will be resent. In Chapter 21, we will understand the adaptive timeout and retransmission strategy in the TCP protocol.

When TCP receives data from the other end of the TCP connection, it will send an acknowledgment. This confirmation is not sent immediately, and is usually delayed by a fraction of a second, which will be discussed in Section 19.3.

TCP will keep the checksum of its header and data. This is an end-to-end checksum, the purpose of which is to detect any changes in data during transmission. If there is an error in the checksum of the received segment, TCP will discard the segment and not acknowledge the receipt of this segment (hope that the sender will time out and retransmit).

Since TCP segments are transmitted as IP datagrams, and the arrival of IP datagrams may be out of order, the arrival of TCP segments may also be out of order. If necessary, TCP will reorder the received data and deliver the received data to the application layer in the correct order.

Since IP datagrams will be repeated, the TCP receiver must discard the repeated data.

TCP can also provide flow control. Each side of the TCP connection has a fixed size buffer space. The receiving end of TCP only allows the other end to send the data that the buffer of the receiving end can accept. This will prevent the faster host from causing the slower host's buffer to overflow. Two applications exchange a byte stream composed of 8 bit bytes through a TCP connection. TCP does not insert record identifiers in the byte stream. We call this a byte stream service. If the application on one side transmits 10 bytes, then 20 bytes, and then 50 bytes, the other side of the connection will not know how many bytes the sender sends each time. The receiver can receive these 80 bytes in 4 times, 20 bytes each time. One end puts the byte stream on the TCP connection, and the same byte stream will appear on the other end of the TCP connection. In addition, TCP does not provide any interpretation of the contents of the byte stream. TCP does not know whether the transmitted data byte stream is binary data, or ASCII characters, EBCDIC characters, or other types of data. The interpretation of the byte stream is interpreted by the application layer on both sides of the TCP connection. This way of handling byte streams is very similar to the way the U nix operating system handles files. The kernel of U nix does not give any interpretation to the content read or written by an application, but hands it to the application program for processing. For the U nix kernel, it cannot distinguish between a binary file and a text file.

How does TCP determine the length of a message segment

       I still quote the official explanation "TCP-IP Detailed Explanation Volume 1" Chapter 18. Section 18.4:

The maximum segment length (MSS) represents the length of the largest block of data that TCP transmits to the other end. When a connection is established [three-way handshake], both parties of the connection must notify their MSS. We have seen that MSS is 1 0 2 4. This results in IP datagrams that are usually 40 bytes long: 20 bytes of TCP header and 20 bytes of IP header.

In some books, it is seen as a "negotiable" option. It is not negotiable under any conditions. When establishing a connection

When receiving, each party has the MSS option that it expects to receive (MSS option can only appear in the SYN segment). If one party does not receive the MSS value from the other party, the MSS is set to the default value of 5 3 6 bytes (this default value allows 20 bytes of IP header and 20 bytes of TCP header to fit 5 7 6 bytes IP datagram).

Generally speaking, if no segmentation occurs, the larger the MSS, the better (this is not always correct, see the examples in Figure 2 4-3 and Figure 2 4-4). The larger the segment, the more data can be transmitted in each segment, and the higher the network utilization rate compared to IP and TCP headers. When TCP sends a SYN, either because a local application process wants to initiate a connection, or because the host at the other end receives a connection request, it can set the MSS value to the MTU length on the outgoing interface minus the fixed IP Header and TCP header length. For an Ethernet, the MSS value can reach 1,460 bytes. Using IEEE 802.3 encapsulation (see section 2.2), its MSS can reach 1 4 52 bytes.

If the destination IP address is "nonlocal", the MSS usually defaults to 5 3 6. It is simple to distinguish whether the address is local or non-local. If the network number and subnet number of the destination IP address are the same as ours, it is local; if the network number of the destination IP address is completely different from ours, it is non-local If the network number of the destination IP address is the same as ours but the subnet number is different from ours, it may be local or non-local. Most TCP implementations provide a configuration option (Appendix E and Figure E-1) to allow the system administrator to specify whether the different subnets are local or non-local. The setting of this option will determine that MSS can be as large as possible (reaching the MTU length of the outgoing interface) or the default value of 5 3 6.

MSS allows the host to limit the length of the datagram sent by the other end. In addition, the host can also control the length of the datagram it sends, which will enable hosts connected to a network with a smaller MTU to avoid fragmentation.

Only when the host at one end is directly connected to a network with an MTU less than 576 bytes, avoiding such fragmentation will be effective.

If the hosts at both ends are connected to the Ethernet, they both use 536 MSS, but the intermediate network uses 296 MTU, it will also

Segmentation appears. Using the MTU discovery mechanism on the path (see Section 24.2) is the only way to deal with this problem.

       The above shows that the value of MSS can be resolved through negotiation. This negotiation process involves the size of the MTU value. As mentioned earlier: [MSS=MTU-IP header on the outgoing interface-TCP header], let s take a look at the data entering the TCP protocol stack The packaging process:

       

The size of the last layer of Ethernet frame should be the size of our export MTU. When the destination host receives an Ethernet data frame, the data begins to rise from the bottom to the bottom of the protocol stack, and the message headers added by the various layers of the protocol are removed. Each layer of the protocol box must check the protocol identifier in the header of the message to determine the upper layer protocol of the received data. This process is called Demultiplexing, and Figures 1-8 show how this process occurs.

So what is MTU? This is actually a concept of the data link layer. Both Ethernet and 802.3 LAN technology standards have restrictions on the size of data frames at the "link layer":

l Maximum transmission unit MTU

 

As you can see in Figure 2.1, Ethernet and 802.3 both have a limit on the length of the data frame, and the maximum value is 150 and 1492 bytes, respectively. This characteristic of the link layer is called MTU, the maximum transmission unit. Most different types of networks have an upper limit.

If the IP layer has a datagram to be transmitted, and the length of the data is larger than the MTU of the link layer, then the IP layer needs to perform fragmentation to divide the datagram into several pieces, so that each piece is smaller than the MTU. We will discuss the process of IP fragmentation in Section 11.5.

Figure 2-5 lists some typical MTU values, which are taken from RFC 1191 [Mogul and Deering 1990]. The MTU of the point-to-point link layer (such as SLIP and PPP) does not refer to the physical characteristics of the network media. On the contrary, it is a logical limitation, the purpose is to provide a sufficiently fast response time for interactive use. In Section 2.10, we will see how this limit value is calculated. In section 3.9, we will use the netstat command to print out the MTU of the network interface.

l Path MTU

 

When two hosts on the same network communicate with each other, the MTU of the network is very important. but if

The communication between two hosts must pass through multiple networks, so the link layer of each network may have a different MTU. important

It is not the value of the MTU of the network where the two hosts are located. What is important is the minimum MTU in the path of the two communicating hosts. It is called the road

Diameter MTU.

The MTU of the path between two hosts is not necessarily a constant. It depends on the route selected at the time. And the route is not necessarily

Is symmetric (the route from A to B may be different from the route from B to A), so the path MTU is not necessarily in both directions

Consistent.

RFC 1191 [Mogul and Deering 1990] describes the discovery mechanism of the path MTU, that is, the path is determined at any time

MTU method. We will look at how it works after introducing the ICMP and IP fragmentation methods. In section 11.6, I

We will use this method of discovery when we see unreachable errors in ICMP. In Section 11.7, you will also see that the traceroute program

This method is also used to determine the MTU of the path to the destination node. In Section 11.8 and Section 24.2, we will introduce how to support the product.

How does UDP and TCP operate when the path MTU discovery method is used?

TCP timeout and retransmission

When talking about how TCP guarantees transmission reliability, it means "When TCP sends a segment, it starts a timer and waits for the destination to confirm receipt of this segment. If it cannot receive an acknowledgment in time, it will retransmit the message. "Segment", let me take a look at the timeout and retransmission of TCP.

TCP provides a reliable transport layer. One of the methods it uses is to confirm the data received from the other end. However, both data and confirmation may be lost. TCP solves this problem by setting a timer when sending. If it has not received an acknowledgment when the timer overflows, it retransmits the data. For any implementation, the key is the timeout and retransmission strategy, that is, how to determine the timeout interval and how to determine the frequency of retransmission.

For each connection, TCP manages 4 different timers.

1) The retransmission timer is used when expecting to receive an acknowledgement from the other end.

2) The persist timer keeps the window size information constantly flowing, even if the other end closes its receiving window.

3) Keepalive timer can detect when the other end of an idle connection crashes or restarts.

4) The 2MSL timer measures the time a connection is in the TIME _ WA IT state.

The most important part of TCP timeout and retransmission is the measurement of the round trip time (RT T) of a given connection. Because routers and network traffic will change, we believe that this time may change frequently. TCP should track these changes and change its timeout accordingly.

Most TCP implementations originating from Berkeley measure the RTT value only once for each connection at any time. When sending a message segment, if the timer of a given connection has been used, the message segment will not be timed.

The estimation of the specific RTT value is more troublesome, you can refer to "TCP-IP Detailed Explanation Volume 1 Chapter 21" if you need it.

TCP undergoes delayed acknowledgement

Interactive data is always sent in packets smaller than the maximum segment length. For these small segments, the receiver uses the time-delayed confirmation method to determine whether the confirmation can be postponed, so that it can be sent together with the return data. This usually reduces the number of segments.

Normally, TCP does not send an ACK immediately when it receives data; instead, it postpones the transmission in order to send the ACK along with the data that needs to be sent in this direction (sometimes this phenomenon is called data piggybacking ACK). Most implementations use a delay of 200 ms, that is, TCP will wait for data to be sent together with a maximum delay of 200 ms.

Let s take a look at another friend s blog about this:

Abstract: When using TCP to transmit small data packets, the design of the program is very important. If the
delay response of TCP data packets is not included in the design  , Nagle algorithm and Winsock buffer function will attract attention, which will seriously affect the performance of the program. This article discusses these 
issues, lists two cases, and gives some optimized design schemes for transmitting small data packets.

Background: When the Microsoft TCP stack receives a packet, it starts a 200 millisecond timer. After the ACK packet is 
sent, the timer will be reset. When the next packet is received, the timer for 200 milliseconds will be started again. In order to improve
the transmission performance of the application  on the intranet and the Internet, the Microsoft TCP stack uses the following strategy to determine
when to send an ACK confirmation packet after receiving the  packet: 
1. If the 200 millisecond timer expires, When the next data packet is received, an ACK confirmation data packet is sent immediately. 
2. If there is a data packet that needs to be sent to the receiving end of the ACK confirmation information, the ACK confirmation information is attached to the data packet and sent immediately. 
3. When the timer expires, the ACK confirmation message is sent immediately. 
In order to avoid congestion of the network with small data packets, the Microsoft TCP stack enables the Nagle algorithm by default. This algorithm can
splice the data sent by multiple calls to Send by the application, and  send them together when the ACK confirmation message of the previous data packet is received. The following are
the exceptions to the Nagle  algorithm: 
1. If the data packet spliced by the Microsoft TCP stack exceeds the MTU value, the data will be sent immediately without waiting for
the ACK confirmation message of the previous data  packet. In Ethernet, the TCP MTU (Maximum Transmission Unit) value is 1460 bytes. 
2. If the TCP_NODELAY option is set, the Nagle algorithm will be disabled, and the data packet sent by the application calling Send will be
delivered to the network immediately  without delay. 
In order to optimize performance at the application layer, Winsock copies the data sent by the application calling Send from the application buffer to Winsock 
Kernel buffer. The Microsoft TCP stack uses a method similar to the Nagle algorithm to determine when to actually deliver the data to the network. 
The default size of the kernel buffer is 8K. Use the SO_SNDBUF option to change the size of the Winsock kernel buffer. If necessary, 
Winsock can buffer data larger than the SO_SNDBUF buffer size. In most cases, the completion of the Send call by the application only indicates that the data 
is copied to the Winsock kernel buffer, but it does not mean that the data is actually delivered to the network. The only exception is that 
the Winsock kernel buffer is disabled by setting SO_SNDBUT to 0.

Winsock uses the following rules to indicate the completion of a Send call to the application: 
1. If the socket is still within the SO_SNDBUF quota, Winsock copies the data to be sent by the application to the kernel buffer to complete the Send call. 
2. If the Socket exceeds the SO_SNDBUF quota and there is only one buffered send data in the kernel buffer before, Winsock copies
the data to be sent  to the kernel buffer to complete the Send call. 
3. If the Socket exceeds the SO_SNDBUF quota and there is more than one buffered sending data
in the kernel buffer, Winsock copies the data to be sent  to the kernel buffer, and then delivers the data to the network until the Socket falls within the SO_SNDBUF quota or there is only one remaining data. Only
after sending the data, the Send call is  completed.

Case 1 
A Winsock TCP client needs to send 10,000 records to the Winsock TCP server and save them to the database. The record size varies from 20 bytes to 100 
bytes. For simple application logic, possible design solutions are as follows: 
1. The client sends in blocking mode, and the server receives in blocking mode. 
2. The client sets SO_SNDBUF to 0, disables the Nagle algorithm, and allows each data packet to be sent separately. 
3. The server calls Recv to receive data packets in a loop. Pass a 200-byte buffer to Recv so that each record can
be retrieved in one Recv call  .

Performance: 
In the test, it is found that the client can only send 5 pieces of data per second to the service segment, a total of 10,000 records, about 976K bytes, and it took more than half an hour 
to transmit all of them to the server.

Analysis: 
Because the client does not set the TCP_NODELAY option, the Nagle algorithm forces the TCP stack to wait for the ACK confirmation
message of the previous packet before sending the packet  . However, the client sets SO_SNDBUF to 0, which disables the kernel buffer. Therefore, 10,000 Send calls can only
send and confirm one  packet per packet. Due to the following reasons, each ACK confirmation message is delayed by 200 milliseconds: 
1. When the server receives a packet, it starts a 200 millisecond timer . 
2. The server does not need to send any data to the client, so the ACK confirmation information cannot be carried along the way by the data packet sent back. 
3. The client cannot send a data packet without receiving the confirmation information of the previous data packet. 
4. After the timer on the server expires, the ACK confirmation message is sent to the client.

How to improve performance: 
There are two problems in this design. 1. there is a delay problem. The client needs to be able to send two data packets to the server within 200 milliseconds. 
Because the client uses the Nagle algorithm by default, the default kernel buffer should be used, and SO_SNDBUF should not be set to 0. Once
the data packet spliced by the TCP  stack exceeds the MTU value, the data packet will be sent immediately without waiting for the previous ACK confirmation message. 2. this design 
scheme calls Send once for every such small data packet. Sending such small packets is not very efficient. In this case,
each record should be supplemented to 100 bytes and 80 records should be  sent every time Send is called. In order to let the server know how many records are sent at a time, the 
client can precede the records with a header information.

Case 2: 
A Winsock TCP client program opens two connections and communicates with a Winsock TCP server that provides stock quotation services. The first connection is 
used as a command channel to transmit the stock code to the server. The second connection is used as a data channel to receive stock quotes. After the two connections are established, the 
client sends the stock code to the server through the command channel, and then waits for the returned stock quote information on the data channel. The client receiving the first 
under a stock quote information after sending a stock code request to the server. Neither the client nor the server has set the SO_SNDBUF and TCP_NODELAY 
options.

Performance: It 
was found in the test that the client can only get 5 quotes per second.

analysis:

This design only allows one piece of stock information to be obtained at a time. The first stock code information is sent to the server through the command channel, and
the stock quote information returned by the server through the data channel is immediately received  . Then, the client immediately sends the second request message, the send call returns immediately, and the 
sent data is copied to the kernel buffer. However, the TCP stack cannot deliver this data packet to the network immediately because it has not received the
ACK confirmation message of the previous data packet  . After 200 milliseconds, the server timer expires, the ACK confirmation message of the first request packet is sent back to the client, and
the second request packet from the client  is delivered to the network. The quotation information of the second request is immediately returned from the data channel to the client, because at this time, the
timer of the client  has expired, and the ACK confirmation message of the first quotation information has been sent to the server. This process happens cyclically.

How to improve performance: 
Here, the design of two connections is not necessary. If a connection is used to request and receive quotation information, the ACK confirmation information of the stock request will 
be immediately carried back by the returned quotation information. To further improve performance, the client should call Send to send multiple stock requests at
a time , and the server should return multiple quotes at  a time. If two one-way connections must be used for some special reasons, both the client and the server should set the TCP_NODELAY 
option, so that small data packets are sent immediately without waiting for the ACK confirmation message of the previous data packet.

Recommendations for improving performance: The 
above two cases illustrate some of the worst cases. When designing a solution to solve the sending and receiving of a large number of small data packets, the following suggestions should be followed: 
1. If the data fragments do not need to be transmitted urgently, the application should splice them into larger data blocks and then call Send. Because the send buffer 
is likely to be copied to the kernel buffer, the buffer should not be too large, usually a little bit smaller than 8K is very efficient. As long as the Winsock kernel buffer 
gets a data block larger than the MTU value, it will send several data packets, leaving the last data packet. Except for the last packet, the sender will not 
be triggered by the 200 millisecond timer. 
2. If possible, avoid one-way Socket data flow. 
3. Do not set SO_SNDBUF to 0, unless you want to ensure that the data packet is delivered to the network immediately after the call to Send is completed. In fact, the 8K buffer is suitable for most 
situations and does not need to be changed again, unless the newly set buffer is tested to be more efficient than the default size. 
4. If the reliability of data transmission is not guaranteed, use UDP.

Conclusion:

1. TCP provides reliable transmission services for "continuous byte streams". TCP does not understand the data content carried by the stream. This content needs to be parsed by the application layer.

2. The "byte stream" is continuous and unstructured, and our application needs ordered and structured data information, so we need to define our own "rules" to interpret this "continuous byte" "Stream", the solution is to define your own packet type, and then use this type to map the "continuous byte stream".

How to define the packet, let's review the previous encapsulation process diagram of the data entering the protocol stack:

Envelope actually defines the user data that enters the protocol stack in the above figure (that is, the data that the user wants to send) as a type that is convenient for identification and communication. This is somewhat similar to the concept of envelopes, which are a format for communication between people. The envelope format is as follows:

Envelope format:

       Recipient's Zip Code

       Recipient address

       Recipient's name

       The content of the letter

Then in the program we also need to define this format: in C++, only two types of structure and class are suitable for expressing this concept. Many friends on the Internet expressed their views on this and posted codes: for example

       /************************************************* ***********************/

/* Start of data packet information definition */

/************************************************* ***********************/

 

#pragma pack(push,1)//Push the original alignment on the stack and adopt the new 1-byte alignment

 

/* Enumeration of packet types [listed here according to requirements] */

typedef enum{

              NLOGIN=1,

              NREG=2,

              NBACKUP=3,

              NRESTORE=3,

              NFILE_TRANSFER=4,

              NHELLO=5

} PACKETTYPE;

 

/* Baotou */

typedef struct tagNetPacketHead{

       byte version;//version

       PACKETTYPE ePType;//Package type

       WORD nLen;//Package body length

} NetPacketHead;

 

/* Packet object [header & body] */

typedef struct tagNetPacket{

       NetPacketHead netPacketHead;//Packet header

       char * packetBody;//Package body

} NetPacket;

 

#pragma pack(pop)

/**************End of data packet information definition ****************************/

3. The order of sending and receiving the package

a) Because TCP needs to negotiate the length of the message segment sent out, the data we send is likely to be divided or even divided and then reassembled and sent to the network layer, and the network layer uses packet transmission, that is, the network The order in which the layer datagram arrives at the target is completely unpredictable, so there will be half-packet and sticky-packet problems when receiving packets. For example, if the sender continuously sends data msg1 and msg2 at both ends, the following situation may occur in the sender [transport layer]:

                                       i. Msg1 and msg2 are smaller than the MSS of TCP, and the two packets are sent out in order without being split and reorganized

                                     ii. If Msg1 is too large, it is divided into two TCP messages msg1-1 and msg2-2 for transmission, and msg2 is small and directly encapsulated into one message for transmission

                                    iii. Msg1 is too large and is divided into two TCP packets msg1-1, msg2-2, msg1-1 is transmitted first, and the remaining msg1-2 and msg2 [smaller] are combined into one packet for transmission

                                   iv. Msg1 is too large and is divided into two TCP packets msg1-1, msg2-2, msg1-1 is transmitted first, and the remaining msg1-2 and msg2[smaller] combined are still too small. The content of the combination is Combine with the first part of the msg3 data sent later to send

                                     v. too much ..

b) Possible situations at the receiving end [transport layer]

                                       i. Receive msg1 first, then msg2, this way is too smooth.

                                     ii. Receive msg1-1 first, then msg1-2, then msg2

                                    iii. Receive msg1 first, then msg2-1, then msg2-2

                                   iv. Receive msg1 and msg2-1 first, then msg2-2

                                     v.//There are many more

c) In fact, the order of packet datagrams received by the "receiving end network layer" may be completely chaotic than the sending end. For example, the "sending end network layer" sends 1, 2, 3, 4, and 5, while the receiving end network layer The order of the received datagram may be 2, 1, 5, 4, 3, but the "transmission layer of the receiving end" will ensure the order and reliability of the link, and the "transmission layer of the receiving end" will affect the "receiving end network layer". "Received out-of-sequence datagrams are reassembled into ordered messages [that is, the order sent by the sender's transport layer], and then handed over to the "receiving end application layer", so the "receiving end transport layer" can always guarantee data The order of the packet, the "receiving application layer" [socket program we wrote] does not have to worry about the order of the received data.

d) However, as mentioned above, the problem of sticky package and half package is inevitable. We need to code ourselves at the receiving end application layer to deal with sticky packets and half packets. The general practice is to define a buffer or use the container provided by the standard library/framework to store the received data in a loop, and judge whether the buffer data meets the header size while receiving changes. If it meets the header size, then determine whether the remaining data in the buffer meets the packet body. The size, if it is satisfied, it will be extracted. The detailed steps are as follows:

1. The received data is stored in the end of the buffer

2. Whether the buffer data meets the header size

3. If the buffer data does not meet the header size, go back to step 1; if the buffer data meets the header size, take out the header, and then judge whether the remaining data in the buffer meets the packet body size defined in the header, or go back to step 1 .

4. If the buffer data meets the sum of the size of a packet header and the size of a packet body, take out the packet header and the packet body for use. Here, you can use the copy method to transfer the buffer data to another place, or you can directly call it to save memory. Data usage is completed by way of callback function.

5. To clear the first header and body information of the buffer, the usual practice is to copy the remaining data in the buffer to the buffer header and cover the "first header and body information".

The specific implementation of sticky package and semi-package processing. Many friends have their own practices, such as the link posted at the top. Here I also posted a reference:

Buffer implementation header file:

#include <windows.h>

 

#ifndef _CNetDataBuffer_H_

#define _CNetDataBuffer_H_

 

#ifndef TCPLAB_DECLSPEC

#define TCPLAB_DECLSPEC _declspec(dllimport)

#endif

 

/************************************************* ***********************/

/* Start of data packet information definition */

/************************************************* ***********************/

 

#pragma pack(push,1)//Push the original alignment on the stack and adopt the new 1-byte alignment

 

/* Enumeration of packet types [listed here according to requirements] */

typedef enum{

              NLOGIN=1,

              NREG=2,

              NBACKUP=3,

              NRESTORE=3,

              NFILE_TRANSFER=4,

              NHELLO=5

} PACKETTYPE;

 

/* Baotou */

typedef struct tagNetPacketHead{

       byte version;//version

       PACKETTYPE ePType;//Package type

       WORD nLen;//Package body length

} NetPacketHead;

 

/* Packet object [header & body] */

typedef struct tagNetPacket{

       NetPacketHead netPacketHead;//Packet header

       char * packetBody;//Package body

} NetPacket;

 

#pragma pack(pop)

/**************End of data packet information definition ****************************/

 

//The initial size of the buffer

#define BUFFER_INIT_SIZE 2048

 

//Buffer expansion coefficient [size of buffer after expansion = original size + coefficient * new data length]

#define BUFFER_EXPAND_SIZE 2

 

//Macro to calculate the length of the remaining data in the buffer except the first header [total length of buffer data-header size]

#define BUFFER_BODY_LEN (m_nOffset-sizeof(NetPacketHead))

 

//Calculate whether the buffer data currently meets the data volume of a complete package [Package header & body]

#define HAS_FULL_PACKET (\

                                                 (sizeof(NetPacketHead)<=m_nOffset) &&/

                                                 ((((NetPacketHead*)m_pMsgBuffer)->nLen) <= BUFFER_BODY_LEN)/

                                          )

 

//Check whether the package is legal [the length of the package body is greater than zero and the package body is not equal to empty]

#define IS_VALID_PACKET(netPacket)/

       ((netPacket.netPacketHead.nLen>0) && (netPacket.packetBody!=NULL))

 

//The length of the first packet of the buffer

#define FIRST_PACKET_LEN (sizeof(NetPacketHead)+((NetPacketHead*)m_pMsgBuffer)->nLen)

 

/* Data buffer */

class/*TCPLAB_DECLSPEC*/CNetDataBuffer

{

       /* Buffer operation related members */

private:

       char *m_pMsgBuffer;//Data buffer

       int m_nBufferSize;//Total buffer size

       int m_nOffset;//Buffer data size

public:

       int GetBufferSize() const;//Get the size of the buffer

       BOOL ReBufferSize(int);//Adjust the size of the buffer

       BOOL IsFitPacketHeadSize() const;//Whether the buffered data fits the packet header size

       BOOL IsHasFullPacket() const;//Whether the buffer has complete packet data [including header and body]      

       BOOL AddMsg(char *pBuf,int nLen);//Add a message to the buffer

       const char *GetBufferContents() const;//Get the contents of the buffer

       void Reset();//Buffer reset [empty buffer data, but did not release the buffer]

       void Poll();//Remove the first packet of the buffer header

public:

       CNetDataBuffer();

       ~CNetDataBuffer();

};

 

#endif

 

Buffer implementation file:

#define TCPLAB_DECLSPEC _declspec(dllexport)

 

#include "CNetDataBuffer.h"

 

/* Construction */

CNetDataBuffer::CNetDataBuffer()

{

       m_nBufferSize = BUFFER_INIT_SIZE;//Set the buffer size

       m_nOffset = 0;//Set the data offset value [data size] to 0

       m_pMsgBuffer = NULL;

       m_pMsgBuffer = new char[BUFFER_INIT_SIZE];//Allocate the buffer to the initial size

       ZeroMemory(m_pMsgBuffer,BUFFER_INIT_SIZE);//Buffer empty   

}

 

/* destruct */

CNetDataBuffer::~CNetDataBuffer()

{

       if (m_nOffset!=0)

       {

              delete [] m_pMsgBuffer;//Release the buffer

              m_pMsgBuffer = NULL;

              m_nBufferSize=0;

              m_nOffset=0;

       }

}

 

 

 

/************************************************* ***********************/

/* Description: Get the size of the data in the buffer */

/* Return: the size of the data in the buffer */

/************************************************* ***********************/

INT CNetDataBuffer::GetBufferSize() const

{

       return this->m_nOffset;

}

 

 

/************************************************* ***********************/

/* Description: Whether the size of the data in the buffer is enough for a header size */

/* Return: return True if it is satisfied, otherwise return False

/************************************************* ***********************/

BOOL CNetDataBuffer::IsFitPacketHeadSize() const

{

       return sizeof(NetPacketHead)<=m_nOffset;

}

 

/************************************************* ***********************/

/* Description: Determine whether the buffer has a complete data packet (header and body) */

/* Return: Return True if the buffer contains a complete packet, otherwise False */

/************************************************* ***********************/

BOOL CNetDataBuffer::IsHasFullPacket() const

{

       //If not even the header size is satisfied, return

       //if (!IsFitPacketHeadSize())

       //return FALSE;

 

       return HAS_FULL_PACKET;//The macro is used here to simplify the code

}

 

/************************************************* ***********************/

/* Description: reset buffer size */

/* nLen: newly added data length */

/* Return: adjustment result */

/************************************************* ***********************/

BOOL CNetDataBuffer::ReBufferSize(int nLen)

{

       char *oBuffer = m_pMsgBuffer;//Save the original buffer address

       try

       {

              nLen=(nLen<64?64:nLen);//Guaranteed minimum increment size

              //The size of the new buffer = the increased size + the original buffer size

              m_nBufferSize = BUFFER_EXPAND_SIZE*nLen+m_nBufferSize;          

              m_pMsgBuffer = new char[m_nBufferSize];//Allocate a new buffer, m_pMsgBuff points to the new buffer address

              ZeroMemory(m_pMsgBuffer,m_nBufferSize);//clear the new buffer

              CopyMemory(m_pMsgBuffer,oBuffer,m_nOffset);//Copy all the contents of the original buffer to the new buffer

       }

       catch(...)

       {

              throw;

       }

 

       delete []oBuffer;//Release the original buffer

       return TRUE;

}

 

/************************************************* ***********************/

/* Description: add a message to the buffer */

/* pBuf: the data to be added */

/* nLen: added message length

/* return: Return True if the addition is successful, otherwise False */

/************************************************* ***********************/

BOOL CNetDataBuffer::AddMsg(char *pBuf,int nLen)

{

       try

       {

              //Check whether the buffer length is satisfied, if not satisfied, re-adjust the buffer size

              if (m_nOffset+nLen>m_nBufferSize)

                     ReBufferSize(nLen);

             

              //Copy new data to the end of the buffer 

              CopyMemory(m_pMsgBuffer+sizeof(char)*m_nOffset,pBuf,nLen);

             

              m_nOffset+=nLen;//Modify the data offset

       }

       catch(...)

       {

              return FALSE;

       }

       return TRUE;

}

 

/* Get the contents of the buffer */

const char * CNetDataBuffer::GetBufferContents() const

{

       return m_pMsgBuffer;

}

 

/************************************************* ***********************/

/* Buffer reset */

/************************************************* ***********************/

void CNetDataBuffer::Reset()

{

      

       if (m_nOffset>0)

       {

              m_nOffset = 0;

              ZeroMemory(m_pMsgBuffer,m_nBufferSize);

       }

}

 

/************************************************* ***********************/

/* Remove the first packet of the buffer header */

/************************************************* ***********************/

void CNetDataBuffer::Poll()

{

       if(m_nOffset==0 || m_pMsgBuffer==NULL)

              return;

       if (IsFitPacketHeadSize() && HAS_FULL_PACKET)

       {           

              CopyMemory(m_pMsgBuffer,m_pMsgBuffer+FIRST_PACKET_LEN*sizeof(char),m_nOffset-FIRST_PACKET_LEN);

       }

}

 

Simple encapsulation of TCP packet sending and receiving:

head File:

#include <windows.h>

#include "CNetDataBuffer.h"

 

//#ifndef TCPLAB_DECLSPEC

//#define TCPLAB_DECLSPEC _declspec(dllimport)

//#endif

 

 

#ifndef _CNETCOMTEMPLATE_H_

#define _CNETCOMTEMPLATE_H_

 

 

//Communication port

#define TCP_PORT 6000

 

/* Communication terminal [including a Socket and a buffer object] */

typedef struct {

       SOCKET m_socket;//Communication socket

       CNetDataBuffer m_netDataBuffer;//The data buffer associated with the socket

} ComEndPoint;

 

/* Receive package callback function parameter */

typedef struct{

       NetPacket *pPacket;

       LPVOID processor;

       SOCKET comSocket;

} PacketHandlerParam;

 

class CNetComTemplate{     

       /* Socket operation related members */

private:

      

public:

       void SendPacket(SOCKET m_connectedSocket,NetPacket &netPacket);//Send package function

       BOOL RecvPacket(ComEndPoint &comEndPoint,void (*recvPacketHandler)(LPVOID)=NULL,LPVOID=NULL);//Packet receiving function

public:

       CNetComTemplate();

       ~CNetComTemplate();

};

 

#endif

 

 

Implementation file:

 

#include "CNetComTemplate.h"

 

CNetComTemplate::CNetComTemplate()

{

 

}

 

CNetComTemplate::~CNetComTemplate()

{

 

}

 

/************************************************* ***********************/

/* Description: send package */

/* m_connectedSocket: Socket with established connection */

/* netPacket: the packet to be sent */

/************************************************* ***********************/

void CNetComTemplate::SendPacket(SOCKET m_connectedSocket,NetPacket &netPacket)

{

       if (m_connectedSocket==NULL || !IS_VALID_PACKET(netPacket))//If the connection has not been established, exit

       {

              return;

       }

       ::send(m_connectedSocket,(char*)&netPacket.netPacketHead,sizeof(NetPacketHead),0);//Send the packet header first

       ::send(m_connectedSocket,netPacket.packetBody,netPacket.netPacketHead.nLen,0);//Sending the packet body

}

 

/************************************************* *************************/

/* Description: receive package */

/* comEndPoint: communication terminal [including socket and associated buffer] */

/* recvPacketHandler: Receive packet callback function, when a packet is received, this function is called for packet distribution processing*/

/************************************************* *************************/

BOOL CNetComTemplate::RecvPacket(ComEndPoint &comEndPoint,void (*recvPacketHandler)(LPVOID),LPVOID pCallParam)

{

       if (comEndPoint.m_socket==NULL)

              return FALSE;

      

       int nRecvedLen = 0;

       char pBuf[1024];

       //If the buffer data is not enough for the packet size, continue to read the tcp segment from the socket

       while (!(comEndPoint.m_netDataBuffer.IsHasFullPacket()))

       {

              nRecvedLen = recv(comEndPoint.m_socket,pBuf,1024,0);

             

              if (nRecvedLen==SOCKET_ERROR || nRecvedLen==0)//If the Socket is wrong or the connection of the other party has been closed normally, the reading is ended

                     break;

              comEndPoint.m_netDataBuffer.AddMsg(pBuf,nRecvedLen);//store the newly received data into the buffer

       }

 

       //The execution here may be three situations:

       //1. The data that has been read satisfies a complete tcp segment

       //2. Socket_error error occurred when reading

       //3. The connection of the other party has been closed before the normal reading is completed

      

 

       //If the data is not read or the complete message segment is not read, return

       if (nRecvedLen==0 || (!(comEndPoint.m_netDataBuffer.IsHasFullPacket())))

       {

              return FALSE;

       }

 

       if (recvPacketHandler!=NULL)

       {

              //Construct the data packet to be passed to the callback function

              NetPacket netPacket;

              netPacket.netPacketHead = *(NetPacketHead*)comEndPoint.m_netDataBuffer.GetBufferContents();

              netPacket.packetBody = new char[netPacket.netPacketHead.nLen];//Dynamic allocation of packet body space

             

              //Construct callback function parameters

              PacketHandlerParam packetParam;

              packetParam.pPacket = &netPacket;

              packetParam.processor = pCallParam;

 

              //Call callback function

              recvPacketHandler(&packetParam);

 

              delete []netPacket.packetBody;

       }

 

       //Remove the first packet of the buffer

       comEndPoint.m_netDataBuffer.Poll();

 

       return TRUE;

}

\

Article source: click to open the link