Network programming -- TCP principle

reference resources

  1. TCP/IP network programming Yin Shengyu

TCP principle

I/O buffering in TCP sockets

The data sending and receiving of TCP socket has no boundary. For example, the client can slowly receive the data transmitted by the server in batches

In fact, the data is not transmitted immediately after the write function is called, and the data is not received immediately after the read function is called. More accurately, at the moment of the write function call, the data will be moved to the output buffer and transmitted to the other party's input buffer at an appropriate time (whether separately or at one time); The read function reads data from the input buffer at the moment of calling

These I/O buffer characteristics are as follows:

  1. I/O buffering exists separately in each TCP socket
  2. I/O buffers are generated automatically when sockets are created
  3. Even if the socket is closed, the data left in the output buffer will continue to be passed
  4. Closing the socket will lose the data in the input buffer

The point in time returned from the write function

The write function and the send function of Windows do not return when the data transmission to the other host is completed, but when the data is moved to the output buffer. However, TCP will ensure the transmission of output buffer data, so the write function returns when the data transmission is completed

Connection with opposite socket (three handshakes)

The process of TCP socket from creation to disappearance is divided into the following three steps:

  1. Establish a connection with the other socket
  2. Data exchange with the other socket
  3. Disconnect from the other socket

TCP has three conversations in the process of establishing a connection with the other socket. Therefore, this process is also called Three-way handshaking

Sockets work in full duplex mode. Therefore, some preparations need to be made before sending and receiving data

(1) First, the host A and host B requesting the connection transmit the following information:

[SYN] SEQ: 1000, ACK:-

In this message, SEQ is 1000, ACK is empty, and seq is 1000 means that the serial number of the transmitted packet is 1000. If the reception is correct, please inform me to transmit packet 1001 to you

This is the message used when requesting a connection for the first time, also known as SYN (Synchronization), which represents the Synchronization message transmitted before sending and receiving data

(2) Next, host B sends the following message to A:

[SYN+ACK] SEQ: 2000, ACK: 1001

At this time, SEQ is 2000, ACK is 1001, and seq is 2000 means: the serial number of the transmitted packet is 2000. If the reception is correct, please inform me to transmit the 2001 packet to you

The meaning of ACK 1001 is: the packet with SEQ = 1000 transmitted just now has been received without error. Now please pass the packet with SEQ = 1001

The confirmation message (ACK 1001) of the data packet transmitted for the first time by host A is bundled with the synchronization message (SEQ 2000) to prepare the data transmitted by host B. therefore, this type of message is also called SYN+ACK

Before sending and receiving data, assign a serial number to the data packet and notify the other party of this serial number, which is a preparation to prevent data loss. By assigning a sequence number to the packet and confirming it, you can view and retransmit the lost packet immediately when the data is lost. Therefore, TCP can ensure reliable transmission

(3) Finally, the message transmitted from host A to host B:

[ACK] SEQ: 1001, ACK: 2001

At this time, the message transmitted by the packet is that the transmitted packet with SEQ 2000 has been correctly received, and now the packet with SEQ 2001 can be transmitted

At this point, host A and host B confirm that they are ready for each other

Data exchange with the other host

Suppose host A passes 100 bytes to host B

(1) First, host A sends 100 bytes of data through one packet, and the SEQ of the packet is 1200

(2) To confirm this, host B sends an ACK 1301 message to host A

At this time, the ACK number is 1301 instead of 1201, because the increment of the ACK number is the number of data bytes transmitted. Assuming that the ACK number is not added with the number of bytes transmitted each time, although the transmission of the data packet can be confirmed, it is not clear that all 100 bytes are correctly transmitted or some are lost. Therefore, the ACK message is delivered according to the following formula:

ACK number = SEQ number + Number of bytes passed + 1

Finally, 1 is added to inform the other party of the SEQ number to be delivered next time

Disconnect from socket (four handshakes)

Firstly, socket a transmits the disconnection message to socket B, socket B sends a message confirming the receipt, and then transmits a message that can be disconnected to socket A. socket a also sends a confirmation message

(1) First, host A and host B requesting disconnection pass the following information:

[FIN] SEQ: 5000, ACK: -

FIN means disconnected

(2) Then, host B sends A confirmation message to A:

[ACK] SEQ: 7500, ACK: 5001

(3) Next, host B sends A FIN message to host A:

[FIN] SEQ: 7501, ACK: 5001

The ACK 5001 is retransmitted because no data is received after receiving the ACK message

(4) Finally, host A confirms the message to host B:

[ACK] SEQ: 5001, ACK: 7502

Implementation of calculator server / client based on Windows

Client implementation

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
#include <WS2tcpip.h>

#define BUF_SIZE 1024
#define RLT_SIZE 4
#define OPSZ 4
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	char opmsg[BUF_SIZE];
	int result, opndCnt, i;
	SOCKADDR_IN servAdr;
	if (argc != 3)
	{
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		ErrorHandling("WSAStartup() error!");
	}

	hSocket = socket(PF_INET, SOCK_STREAM, 0);
	if (hSocket == INVALID_SOCKET)
	{
		ErrorHandling("socket() error");
	}

	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	inet_pton(AF_INET, argv[1], &servAdr.sin_addr);
	servAdr.sin_port = htons(atoi(argv[2]));

	if (connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
	{
		ErrorHandling("connect() error!");
	}
	else
	{
		puts("Connected.......");
	}

	fputs("Operand count: ", stdout);
	scanf_s("%d", &opndCnt);
	opmsg[0] = (char)opndCnt;

	for (i = 0; i < opndCnt; i++)
	{
		printf("Operand %d: ", i + 1);
		scanf_s("%d", (int*)&opmsg[i * OPSZ + 1]);
	}
	fgetc(stdin);
	fputs("Operator: ", stdout);
	scanf_s("%c", &opmsg[opndCnt * OPSZ + 1], 1);
	send(hSocket, opmsg, opndCnt * OPSZ + 2, 0);
	recv(hSocket, (char*)&result, RLT_SIZE, 0);

	printf("Operation result: %d \n", result);
	closesocket(hSocket);
	WSACleanup();
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

Server side implementation

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>

#define BUF_SIZE 1024
#define OPSZ 4
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hServSock, hClntSock;
	char opinfo[BUF_SIZE];
	int result, opndCnt, i;
	int recvCnt, recvLen;
	SOCKADDR_IN servAdr, clntAdr;
	int clntAdrSize;
	if (argc != 2)
	{
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		ErrorHandling("WSAStartup() error!");
	}

	hServSock = socket(PF_INET, SOCK_STREAM, 0);
	if (hServSock == INVALID_SOCKET)
	{
		ErrorHandling("socket() error");
	}

	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAdr.sin_port = htons(atoi(argv[1]));

	if (bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
	{
		ErrorHandling("bind() error");
	}
	if (listen(hServSock, 5) == SOCKET_ERROR)
	{
		ErrorHandling("listen() error");
	}
	clntAdrSize = sizeof(clntAdr);

	for (i = 0; i < 5; i++)
	{
		opndCnt = 0;
		hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &clntAdrSize);
		recv(hClntSock, &opndCnt, 1, 0);

		recvLen = 0;
		while (opndCnt * OPSZ + 1 > recvLen)
		{
			recvCnt = recv(hClntSock, &opinfo[recvLen], BUF_SIZE - 1, 0);
			recvLen += recvCnt;
		}
		result = calculate(opndCnt, (int*)opinfo, opinfo[recvLen - 1]);
		send(hClntSock, (char*)&result, sizeof(result), 0);
		closesocket(hClntSock);
	}
	closesocket(hServSock);
	WSACleanup();
	return 0;
}

int calculate(int opnum, int opnds[], char op)
{
	int result = opnds[0], i;
	switch (op)
	{
	case '+':
		for (i = 1; i < opnum; i++)
		{
			result += opnds[i];
			break;
		}
	case '-':
		for (i = 1; i < opnum; i++)
		{
			result -= opnds[i];
			break;
		}
	case '*':
		for (i = 1; i < opnum; i++)
		{
			result *= opnds[i];
			break;
		}
	}
	return result;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

Keywords: socket computer networks tcp TCP/IP

Added by planetsim on Sun, 03 Oct 2021 20:34:42 +0300