implement iterative echo server/client

1. Iterate server/client

The previously discussed HelloWorld server has finished processing 1 1One client connection request exits immediately, and the connection request waiting queue does not actually make much sense. But this is not the server side we imagined. After setting the size of the waiting queue, all clients should be served. If you want to continue to accept subsequent client connection requests, how should you extend the code? The easiest way is to insert a loop statement to call the accept function repeatedly, as shown in the figure below.

insert image description here

As can be seen from the figure above, after calling the accept function, the I/O-related read and write functions are called immediately, and then the close function is called. This is not for server-side sockets, but for sockets created when the accept function is called.

Calling the close function means ending the service for a certain client. At this time, if you want to serve other clients, you need to call the accept function again. It is true that only one client can be served at the same time. After learning processes and threads in the future, you can write a server that serves multiple clients at the same time. This is the only way to go at present, although it is a pity, but please don't worry.

Even though the server side runs iteratively, the client side code is not much different.

2. Iterate echo server/client

Echo (echo) server/client, as the name suggests, the server sends the string data transmitted by the client back to the client intact, just like an echo.

  • The server only connects to one client at a time and provides echo service.
  • The server side turns to 5 55 clients serve and exit.
  • The client receives the string entered by the user and sends it to the server.
  • The server sends the received string data back to the client, that is, "echo".
  • String echo between server and client is performed Quntil .

2.1 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);

	// 为处理5个客户端连接而添加的循环语句。共调用5次accept函数,依次向5个客户端提供服务。
	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函数,向连接的相应套接字发送EOF。
		// 换言之,客户端套接字若调用close函数,则第62行的循环条件变成假(false),因此执行第69行的代码。
		close(clnt_sock);
	}

	// 向5个客户端提供服务后关闭服务器端套接字并终止程序。
	close(serv_sock);

	return 0;
}

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

Compile and run:

gcc echo_server.c -o eserver
./eserver 9190

2.2 echo_client.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 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]));
	
	// 调用connect函数。若调用该函数引起的连接请求被注册到服务器端等待队列,则connect函数将完成正常调用。
	// 因此,即使通过第41行代码输出了连接提示字符串,如果服务器尚未调用accept函数,也不会真正建立服务关系。
	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);
		fgets(message, BUF_SIZE, stdin);
		
		if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) 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函数向相应套接字发送EOF(EOF即意味着中断连接)
	close(sock);

	return 0;
}

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

Compile and run:

gcc echo_client.c -o eclient
./eclient 127.0.0.1 9190

3. Problems with echo client

while (1)
{
    
    
	fputs("Input message(Q to quit): ", stdout);
	fgets(message, BUF_SIZE, stdin);
	
	if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) break;

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

The above code has a wrong assumption: "The actual I/O operation will be performed in units of strings every time the read and write functions are called", of course, each call to the write function will pass 1 11 string, so this assumption is somewhat reasonable.

But "TCP does not have a data boundary", the above client is based on TCP, therefore, the string passed by multiple calls to the write function may be passed to the server at one time. At this time, the client may receive multiple strings from the server, which is not what we want to see.

Also need to consider the following situation on the server side: "The string is too long and needs to be divided into 2 22 packets sent". The server side hopes to pass the call1 1One write function transfers data, but if the data is too large, the operating system may divide the data into multiple packets and send them to the client. In addition, during this process, the client may call the read function before receiving all the data packets.

All of these problems stem from the data transfer nature of TCP. So how to solve it?

4. Echo client problem solution

First review the I/O related code on the echo server side:

while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)
{
    
    
	write(clnt_sock, message, str_len);
}

Then review the I/O related code of the echo client:

write(sock, message, strlen(message));
str_len = read(sock, message, BUF_SIZE - 1);

Both are calling the read or write function in a loop. In fact, the previous echo client will receive 100% of the data transmitted by itself, but there are some problems with the unit when receiving data.

What the echo client transmits is a string, and it is sent once by calling the write function. After that, the read function is called once, looking forward to receiving the string transmitted by itself. This is where the problem lies.

The above-mentioned echo client problem is actually a common mistake made by junior programmers, and it is actually easy to solve because the size of the received data can be determined in advance. If previously transmitted 20 2020 -byte long string, then call the read function to read20 2020 bytes will do. Now that there is a solution, the code is given next.

4.1 echo_client2.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 sock;
	char message[BUF_SIZE];
	int str_len, recv_len, recv_cnt;
	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]));
	
	// 调用connect函数。若调用该函数引起的连接请求被注册到服务器端等待队列,则connect函数将完成正常调用。
	// 因此,即使通过第41行代码输出了连接提示字符串,如果服务器尚未调用accept函数,也不会真正建立服务关系。
	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);
		fgets(message, BUF_SIZE, stdin);
		
		if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) break;

		str_len = write(sock, message, strlen(message));
		
		recv_len = 0;

		while (recv_len < str_len)
		{
    
    
			recv_cnt = read(sock, &message[recv_len], BUF_SIZE - 1);
			if (recv_cnt == -1)
			{
    
    
				error_handling("read() error!");
			}
			recv_len += recv_cnt;
		}
		
		message[recv_len] = 0;

		printf("Message from server: %s", message);
	}
	
	// 调用close函数向相应套接字发送EOF(EOF即意味着中断连接)
	close(sock);

	return 0;
}

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

Compile and run:

gcc echo_client2.c -o eclient2
./eclient2 127.0.0.1 9190

Guess you like

Origin blog.csdn.net/qq_42815188/article/details/129354541