¿Qué sucede si el servidor no escucha y el cliente inicia un establecimiento de conexión?

Hola a todos, soy Xiaolin.

Por la mañana, cuando vi a un lector hablando de los tres lados del byte, hice esta pregunta:

imagen

Desde el punto de vista de este lector, el cliente no podría hacer ping al servidor si el servidor no llamara a escuchar.Obviamente, fue un error.

El protocolo que utiliza el ping es ICMP, que pertenece a la capa de red, y el entrevistador hace preguntas sobre la capa de transporte.

Para resolver este problema, si el servidor solo vincula la dirección IP y el puerto sin llamar a escuchar, y luego el cliente inicia el establecimiento de una conexión TCP con el servidor, ¿qué sucederá en este momento?

Haz un experimento

Este problema se puede averiguar haciendo un experimento.

Uso el siguiente programa como ejemplo, que vincula la dirección IP + el puerto sin llamar a listen.

/*******服务器程序  TCPServer.c ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main(int argc, char *argv[])
{
    
    
    int sockfd, ret;
    struct sockaddr_in server_addr;

    /* 服务器端创建 tcp socket 描述符 */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
    
    
        fprintf(stderr, "Socket error:%s\n\a", strerror(errno));
        exit(1);
    }

    /* 服务器端填充 sockaddr 结构 */
    bzero(&server_addr, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(8888);
  
  /* 绑定 ip + 端口 */
    ret = bind(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr));
    if(ret < 0)
    {
    
    
        fprintf(stderr, "Bind error:%s\n\a", strerror(errno));
        exit(1);
    }
  
  //没有调用 listen
    
    sleep(1000);
    close(sockfd);
    return 0;
}

Luego, uso un navegador para acceder a esta dirección: http://121.43.173.240:8888/

imagen

Error al conectar con el servidor.

Al mismo tiempo, también utilicé la herramienta de captura de paquetes para capturar este proceso.

imagen

Se puede ver que después de que el cliente envía un mensaje SYN al servidor, el servidor devuelve un mensaje RST.

Por lo tanto, hay una respuesta a esta pregunta.Si el servidor solo vincula la dirección IP y el puerto, pero no llama a escuchar, entonces el cliente inicia un establecimiento de conexión con el servidor y el servidor devolverá un mensaje RST.

Análisis de código fuente

Luego, lleve a todos a analizar el código fuente.

La función de entrada del kernel de Linux para procesar los mensajes TCP recibidos es tcp_v4_rcv Después de recibir un mensaje TCP, llamará a la función __inet_lookup_skb para encontrar el socket al que pertenece el mensaje TCP.

int tcp_v4_rcv(struct sk_buff *skb)
{
 ...
  
 sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
 if (!sk)
  goto no_tcp_socket;
 ...
}

La función __inet_lookup_skb primero busca el socket en el estado de establecimiento de la conexión (__inet_lookup_establecido) y solo busca el socket de escucha (__inet_lookup_listener) si no hay coincidencia.

imagen

La implementación de la función de buscar el conector de escucha (__inet_lookup_listener) es calcular un valor hash según la dirección de destino y el puerto de destino, y luego encontrar el conector correspondiente al puerto de escucha en la tabla hash.

En este caso, el servidor no llamó a la función de escucha, por lo que, naturalmente, no puede encontrar el conector que escucha el puerto.

Por lo tanto, la función __inet_lookup_skb finalmente no puede encontrar el socket correspondiente, por lo que salta a no_tcp_socket.

imagen

En este manejo de errores, siempre que la "suma de verificación" del mensaje recibido (skb) esté bien, el kernel llamará a tcp_v4_send_reset para enviar RST para terminar la conexión.

Hasta ahora, se ha analizado todo el proceso del código fuente.

De hecho, muchos problemas de red, todos pueden hacer sus propios experimentos para encontrar la respuesta.

imagen

¿Se puede establecer una conexión TCP sin escuchar?

La pregunta del título ha sido respondida antes, ahora miramos otra pregunta similar.

Leí las noticias del grupo antes y vi que a algunos lectores se les hizo esa pregunta cuando entrevistaban a Tencent.

¿Se puede establecer una conexión TCP sin usar listen?

La respuesta es sí, el cliente puede conectarse para formar una conexión (autoconexión TCP), o dos clientes pueden enviarse solicitudes entre sí para establecer una conexión al mismo tiempo (TCP se abre al mismo tiempo), ambos Las situaciones tienen una cosa en común, es decir, la conexión se puede establecer sin la participación del servidor, es decir, sin la escucha .

Entonces no hay escucha, ¿por qué se puede establecer una conexión?

Sabemos que cuando se ejecuta el método de escucha, se crearán una cola semiconectada y una cola completamente conectada.

Durante el protocolo de enlace de tres vías, la información de conexión se almacenará temporalmente en estas dos colas.

Entonces, para formar una conexión, la premisa es que tenga un lugar para almacenarlo, de modo que pueda encontrar el enchufe correspondiente según el puerto IP + y otra información al darse la mano.

Entonces, ¿el cliente tendrá una cola semi-conectada?

Obviamente no, porque el cliente no ejecuta listen, porque tanto la cola semiconectada como la cola completamente conectada son creadas automáticamente por el kernel cuando se ejecuta el método listen.

Pero el kernel también tiene una tabla hash global, que se puede usar para almacenar información de conexión de sock.

Esta tabla hash global en realidad se subdivide en hash, bhash y listen_hash, etc., pero debido a que es demasiado detallada, todos entienden que un hash global es suficiente.

En el caso de la autoconexión TCP, el cliente colocará su propia información de conexión en la tabla hash global al final del método de conexión y luego enviará la información.Cuando el mensaje regrese a la capa de transporte TCP a través de la dirección de bucle invertido, De acuerdo con la información del puerto IP +, la información se extraerá del hash global nuevamente. Entonces, los paquetes de protocolo de enlace van y vienen, y finalmente la conexión se establece con éxito .

La situación de abrir TCP al mismo tiempo es similar, excepto que un cliente se convierte en dos clientes.

Haz un experimento

El código de autoconexión del cliente, el socket TCP puede conectar la dirección y el puerto de su propio enlace:

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

#define LOCAL_IP_ADDR		(0x7F000001) // IP 127.0.0.1
#define LOCAL_TCP_PORT		(34567) // 端口

int main(void)
{
    
    
	struct sockaddr_in local, peer;
	int ret;
	char buf[128];
	int sock = socket(AF_INET, SOCK_STREAM, 0);

	memset(&local, 0, sizeof(local));
	memset(&peer, 0, sizeof(peer));

	local.sin_family = AF_INET;
	local.sin_port = htons(LOCAL_TCP_PORT);
	local.sin_addr.s_addr = htonl(LOCAL_IP_ADDR);

	peer = local;	

    int flag = 1;
    ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
    if (ret == -1) {
    
    
        printf("Fail to setsocket SO_REUSEADDR: %s\n", strerror(errno));
        exit(1);
    }

	ret = bind(sock, (const struct sockaddr *)&local, sizeof(local));
	if (ret) {
    
    
		printf("Fail to bind: %s\n", strerror(errno));
		exit(1);
	}
	
	ret = connect(sock, (const struct sockaddr *)&peer, sizeof(peer));
	if (ret) {
    
    
		printf("Fail to connect myself: %s\n", strerror(errno));
		exit(1);
	}
	
	printf("Connect to myself successfully\n");

    //发送数据
	strcpy(buf, "Hello, myself~");
	send(sock, buf, strlen(buf), 0);

	memset(buf, 0, sizeof(buf));
	
	//接收数据
	recv(sock, buf, sizeof(buf), 0);
	printf("Recv the msg: %s\n", buf);

    sleep(1000);
	close(sock);
	return 0;
}

Compilar y ejecutar:

Utilice el comando netstat para ordenar al cliente que se conecte automáticamente a la conexión TCP:

inserte la descripción de la imagen aquí
En la captura de pantalla, podemos ver que el socket TCP se ha "conectado" con éxito y ha enviado y recibido paquetes de datos. La salida de netstat demuestra que las direcciones y los puertos de ambos extremos del TCP son exactamente iguales.

Supongo que te gusta

Origin blog.csdn.net/qq_34827674/article/details/126194533
Recomendado
Clasificación