Advanced learning of network communication protocol

1, Fundamentals of high speed network communication

  1. After introducing the problem of sticking packets in network programming in the previous chapter, most of the general network transmission processes are transmitted in the way of data packet organization. Therefore, for general data packets, it includes packet header, packet tail, packet length, specific data in the packet and other information. There is no need to add the check code and other related contents. The TCP network will check the data by itself after receiving. For details See here.

  2. For the previously introduced Linux basic TCP communication The establishment of is incomplete, which is mainly reflected in the following points:

    • In the process of establishing socket, the conflict caused by some services occupying the current host Port is not handled.
    • There is no exception handling for the abnormal drop and loss of connection of the Client, which may lead to the Crash of the Server!
    • For the data bandwidth to be transmitted, the basic calculation shall be carried out, and the basic transmission capacity of the equipment hardware shall be considered to avoid overflow of the underlying data buffer space of the Linux Socket and blocking of the send or recv function.
    • For cases where you do not want to accept blocking wait, you also need to consider using multithreading or select non blocking wait function.

2, Basic exception handling

1. Organization form of data package structure

typedef struct ProfileData{
    int iHeader;               // 0xFFFF FFFE
    int iReserveS1;            // 0x0000 0000
    int iProfileSEQ;           // Data serial number
    int iReserveS2;            // 0x0000 0000
    int iEncoderVal;           // Coded data
    int iReserveS3;            // 0x0000 0000
    int *piProfileData;        // Data pointer
    int piProfileDataLen;      // Total data length
    int iPacketEnd;            // 0x0000 0000  
}T_StructProfileData;

2. Client lost connection signal processing

static void socket_sig_deal(void)
{
    struct sigaction sa;
    sigemptyset(&sa.sa_mask); // Set the blocking signal shielded during signal processing
    sa.sa_handler = handle_pipe_lt100; // Set the address of signal processing function, generally SA_ The handler only passes in the semaphore value and does not carry other signal related information
    sa.sa_flags = 0; // Flag bit setting, with sa_handler/sa_sigaction is used in different processing functions. Other parameters need to be passed in. When entering the processing function, SA needs to be selected sa_ sigaction + SA_ SIGINFO
    sigaction(SIGPIPE,&sa,NULL); // SIGPIPE pipeline disconnection signal, generated error = EPIPE, failure to capture will lead to program collapse
}

3. Actively judge the socket connection status

static int net__socket_established(int iSock)
{
    struct tcp_info info; // Create tcp_ The info structure is used to store tcp status information
    int len = sizeof(info); 
    getsockopt(iSock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); // Get TCP current status information
    if ((info.tcpi_state == TCP_ESTABLISHED)) // Judge whether the current TCP is in the established connection state
	{
        return SUCCESS; // Return to connection status
    }
	else
	{
        return ERR_CONN_LOST; // Return lost contact status
    }
}

3, Basic code implementation of high speed communication

Server code (C.1)

#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h> // SOL_SOCKET,SO_RCVTIMO,SO_SNDTIMEO,IPPOTO_TCP,TCP_NODELAY

typedef struct ProfileData{
    int iHeader;               // 0xFFFF FFFE
    int iReserveS1;            // 0x0000 0000
    int iProfileSEQ;           // Data serial number
    int iReserveS2;            // 0x0000 0000
    int iEncoderVal;           // Coded data
    int iReserveS3;            // 0x0000 0000
    int *piProfileData;        // Data pointer
    int piProfileDataLen;      // Total data length
    int iPacketEnd;            // 0x0000 0000  
}T_StructProfileData;

static void socket_sig_deal(void);
static int net__socket_established(int iSock);
int highSpeedCommunicateSetup(int *ipState);
int highSpeedCommunicateTransimit(int iSock, const T_StructProfileData t_StructDataPacket);

#define host "0.0.0.0"
#define port 50000

#define SERVICE "50000"
#define socket_domain 1

#define SUCCESS 1
#define INVALID_SOCKET -1
#define ERR_CONN_LOST -2
#define ERR_ERRNO -3

/*
TCP/IP Nagle algorithm is enabled by default for TCP in the protocol. Nagle algorithm optimizes the network by reducing the packets that need to be transmitted. In the kernel implementation, the sending and receiving of data packets will be cached first, corresponding to write cache and read cache respectively.
Start TCP_NODELAY means that the Nagle algorithm is disabled and small packets are allowed to be sent. For delay sensitive applications with small amount of data transmission, turn on TCP_ The nodelay option is undoubtedly the right choice.
For example, for SSH sessions, the speed at which users can remotely tap the keyboard to issue instructions is definitely not on the same order of magnitude as the network bandwidth capacity, so there is very little data transmission; It also requires that the user's input can be returned in time,
There is a low delay. If the Nagle algorithm is enabled, frequent delays are likely to occur, resulting in a very poor user experience. Of course, you can also choose to buffer in the application layer, such as using buffered stream in java,
Write large packets to the write cache of the kernel for sending as much as possible; Vector I / O (writev interface) is also a good choice.
For turning off TCP_NODELAY applies the Nagle algorithm. The data will not be sent out until a certain amount is accumulated in the write cache, which significantly improves the network utilization (the ratio of the actual transmission data payload to the protocol header is greatly increased).
But this inevitably increases the delay; Combined with the feature of TCP delayed ack, this problem will be more significant. The delay is basically about 40ms. Of course, this problem will be exposed only when two consecutive write operations are performed.
It is not a good network programming mode to continuously write and then read small data packets for many times; It should be optimized at the application layer.
*/

#define set_tcp_nodelay 0 // 

#define DATA_LEN 3200

int net__socket_listen(void);
int net__socket_accept(int listensock);

ssize_t net__write(int sock,const void *buf, size_t count);
int packet__write(int sock,const void *buf, size_t count);

int main(void)
{
	int flag = 0;
	T_StructProfileData ProfileData;
	memset(&ProfileData,0,sizeof(T_StructProfileData));

	ProfileData.iHeader = 0xFAFFF6F8;
	ProfileData.iReserveS1 = ProfileData.iReserveS2 = ProfileData.iReserveS3 = ProfileData.iPacketEnd = 0x00000000;
	ProfileData.iProfileSEQ = 0x00000000;	
	ProfileData.iEncoderVal = 0xEFFFD2E3;
	ProfileData.piProfileDataLen = 3200;
	ProfileData.piProfileData = (int *)malloc(sizeof(int)*ProfileData.piProfileDataLen);

	memset(ProfileData.piProfileData, 21,sizeof(int)*ProfileData.piProfileDataLen);

	int sock_ok  = highSpeedCommunicateSetup(&flag);
	if(flag == -1){
		printf("net__socket_accept failed.\n");
		return 0;
	}

	int temp = sock_ok;
	printf("####################################################### sock_ok=%d\n",temp);
	sleep(3);

	while(1){
		if(sock_ok != temp){
			printf("####################################################### sock_ok=%d\n",sock_ok);
		}
		ProfileData.iProfileSEQ += 1;
		flag = highSpeedCommunicateTransimit(sock_ok,ProfileData);
		if(flag == ERR_CONN_LOST){
			printf("ERR_CONN_LOST ####################################################################!!!!!!!!!!!!!!\n");
		}
		if(flag >= 0){
			printf("packet__write successful.\n");
		}else{
			printf("packet__write failed.\n");
		}
		sleep(1);
	}
	return 0;
}

int highSpeedCommunicateSetup(int *ipState)
{
	int isock = 0;
	isock = net__socket_listen();
	if(-1 == isock)
	{
		return INVALID_SOCKET;
	}
	isock = net__socket_accept(isock);
	if(-1 == isock)
	{
		return INVALID_SOCKET;
	}
	return isock;
}

int highSpeedCommunicateTransimit(int iSock, const T_StructProfileData t_StructDataPacket)
{
	int flag = 0;
	flag = packet__write(iSock, &(t_StructDataPacket.iHeader), 4); if(0 != flag) {return flag;}// &(t_StructDataPacket.iHeader)
	flag = packet__write(iSock, &(t_StructDataPacket.iReserveS1), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
	flag = packet__write(iSock, &(t_StructDataPacket.iProfileSEQ), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
	flag = packet__write(iSock, &(t_StructDataPacket.iReserveS2), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
	flag = packet__write(iSock, &(t_StructDataPacket.iEncoderVal), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
	flag = packet__write(iSock, &(t_StructDataPacket.iReserveS3), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
	flag = packet__write(iSock, &(t_StructDataPacket.iReserveS3), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
	flag = packet__write(iSock, &(t_StructDataPacket.iReserveS3), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
	flag = packet__write(iSock, t_StructDataPacket.piProfileData, t_StructDataPacket.piProfileDataLen * 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
	flag = packet__write(iSock, &(t_StructDataPacket.iPacketEnd), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
	return 0;
}

int net__socket_listen(void)
{
	int sock = INVALID_SOCKET;
	struct addrinfo hints;
	struct addrinfo *ainfo, *rp;
	char service[10];
	int rc;
	char ss_opt = 1;
	unsigned int sock_count = 0;

	snprintf(service, 10, "%d", port);
	memset(&hints, 0, sizeof(struct addrinfo)); // Initialize template hints variable
	if(socket_domain){
		hints.ai_family = AF_INET; //AF_INET6
	}else{
		hints.ai_family = AF_UNSPEC;
	}
	hints.ai_flags = AI_PASSIVE;
	hints.ai_socktype = SOCK_STREAM;

	rc = getaddrinfo(NULL, SERVICE, &hints, &ainfo);
	if (rc){
		printf("Error creating listener: %s.", gai_strerror(rc));
		return INVALID_SOCKET;
	}

	for(rp = ainfo; rp; rp = rp->ai_next){
		if(rp->ai_family == AF_INET){
			printf("Opening ipv4 listen socket on port %d.\n", ntohs(((struct sockaddr_in *)rp->ai_addr)->sin_port));
		}else if(rp->ai_family == AF_INET6){
			printf("Opening ipv6 listen socket on port %d.\n", ntohs(((struct sockaddr_in6 *)rp->ai_addr)->sin6_port));
		}else{
			continue;
		}
		sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if(sock == INVALID_SOCKET){
			printf("ERR:%s-%d\n",__FUNCTION__,__LINE__);
			continue;
		}
		if(bind(sock, rp->ai_addr, rp->ai_addrlen) != 0){
			printf("ERR:%s-%d\n",__FUNCTION__,__LINE__);
			break;
		}
		close(sock);
	}

	if(listen(sock, 100) == -1){
		printf("Error:");
		close(sock);
		return INVALID_SOCKET;
	}
	freeaddrinfo(ainfo);

	return sock;
}

int net__socket_accept(int listensock)
{
	int new_sock = INVALID_SOCKET, struct_len = 0;
	struct sockaddr_in client_addr;

	struct_len = sizeof(struct sockaddr_in);

	new_sock = accept(listensock, (struct sockaddr *)&client_addr, &struct_len);
	if(new_sock == INVALID_SOCKET){
		return INVALID_SOCKET;
	}

	if(set_tcp_nodelay){
		int flag = 1;
		if(setsockopt(new_sock, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)) != 0){
			printf("XiongGu Warning: Unable to set TCP_NODELAY.");
		}
	}
	return new_sock;
}

/**********************************************************************
* Function name: / / static ssize_t net__write(int sock,const void *buf, size_t count)

* Function Description: / / try to write count byte data to Socket buffer
* Table accessed://
* Modified table://
* Input parameters: / / available socket handle of int sock et
* Input parameters: / / void *buf is used to store bytes to be read and written (usually char)
* Input parameter: / / size_t count the total number of bytes expected to be written. This value must be less than or equal to buf space size / sizeof (char) > = count

* Output parameters: / / description of output parameters
* Return value: / / ssize_t: Indicates the number of bytes actually written
* Other Description: / / other description
* Modified by 	      Modification content
* -----------------------------------------------
* 2021/11/02	  	    XXXX	      XXXX
***********************************************************************/
ssize_t net__write(int sock,const void *buf, size_t count)
{
	return send(sock, buf, count, 0);
}

/**********************************************************************
* Function name: / / static int packet__write(int sock,const void *buf, size_t count)

* Function Description: / / try to write count byte data from Socket buffer until count byte data is written
* Table accessed://
* Modified table://
* Input parameters: / / available socket handle of int sock et
* Input parameters: / / void *buf is used to store bytes to be read and written (usually char)
* Input parameter: / / size_t count the total number of bytes expected to be written. This value must be less than or equal to buf space size / sizeof (char) > = count

* Output parameters: / / description of output parameters
* Return value: / / ssize_t: Indicates the number of bytes actually written
* Other Description: / / other description
* Modified by 	      Modification content
* -----------------------------------------------
* 2021/11/02	  	    XXXX	      XXXX
***********************************************************************/
int packet__write(int sock,const void *buf, size_t count)
{
	ssize_t write_length;
	char *data = (char *)buf;
	int pos=0;
	int to_process = count;
	while(to_process > 0)
	{
		write_length = net__write(sock,&data[pos],count);
		if(write_length > 0){
			to_process -= write_length;
			pos += write_length;
		}
		else
		{
			if(errno == EAGAIN || errno == EWOULDBLOCK)
			{
				return 0;
			}
			else
			{
				
				switch(errno)
				{
					case ECONNRESET:
						return ERR_CONN_LOST;
					default:
						return ERR_ERRNO;
				}
			}
		}
	}
	return 0;
}

/**********************************************************************
* Function name: / / static void handle_pipe(int Sig)

* Function Description: / / try to read count bytes from Socket buffer
* Table accessed://
* Modified table://
* Input parameter: / / int Sig signal number generated by current system

* Output parameters:// 
* Return value:// 
* Other Description: / / other description
* Modified by 	      Modification content
* -----------------------------------------------
* 2021/11/02	  	    XXXX	      XXXX
***********************************************************************/
static void handle_pipe(int Sig)
{
	printf("Handle the Disconnected Error Sig!\n"); // The captured signal is printed without any processing
}

/**********************************************************************
* Function name: / / static void socket_sig_deal(void)

* Function Description: / / try to read count bytes from Socket buffer
* Table accessed://
* Modified table://
* Input parameters:// 

* Output parameters:// 
* Return value:// 
* Other Description: / / other description
* Modified by 	      Modification content
* -----------------------------------------------
* 2021/11/02	  	    XXXX	      XXXX
***********************************************************************/
static void socket_sig_deal(void)
{
    struct sigaction sa;
	sigemptyset(&sa.sa_mask); // Set the blocking signal shielded during signal processing
	sa.sa_handler = handle_pipe; // Set the address of signal processing function, generally SA_ The handler only passes in the semaphore value and does not carry other signal related information
	sa.sa_flags = 0; // Flag bit setting, with sa_handler/sa_sigaction is used in different processing functions. Other parameters need to be passed in. When entering the processing function, SA needs to be selected sa_ sigaction + SA_ SIGINFO
	sigaction(SIGPIPE,&sa,NULL); // SIGPIPE pipeline disconnection signal, generated error = EPIPE, failure to capture will lead to program collapse
}

/**********************************************************************
* Function name: / / static int net__socket_established(int iSock)

* Function Description: / / try to read count bytes from Socket buffer
* Table accessed://
* Modified table://
* Input parameter: / / int iSock socket handle to be detected

* Output parameters:// 
* Return value: / / int: 0 indicates stable transmission establishment - 2 indicates disconnected transmission establishment
* Other Description: / / other description
* Modified by 	      Modification content
* -----------------------------------------------
* 2021/11/02	  	    XXXX	      XXXX
***********************************************************************/
static int net__socket_established(int iSock)
{
    struct tcp_info info; // Create tcp_ The info structure is used to store tcp status information
    int len = sizeof(info); 
    getsockopt(iSock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); // Get TCP current status information
    if ((info.tcpi_state == TCP_ESTABLISHED)) // Judge whether the current TCP is in the established connection state
	{
        return SUCCESS; // Return to connection status
    }
	else
	{
        return ERR_CONN_LOST; // Return lost contact status
    }
}

2. Client code (Client.c)

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#define Debug 1
#define BUF_SIZE 12800

/**********************************************************************
* Function name: / / static ssize_t net__read(int sock,void *buf, size_t count)

* Function Description: / / try to read count bytes from Socket buffer
* Table accessed://
* Modified table://
* Input parameters: / / available socket handle of int sock et
* Input parameters: / / void *buf is used to store the read bytes (usually char)
* Input parameter: / / size_t count the total number of bytes expected to be read. This value must be less than or equal to buf space size / sizeof (char) > = count

* Output parameters:// 
* Return value: / / ssize_t: Indicates the number of bytes actually read
* Other Description: / / other description
* Modified by 	      Modification content
* -----------------------------------------------
* 2021/11/02	  	    XXXX	      XXXX
***********************************************************************/
ssize_t net__read(int sock,void *buf, size_t count);

/**********************************************************************
* Function name: / / static int packet__read(int sock,const void *buf, size_t count)

* Function Description: / / try to read the count byte data from the Socket buffer until the count byte data is read
* Table accessed://
* Modified table://
* Input parameters: / / available socket handle of int sock et
* Input parameters: / / void *buf is used to store the read bytes (usually char)
* Input parameter: / / size_t count the total number of bytes expected to be read. This value must be less than or equal to buf space size / sizeof (char) > = count

* Output parameters:// 
* Return value: / / int: indicates the execution of the function
* Other Description: / / other description
* Modified by 	      Modification content
* -----------------------------------------------
* 2021/11/02	  	    XXXX	      XXXX
***********************************************************************/
int packet__read(int sock,const void *buf, size_t count);


int
main(int argc, char *argv[])
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int sfd, s, j;
    size_t len;
    ssize_t nread;
    char buf[BUF_SIZE];

    if (argc < 2) {
        fprintf(stderr, "Usage: %s host port msg...\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* Obtain address(es) matching host/port. */

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;    /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM; /* Datagram socket */
    hints.ai_flags = AI_PASSIVE;
    hints.ai_protocol = 0;          /* Any protocol */

    s = getaddrinfo(argv[1], argv[2], &hints, &result);
    if (s != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        exit(EXIT_FAILURE);
    }

    /* getaddrinfo() returns a list of address structures.
        Try each address until we successfully connect(2).
        If socket(2) (or connect(2)) fails, we (close the socket
        and) try the next address. */

    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sfd = socket(rp->ai_family, rp->ai_socktype,
                    rp->ai_protocol);
        if (sfd == -1)
            continue;

        if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1){
            struct sockaddr_in * temp;
             printf("temp=NULL\n");
             if( rp->ai_addr == NULL )
             {
                 printf("addr is null\n");
             }
             else
             {  
                temp = (struct sockaddr_in *)(rp->ai_addr);
                //  char *ip = inet_ntoa(temp.sin_addr);
                 printf("%s\n",inet_ntoa(temp->sin_addr));
             }  
             printf("temp=NULL********\n");
            break;                  /* Success */
        }

        close(sfd);
    }

    freeaddrinfo(result);           /* No longer needed */

    if (rp == NULL) {               /* No address succeeded */
        fprintf(stderr, "Could not connect\n");
        exit(EXIT_FAILURE);
    }

    /* Send remaining command-line arguments as separate
        datagrams, and read responses from server. */

	unsigned int i = 1;
	int flag = 0;

    for (;;i++) {
#if Debug
		printf("iHeader:\n");
#endif
		flag = packet__read(sfd,buf, 4); // iHeader
		if(flag == 1){
#if Debug
			for(j=0;j<4;j++){
				printf("%d ", buf[j]);
			}
			printf("\n");
#endif
		}else
		{
			continue;
		}

#if Debug
		printf("iReserveS1-iProfileSEQ-iReserveS2-iEncoderVal-iReserveS3-iReserveS3-iReserveS3:\n");
#endif
		flag = packet__read(sfd,buf, 28); // iReserveS1-iProfileSEQ-iReserveS2-iEncoderVal-iReserveS3-iReserveS3-iReserveS3
		if(flag == 1){
#if Debug
			for(j=0;j<28;j++){
				printf("%d ", buf[j]);
			}
			printf("\n");
#endif
		}else
		{
			continue;
		}

#if Debug
		printf("piProfileData:\n");
#endif
		flag = packet__read(sfd,buf, 12800); // piProfileData 3200*4Bytes = 12800 Bytes
		if(flag == 1){
#if Debug
			for(j=0;j<12800;j++){
				printf("%d ", buf[j]);
			}
			printf("\n");
#endif
		}else
		{
			continue;
		}

#if Debug
		printf("iPacketEnd:\n");
#endif
		flag = packet__read(sfd,buf, 4); // iPacketEnd
		if(flag == 1){
#if Debug
			for(j=0;j<4;j++){
				printf("%d ", buf[j]);
			}
			printf("\n");
#endif
		}else
		{
			continue;
		}
#if Debug
		printf("######################################### Line[%d] #########################################\n",i);
#endif
    }

    exit(EXIT_SUCCESS);
}

ssize_t net__read(int sock,void *buf, size_t count)
{
	return recv(sock, buf, count, 0);
}

int packet__read(int sock,const void *buf, size_t count)
{
	ssize_t read_length;
	char *data = (char *)buf;
	int pos=0;
	int to_process = count;
	while(to_process > 0)
	{
		read_length = net__read(sock,&data[pos],to_process);
		if(read_length > 0){
			to_process -= read_length;
			pos += read_length;
		}
		else
		{
#if Debug
			// printf("Current Status: To_Process(%d)-Write_Len(%d)\n",to_process,read_length);
			// return -1; //  exception handling
#endif
		}
	}
	return 1;
}

5, High speed communication code test

1. Compilation and operation

gcc -o MS MyServer.c
gcc -o MC MyClient.c
./MS
./MC 192.168.1.110 50000

2. The test results are as follows:

Server Server:
Opening ipv4 listen socket on port 50000.
ERR:net__socket_listen-177
####################################################### sock_ok=4
packet__write successful.

Client client:
21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 
iPacketEnd:
0 0 0 0 
######################################### Line[11] #########################################
iHeader:

Reference

How to judge the socket connection status in real time https://www.cnblogs.com/embedded-linux/p/7468442.html
SOCKET:SO_ Ringer options: https://www.cnblogs.com/kuliuheng/p/3670353.html
Causes and solutions of Linux SIGPIPE signal: https://blog.csdn.net/u010821666/article/details/81841755

Added by wulfgar on Mon, 07 Mar 2022 05:19:02 +0200