TCP IP network programming (4) TCP-based server and client

Understand TCP, UDP

TCP/IP protocol stack

​TCP /IP protocol stack

Please add image description

The TCP/IP protocol stack is divided into 4 layers. It can be understood that data sending and receiving is divided into 4 hierarchical processes.

​TCP protocol stack

Insert image description here

​UDP protocol stack

Insert image description here

link layer

The link layer is the result of standardization in the physical connection field and is also the most basic field, specifically defining network standards such as LAN, WAN, and MAN. Two hosts exchange data through the network, which requires a physical connection as shown in the figure below. The link layer is responsible for these standards.

Insert image description here

IP layer

The IP protocol is a message-oriented, unreliable protocol. It will help us choose a path every time we transmit data, but it is not consistent. If a path error occurs during transmission, another path is selected. But if data loss or error occurs, it cannot be solved. The IP protocol cannot cope with data errors.

TCP/UDP layer

The IP layer solves the problem of path selection in data transmission, and only needs to transmit data along this path. The TCP and UDP layers complete actual data transmission based on the path information provided by the IP layer, so this layer is also called the transport layer. TCP can guarantee reliable data transmission, but it sends data based on the IP layer.

Insert image description here

TCP and UDP exist above the IP layer and determine the data transmission method between hosts. The TCP protocol gives reliability to the unreliable IP protocol after confirmation.

Application layer

The above classes are automatically handled during socket communication. The selection of data transmission path and data confirmation process are hidden inside the socket. But only by mastering these theories can we write network programs that meet our needs.

The tool provided to everyone is sockets, and everyone only needs to use sockets to write programs. In the process of writing software, it is necessary to determine the data transmission rules between the server and the client based on the characteristics of the program. This is the application layer protocol. A large part of network programming is designing and implementing application layer protocols.

Implement TCP-based server and client

Default function calling sequence on TCP server side

1、socket()    		创建套接字
2、bind()			分配套接字地址
3、listen()			等待连接请求状态
4、accept()			允许连接
5、read()/write()	数据交换
6、close()			断开连接

Enter the waiting connection request state

We have called the bind function to assign an address to the socket. Next, we call the listen function to enter the state of waiting for a connection request. Only by calling the listen function can the client enter a state where it can issue a connection request. At this time, the client can call connect. function.

#include<sys/socket.h>

int listen(int sock, int backlog);
	成功返回0,失败返回-1
    sock	为希望进入等待连接请求状态的文件描述符,传递的描述符套接字参数为服务器端套接字
    backlog 连接请求等待队列的长度,若为5,则队列长度为5,表示最多使5个连接请求进入队列

The waiting connection request state means that when the client requests a connection, the connection is kept in a waiting state before accepting the connection. The client connection request itself is also a kind of data received in the network, and a socket is required to accept it.

Accept client connection requests

After calling the listen function, if there are new connection requests, they should be accepted in order. Accepting a request means entering a state where data can be accepted. At this time, a socket is needed to accept data, but the server-side socket is acting as a gatekeeper and can no longer play the role of accepting data. Therefore, another socket is needed. This socket does not need to be created personally. The accept function will create the socket and connect to the client that initiated the request.

#include <sys/socket.h>

int accept(int sock, struct sockaddr* addr, socklen_t* addrlen);
	成功返回创建的套接字文件描述符,失败返回-1
    sock  	服务器套接字的文件描述符
	addr  	保存发起连接请求的客户端地址信息的变量的地址,调用函数后会向该变量填充客户端地址信息
	addrlen	第二个参数addr结构体的长度,调用函数后会向该变量填充客户端地址长度

The accept function accepts connection requests and waits for pending client connection requests in the queue. When the function call is successful, the accept function will internally generate a socket for data I/O and return the file descriptor. The socket is automatically created and a connection is automatically established with the client that initiated the connection request.

Default function calling order for TCP clients

Calling sequence of TCP client functions

1、socket()			创建套接字
2、connect()			请求连接
3、read()/write()	交换数据
4、closr()			断开连接

Compared with the server side, the difference lies in the connection request, which makes the connection request initiated to the server side after creating the client socket. The server side calls the listen function and creates a request waiting queue, and then the client can request a connection.

#include<sys/socket.h>

int connect(int sock, struct sockaddr* servaddr, socklen_t addrlen);
	成功返回0,失败返回-1
     sock  		客户端套接字文件描述符
	 servaddr	保存目标服务器地址信息的变量地址值
	 addrlen	以字节为单位,传递第二个参数的地址变量的长度

After the client calls the connect function, it will return only if the following situations occur

  • The server accepts the connection request
  • The connection request is interrupted due to abnormal circumstances such as network disconnection.

Accepting a connection request does not mean that the server calls the accept function. In fact, the server records the connection request information into the waiting queue. Therefore, data exchange is not performed immediately after the connect function returns.

TCP-based server-side and client-side function calling relationship

Insert image description here

The overall process is as follows:

After the server creates the socket, it continuously calls the bind and listen functions to enter the waiting state. The client initiates a connection request by calling the connect function. The client waits until the server calls the listen function before calling connect to initiate a connection request. The client must also be independent. Before calling the connect function, the server may call the accept function first. At this time, the server calls the accept function and enters the blocking state until the client calls the connect function.

Implement iteration server-side and client-side

Implement iteration server side

Insert image description here

The simplest way to implement iteration on the server side is to insert a loop statement and call the accept function repeatedly. The close(client) at the end of the loop closes the socket created by calling the accept function, which means that the service for a certain client has ended. If you still want to serve other clients at this time, you must call the accept function again. Currently, it can only serve one client at the same time. After learning processes and threads, you can write a server that serves multiple clients at the same time.

Iterative echo server side, client side

The basic operation mode of the echo server and supporting echo client:

  • The server is only connected to one client at the same time and provides echo services.
  • The server provides services to 5 clients in turn and exits.
  • The client accepts the user's input string and sends it to the server.
  • The server transmits the received string data back to the client, that is, "echo".
  • The string echo between the two ends is performed until the client enters Q.

First, we introduce the echo server that meets the above requirements.

echo_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	char message[BUF_SIZE];
	int str_len, i;
	
	struct sockaddr_in serv_adr;
	struct sockaddr_in clnt_adr;
	socklen_t clnt_adr_sz;
	
	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(serv_sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));

	if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");
	
	clnt_adr_sz=sizeof(clnt_adr);

	for(i=0; i<5; i++)
	{
		clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
		if(clnt_sock==-1)
			error_handling("accept() error");
		else
			printf("Connected client %d \n", i+1);
	
		while((str_len=read(clnt_sock, message, BUF_SIZE))!=0)
			write(clnt_sock, message, str_len);

		close(clnt_sock);
	}

	close(serv_sock);
	return 0;
}

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

operation result

gcc echo_server.c -o eserver
./eserver 9190
输出:
Connecten client 1
Connecten client 2
Connecten client 3

echo client code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len;
	struct sockaddr_in serv_adr;

	if(argc!=3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));
	
	if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("connect() error!");
	else
		puts("Connected...........");
	
	while(1) 
	{
		fputs("Input message(Q to quit): ", stdout); 	//如果输入Q说明结束while循环
		fgets(message, BUF_SIZE, stdin);
		
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))  //检验message是否为Q/q
			break;

		write(sock, message, strlen(message));
		str_len=read(sock, message, BUF_SIZE-1);
		message[str_len]=0;
		printf("Message from server: %s", message);
	}
	
	close(sock);
	return 0;
}

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

operation result

gcc echo_client.c -o eclient
./eclient 192.168.233.20 9190
输出:
Connected ....
Input message: hello
Message from server: hello
Input message : Q

This is the fourth article in the "TCP/IP Network Programming" column. Readers are welcome to subscribe!

For more information, click GitHub . Welcome readers to Star

⭐Academic exchange group Q 754410389 is being updated~~~

Guess you like

Origin blog.csdn.net/m0_63743577/article/details/132707211