Programación de red TCP IP (4) Servidor y cliente basado en TCP

Comprender TCP, UDP

Pila de protocolo TCP/IP

​Pila de protocolos TCP

Por favor agregue la descripción de la imagen.

La pila de protocolos TCP/IP se divide en 4 capas, se puede entender que el envío y la recepción de datos se dividen en 4 procesos jerárquicos.

Pila de protocolos TCP

Insertar descripción de la imagen aquí

Pila de protocolos UDP

Insertar descripción de la imagen aquí

capa de enlace

La capa de enlace es el resultado de la estandarización en el campo de la conexión física y también es el campo más básico, que define específicamente estándares de red como LAN, WAN y MAN. Dos hosts intercambian datos a través de la red, lo que requiere una conexión física como se muestra en la siguiente figura. La capa de enlace es responsable de estos estándares.

Insertar descripción de la imagen aquí

capa IP

El protocolo IP es un protocolo poco fiable orientado a mensajes. Nos ayudará a elegir una ruta cada vez que transmitamos datos, pero no es consistente. Si se produce un error de ruta durante la transmisión, se selecciona otra ruta. Pero si se produce una pérdida de datos o un error, no se puede solucionar. El protocolo IP no puede hacer frente a errores de datos.

Capa TCP/UDP

La capa IP resuelve el problema de la selección de ruta en la transmisión de datos y solo necesita transmitir datos a lo largo de esta ruta. Las capas TCP y UDP completan la transmisión de datos real en función de la información de ruta proporcionada por la capa IP, por lo que esta capa también se denomina capa de transporte. TCP puede garantizar una transmisión de datos confiable, pero envía datos según la capa IP.

Insertar descripción de la imagen aquí

TCP y UDP existen por encima de la capa IP y determinan el método de transmisión de datos entre hosts. El protocolo TCP brinda confiabilidad al protocolo IP no confiable después de la confirmación.

Capa de aplicación

Las clases anteriores se manejan automáticamente durante la comunicación por socket. La selección de la ruta de transmisión de datos y el proceso de confirmación de datos están ocultos dentro del socket. Pero sólo dominando estas teorías podremos escribir programas de red que satisfagan nuestras necesidades.

La herramienta proporcionada a todos son los sockets, y todos solo necesitan usar sockets para escribir programas. En el proceso de escritura de software, es necesario determinar las reglas de transmisión de datos entre el servidor y el cliente en función de las características del programa, que es el protocolo de la capa de aplicación. Una gran parte de la programación de redes consiste en diseñar e implementar protocolos de capa de aplicación.

Implementar servidor y cliente basados ​​en TCP

Secuencia de llamada de función predeterminada en el lado del servidor TCP

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

Ingrese al estado de solicitud de conexión en espera

Hemos llamado a la función de enlace para asignar una dirección al socket. A continuación, llamamos a la función de escucha para ingresar al estado de espera de una solicitud de conexión. Solo llamando a la función de escucha puede el cliente ingresar a un estado en el que pueda emitir una conexión. solicitud. En este momento, el cliente puede llamar a la función connect.

#include<sys/socket.h>

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

El estado de solicitud de conexión en espera significa que cuando el cliente solicita una conexión, la conexión se mantiene en estado de espera antes de aceptar la conexión. La solicitud de conexión del cliente en sí también es un tipo de datos recibidos en la red y se requiere un socket para aceptar. él.

Aceptar solicitudes de conexión de clientes

Después de llamar a la función de escucha, si hay nuevas solicitudes de conexión, deben aceptarse en orden. Aceptar una solicitud significa ingresar a un estado en el que se pueden aceptar datos. En este momento, se necesita un socket para aceptar datos, pero el socket del lado del servidor actúa como un guardián y ya no puede desempeñar la función de aceptar datos. Por lo tanto, se necesita otro socket. No es necesario crear este socket personalmente. La función de aceptación creará el socket y se conectará con el cliente que inició la solicitud.

#include <sys/socket.h>

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

La función de aceptación acepta solicitudes de conexión y espera solicitudes de conexión de clientes pendientes en la cola. Cuando la llamada a la función es exitosa, la función de aceptación generará internamente un socket para E/S de datos y devolverá el descriptor del archivo. El socket se crea automáticamente y se establece automáticamente una conexión con el cliente que inició la solicitud de conexión.

Orden de llamada de función predeterminado para clientes TCP

Secuencia de llamada de funciones de cliente TCP

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

En comparación con el lado del servidor, la diferencia radica en la solicitud de conexión, que hace que la solicitud de conexión se inicie en el lado del servidor después de crear el socket del cliente. El lado del servidor llama a la función de escucha y crea una cola de espera de solicitudes, y luego el cliente puede solicitar una conexión.

#include<sys/socket.h>

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

Después de que el cliente llame a la función de conexión, regresará solo si ocurren las siguientes situaciones

  • El servidor acepta la solicitud de conexión.
  • La solicitud de conexión se interrumpe debido a circunstancias anormales, como la desconexión de la red.

Aceptar una solicitud de conexión no significa que el servidor llame a la función de aceptación. De hecho, el servidor registra la información de la solicitud de conexión en la cola de espera, por lo que el intercambio de datos no se realiza inmediatamente después de que regresa la función de conexión.

Relación de llamada de función del lado del servidor y del lado del cliente basada en TCP

Insertar descripción de la imagen aquí

El proceso general es el siguiente:

Después de que el servidor crea el socket, llama continuamente a las funciones de enlace y escucha para entrar en el estado de espera. El cliente inicia una solicitud de conexión llamando a la función de conexión. El cliente espera hasta que el servidor llama a la función de escucha antes de llamar a conectar para iniciar una conexión. solicitud. El cliente también debe ser independiente. Antes de llamar a la función de conexión, el servidor puede llamar primero a la función de aceptación. En este momento, el servidor llama a la función de aceptación y entra en el estado de bloqueo hasta que el cliente llama a la función de conexión.

Implementar iteraciones del lado del servidor y del lado del cliente.

Implementar la iteración del lado del servidor.

Insertar descripción de la imagen aquí

La forma más sencilla de implementar la iteración en el lado del servidor es insertar una declaración de bucle y llamar a la función de aceptación repetidamente. El cierre (cliente) al final del ciclo cierra el socket creado al llamar a la función de aceptación, lo que significa que el servicio para un determinado cliente ha finalizado. Si aún desea atender a otros clientes en este momento, debe llamar a aceptar funcionar nuevamente. Actualmente, solo puede atender a un cliente al mismo tiempo. Después de aprender los procesos y subprocesos, puede escribir un servidor que atienda a varios clientes al mismo tiempo.

Lado del servidor de eco iterativo, lado del cliente

El modo de funcionamiento básico del servidor echo y el cliente echo compatible:

  • El servidor solo está conectado a un cliente al mismo tiempo y proporciona servicios de eco.
  • El servidor proporciona servicios a 5 clientes por turno y sale.
  • El cliente acepta la cadena de entrada del usuario y la envía al servidor.
  • El servidor transmite los datos de la cadena recibida al cliente, es decir, "eco".
  • El eco de la cadena entre los dos extremos se realiza hasta que el cliente ingresa Q.

Primero, presentamos el servidor echo que cumple con los requisitos anteriores.

servidor_echo.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);
}

resultado de la operación

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

código de cliente de eco

#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);
}

resultado de la operación

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

Este es el cuarto artículo de la columna "Programación de redes TCP/IP". ¡Los lectores pueden suscribirse!

Para obtener más información, haga clic en GitHub Bienvenidos lectores a Star

⭐Grupo de intercambio académico Q 754410389 se está actualizando~~~

Supongo que te gusta

Origin blog.csdn.net/m0_63743577/article/details/132707211
Recomendado
Clasificación