(Notas de programación de red): proceso de establecimiento de la conexión TCP, ventana deslizante, ideas de empaquetado de funciones, paquetes adhesivos y casos simultáneos

Tabla de contenido

Tres apretón de manos y cuatro manos agitadas

Tamaño de la ventana: se refiere al tamaño del búfer

Ventana deslizante (control de flujo TCP)

mss 和 MTU

Ideas de empaque de funciones

Concepto de bolsa pegajosa

Servidor altamente concurrente

Tres apretón de manos y cuatro manos agitadas

  • Apretón de manos de tres vías: el establecimiento de una conexión requiere un proceso de apretón de manos de tres vías

  • Cuatro ondas: la desconexión requiere cuatro ondas
  • TCP es una transmisión de datos segura orientada a la conexión
    • Cuando el cliente y el servidor están establecidos, deben pasar por un proceso de protocolo de enlace de tres vías. Cuando el cliente y el servidor están desconectados, deben pasar por cuatro manos agitadas. La siguiente figura muestra el protocolo de enlace de tres vías entre el cliente y el servidor para establecer una conexión, datos Todo el proceso de transmisión y desconexión de cuatro ondas.
  • Tiempo de TCP:

  • El significado del gráfico
    • SYN: significa solicitud
    • ACK: significa confirmación
    • mss: Representa el tamaño máximo de segmento. Si un segmento es demasiado grande y supera la longitud máxima de trama de la capa de enlace después de encapsularse en una trama, debe fragmentarse en la capa IP. Para evitar esta situación, el cliente declara su tamaño máximo de segmento. Se recomienda que el segmento enviado desde el lado del servidor no exceda esta longitud.
  • El SYN enviado por el servidor y el SYN enviado por el propio cliente también ocuparán 1 bit
    • En la figura anterior, ACK representa el número de secuencia de confirmación, el valor del número de secuencia de confirmación es el valor del número de secuencia enviado por la otra parte + la longitud de los datos, 
    • Se presta especial atención a que SYN y FIN también ocuparán uno
  • Nota:
    • SYS -----> sincrónico
    • ACK -----> reconocimiento
    • FIN ------> terminar
  • El apretón de manos de tres vías y el movimiento de manos de cuatro veces se implementan en el kernel

Formato de datagrama TCP

Tamaño de la ventana: se refiere al tamaño del búfer

  • La bandera SYN ya no se necesita cuando se comunica, solo se necesita cuando se solicita una conexión
  • El número de secuencia aleatorio seq cuando se transmiten datos es el valor del número de secuencia aleatorio del último ACK enviado a la otra parte, y el ACK enviado a la otra parte es el valor del ACK que se acaba de enviar a la otra parte la última vez.

  • El paquete de confirmación ACK enviado en la figura representa un acuse de recibo del envío de datos a la otra parte, lo que indica que he recibido todos los datos que usted envió y le dice a la otra parte que envíe los datos comenzando con el número de secuencia la próxima vez.
  • Dado que cada vez que envíe datos, recibirá el paquete de confirmación de la otra parte, para que pueda confirmar si la otra parte lo ha recibido. Si no recibe el paquete de confirmación de la otra parte, se retransmitirá
    • mss: Longitud máxima del mensaje, dígale a la otra parte cuánto puedo recibir como máximo a la vez, no puede exceder esta longitud
    • ganar: significa decirle a la otra parte cuál es el tamaño máximo de caché aquí

Ventana deslizante (control de flujo TCP)

  • Función principal: la ventana deslizante es principalmente para control de flujo
  • Vea la figura a continuación: Si el extremo de envío envía más rápido, el extremo de recepción procesará los datos más lentamente después de recibir los datos, y el tamaño del búfer de recepción es fijo, lo que hará que el búfer de recepción esté lleno y pierda datos. El protocolo TCP resuelve este problema mediante el mecanismo de "Ventana deslizante".

  1. El remitente inicia una conexión y declara que el tamaño máximo del segmento es 1460, el número de secuencia inicial es 0 y el tamaño de la ventana es 4K, lo que significa que "mi búfer de recepción tiene 4K bytes libres y los datos que envías no deben exceder los 4K". El extremo receptor responde a la solicitud de conexión y declara que el tamaño máximo del segmento es 1024, el número de secuencia inicial es 8000 y el tamaño de la ventana es 6 K. El remitente responde y finaliza el apretón de manos de tres vías.
  2. El remitente envía los segmentos 4 a 9, cada uno con datos de 1 K. El remitente sabe que el búfer del receptor está lleno de acuerdo con el tamaño de la ventana, por lo que deja de enviar datos.
  3. El programa de aplicación en el extremo receptor recoge datos 2K y el búfer receptor tiene 2K libres nuevamente, y el extremo receptor envía el segmento 10 y declara que el tamaño de la ventana es 2K mientras responde que ha recibido datos 6K.
  4. El programa de aplicación en el extremo receptor quita datos 2K nuevamente, el búfer receptor tiene 4K libres y el extremo receptor envía el segmento 11 para volver a declarar el tamaño de la ventana como 4K.
  5. El remitente envía los segmentos 12-13, cada uno con datos de 2K, y el segmento 13 también contiene el bit FIN.
  6. El extremo receptor responde a los datos 2K recibidos (6145-8192), además el bit FIN ocupa un número de secuencia de 8193, por lo que el número de secuencia de respuesta es 8194, la conexión está en un estado medio cerrado y el extremo receptor también declara que el tamaño de la ventana es 2K.
  7. La aplicación en el extremo receptor quita datos de 2K y el extremo receptor vuelve a declarar el tamaño de la ventana como 4K.
  8. La aplicación en el extremo receptor toma los datos restantes de 2K, el búfer de recepción está completamente vacío y el extremo receptor vuelve a declarar el tamaño de la ventana como 6K.
  9. La aplicación en el extremo receptor decide cerrar la conexión después de quitar todos los datos El segmento de envío 17 contiene el bit FIN, y el extremo de envío responde y la conexión se cierra completamente.
  10. La figura anterior utiliza pequeños cuadrados en el extremo de recepción para indicar los datos de 1K. Los pequeños cuadrados sólidos indican los datos recibidos y el cuadro discontinuo indica el búfer de recepción. Por lo tanto, los cuadrados huecos en el cuadro discontinuo indican el tamaño de la ventana, como se puede ver en la figura , A medida que la aplicación recoge datos, el marco punteado se desliza hacia la derecha, por lo que se denomina ventana deslizante.
  • También se puede ver en este ejemplo que el remitente envía datos a una K y una K, mientras que la aplicación en el extremo receptor puede retirar datos a dos K ​​y dos K. Por supuesto, también es posible retirar datos de 3K o 6K a la vez, o solo a la vez. Varios bytes de datos. En otras palabras, los datos que ve la aplicación son un todo o un flujo. En la comunicación subyacente, estos datos pueden dividirse en muchos paquetes de datos para ser enviados, pero ¿cuántos bytes tiene un paquete de datos para la aplicación? El programa no es visible, por lo que el protocolo TCP es un protocolo orientado a la transmisión. UDP es un protocolo orientado a mensajes. Cada segmento UDP es un mensaje. La aplicación debe extraer datos en unidades de mensajes y no puede extraer ningún byte de datos a la vez. Esto es muy diferente de TCP.
  • En la figura, ganar significa decirle a la otra parte cuál es el tamaño del búfer en este lado, mss significa decirle a la otra parte cuántos datos puedo recibir como máximo una vez, y es mejor que no exceda esta longitud.
  • Cuando el cliente envía un paquete al servidor, no necesariamente tiene que esperar hasta que el servidor devuelve un paquete de respuesta. Dado que el cliente conoce el tamaño de la ventana del servidor, puede continuar enviando varias veces. Cuando los datos enviados alcancen el tamaño de la ventana de la otra parte, no se volverán a enviar. Debe esperar a que la otra parte procese y la otra parte puede continuar enviando después del procesamiento.

mss 和 MTU

  • MTU : Unidad de transmisión máxima
  • MTU: Término de comunicación Unidad de transmisión máxima (Unidad de transmisión máxima, MTU)
    • Se refiere al tamaño máximo de paquete de datos (en bytes) que puede atravesar una determinada capa de un protocolo de comunicación.
    • El parámetro máximo de la unidad de transmisión suele estar relacionado con la interfaz de comunicación (tarjeta de interfaz de red, puerto serie, etc.). Si este valor es demasiado grande, se retransmitirá una mayor cantidad de datos durante la pérdida y retransmisión de paquetes. El valor máximo en la figura es 1500. De hecho, Es un valor de experiencia.

  • mss: La longitud máxima del mensaje , justo cuando se establece la conexión , le dice a la otra parte cuántos datos puedo recibir y no hay mss en el proceso de comunicación de datos.

Ideas de empaque de funciones

  • La idea de excepciones de manejo de encapsulación de funciones
    • Combine man-page y errno para encapsulación.
    • Al encapsular el nombre, puede poner en mayúscula la letra del nombre de la primera función. Por ejemplo, socket se puede encapsular en Socket, por lo que puede presionar shift + k para buscar, shift + k no distingue entre mayúsculas y minúsculas cuando busca descripciones de funciones, y puede usar la página man Compruebe, la página de manual no distingue entre mayúsculas y minúsculas.
  • Funciones que pueden causar bloqueo como aceptar y leer
    • Si es interrumpida por una señal, debido a que la prioridad de la señal es mayor, la señal se procesará primero. Una vez finalizado el procesamiento de la señal, se desbloqueará la aceptación o lectura y luego se devolverá. En este momento, el valor de retorno es -1, establecer errno = EINTR;
    • errno = ECONNABORTED indica que la conexión fue interrumpida y anormal .
  • errno 宏
    • El archivo /usr/include/asm-generic/errno.h contiene todas las macros de errno y la información correspondiente de descripción del error.
  • wrap.h
#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>

void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int tcp4bind(short port,const char *IP);
#endif
  • wrap.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>

void perr_exit(const char *s)
{
	perror(s);
	exit(-1);
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
	int n;

again:
	if ((n = accept(fd, sa, salenptr)) < 0) {
		if ((errno == ECONNABORTED) || (errno == EINTR))
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}

int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

	if ((n = bind(fd, sa, salen)) < 0)
		perr_exit("bind error");

    return n;
}

int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

	if ((n = connect(fd, sa, salen)) < 0)
		perr_exit("connect error");

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

	if ((n = listen(fd, backlog)) < 0)
		perr_exit("listen error");

    return n;
}

int Socket(int family, int type, int protocol)
{
	int n;

	if ((n = socket(family, type, protocol)) < 0)
		perr_exit("socket error");

	return n;
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ( (n = read(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ( (n = write(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

int Close(int fd)
{
    int n;
	if ((n = close(fd)) == -1)
		perr_exit("close error");

    return n;
}

/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{
	size_t  nleft;              //usigned int 剩余未读取的字节数
	ssize_t nread;              //int 实际读到的字节数
	char   *ptr;

	ptr = vptr;
	nleft = n;

	while (nleft > 0) {
		if ((nread = read(fd, ptr, nleft)) < 0) {
			if (errno == EINTR)
				nread = 0;
			else
				return -1;
		} else if (nread == 0)
			break;

		nleft -= nread;
		ptr += nread;
	}
	return n - nleft;
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;
	const char *ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;
			else
				return -1;
		}

		nleft -= nwritten;
		ptr += nwritten;
	}
	return n;
}

static ssize_t my_read(int fd, char *ptr)
{
	static int read_cnt;
	static char *read_ptr;
	static char read_buf[100];

	if (read_cnt <= 0) {
again:
		if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
			if (errno == EINTR)
				goto again;
			return -1;
		} else if (read_cnt == 0)
			return 0;
		read_ptr = read_buf;
	}
	read_cnt--;
	*ptr = *read_ptr++;

	return 1;
}

ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t n, rc;
	char    c, *ptr;

	ptr = vptr;
	for (n = 1; n < maxlen; n++) {
		if ( (rc = my_read(fd, &c)) == 1) {
			*ptr++ = c;
			if (c  == '\n')
				break;
		} else if (rc == 0) {
			*ptr = 0;
			return n - 1;
		} else
			return -1;
	}
	*ptr  = 0;

	return n;
}

int tcp4bind(short port,const char *IP)
{
    struct sockaddr_in serv_addr;
    int lfd = Socket(AF_INET,SOCK_STREAM,0);
    bzero(&serv_addr,sizeof(serv_addr));
    if(IP == NULL){
        //如果这样使用 0.0.0.0,任意ip将可以连接
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    }else{
        if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){
            perror(IP);//转换失败
            exit(1);
        }
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port   = htons(port);
    Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    return lfd;
}

Concepto de bolsa pegajosa

  • Paquete fijo: múltiples transmisiones de datos, conexión de extremo a extremo, el extremo receptor no puede distinguir correctamente cuánto se envía por primera vez y cuánto se envía por segunda vez.
  • ¿Análisis y solución del problema del paquete pegajoso?
    • Esquema 1: encabezado del paquete + datos
      • Como longitud de datos de 4 bits + datos -----------> 00101234567890 
      • Donde 0010 representa la longitud de los datos y 1234567890 representa 10 bytes de datos.
      • Además, el remitente y el receptor pueden negociar una estructura de mensaje más compleja, lo que equivale a un acuerdo acordado por ambas partes.
    • Opción 2: agregar marcadores finales.
      • Por ejemplo, el último carácter al final es \ n \ $, etc.
    • Esquema 3: longitud fija del paquete de datos
      • Si el remitente y el receptor acuerdan enviar solo 128 bytes de contenido cada vez, el receptor puede recibir una longitud fija de 128 bytes.

Servidor altamente concurrente

  • Cómo admitir varios clientes --- admitir varios servidores simultáneos
  • Dado que las funciones de aceptar y leer están bloqueadas, por ejemplo, al leer, no se puede llamar a accept para aceptar nuevas conexiones, y cuando accept está bloqueado esperando, no puede leer datos.
  • Solución : al utilizar varios procesos, puede dejar que el proceso padre acepte nuevas conexiones y dejar que el proceso hijo maneje la comunicación con el cliente
    • Idea: Deje que el proceso padre acepte aceptar la nueva conexión, luego bifurque el proceso hijo, deje que el proceso hijo maneje la comunicación y salga después de que se procese el proceso hijo. El proceso padre utiliza la señal SIGCHLD para reciclar el proceso hijo.
  • Flujo de procesamiento:
1 创建socket, 得到一个监听的文件描述符lfd---socket()
2 将lfd和IP和端口port进行绑定-----bind();
3 设置监听----listen()
4 进入while(1)
  {
  	//等待有新的客户端连接到来
  	cfd = accept();
  	
  	//fork一个子进程, 让子进程去处理数据
  	pid = fork();
  	if(pid<0)
  	{
  		exit(-1);
  	}
  	else if(pid>0)
  	{
  		//关闭通信文件描述符cfd
  		close(cfd);
  	}
  	else if(pid==0)
  	{
  		//关闭监听文件描述符
  		close(lfd);
  		
  		//收发数据
  		while(1)
  		{
  			//读数据
  			n = read(cfd, buf, sizeof(buf));
  			if(n<=0)
  			{
  				break;
  			}
  			
  			//发送数据给对方
  			write(cfd, buf, n);
  		}
  		
  		close(cfd);
  		
  		//下面的exit必须有, 防止子进程再去创建子进程
  		exit(0);
  	}
  }
  close(lfd);
  • Características que deben agregarse: el proceso principal utiliza la señal SIGCHLD para completar el reciclaje del proceso secundario
  • Nota: La función de aceptar o leer es una función de bloqueo y será interrumpida por la señal. No debe considerarse como un error en este momento, errno = EINTR

Código de muestra

//多进程版本的网络服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include "wrap.h"

int main()
{
	//创建socket
	int lfd = Socket(AF_INET, SOCK_STREAM, 0);
	
	//绑定
	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
	
	//设置监听
	Listen(lfd, 128);
	
	pid_t pid;
	int cfd;
	char sIP[16];
	socklen_t len;
	struct sockaddr_in client;
	while(1)
	{
		//接受新的连接
		len = sizeof(client);
		memset(sIP, 0x00, sizeof(sIP));
		cfd = Accept(lfd, (struct sockaddr *)&client, &len);
		printf("client:[%s] [%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));
		
		//接受一个新的连接, 创建一个子进程,让子进程完成数据的收发操作
		pid = fork();
		if(pid<0)
		{
			perror("fork error");
			exit(-1);
		}
		else if(pid>0)
		{
			//关闭通信文件描述符cfd
			close(cfd);			
		}
		else if(pid==0)
		{
			//关闭监听文件描述符
			close(lfd);
			
			int i=0;
			int n;
			char buf[1024];
			
			while(1)
			{
				//读数据
				n = Read(cfd, buf, sizeof(buf));
				if(n<=0)
				{
					printf("read error or client closed, n==[%d]\n", n);
					break;
				}
				//printf("client:[%s] [%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));
				printf("[%d]---->:n==[%d], buf==[%s]\n", ntohs(client.sin_port), n, buf);
				
				//将小写转换为大写
				for(i=0; i<n; i++)
				{
					buf[i] = toupper(buf[i]);
				}
				//发送数据
				Write(cfd, buf, n);
			}
			
			//关闭cfd
			close(cfd);
			exit(0);
		}
	}
	
	//关闭监听文件描述符
	close(lfd);
	
	return 0;
}
  • Solución: use multihilo
  • Deje que el subproceso principal acepte nuevas conexiones y deje que los subprocesos secundarios manejen la comunicación con el cliente; use el subproceso múltiple para establecer el subproceso en el atributo de separación y deje que el subproceso reclame recursos después de salir.
  • Versión multiproceso del proceso de desarrollo del servidor
{
	1 创建socket, 得到一个监听的文件描述符lfd---socket()
	2 将lfd和IP和端口port进行绑定-----bind();
	3 设置监听----listen() 
	4 while(1)
	  {
	  	//接受新的客户端连接请求
	  	cfd = accept();
	  	
	  	//创建一个子线程
	  	pthread_create(&threadID, NULL, thread_work, &cfd);
	  	
	  	//设置线程为分离属性
	  	pthread_detach(threadID);
	  	
	  }
	  
	  close(lfd);
}	  	
	  
子线程执行函数:
	void *thread_work(void *arg)
	{
		//获得参数: 通信文件描述符
		int cfd = *(int *)arg;
		
		while(1)
		{
			//读数据
			n = read(cfd, buf, sizeof(buf));
			if(n<=0)
			{
				break;
			}
			
			//发送数据
			write(cfd, buf, n);
		}
		
		close(cfd);
	}
  • ¿Pueden los subprocesos secundarios cerrar lfd?
    • El subproceso secundario no puede cerrar el descriptor de archivo de escucha lfd, porque el subproceso secundario y el subproceso principal comparten el descriptor de archivo en lugar de copiarlo.
  • ¿Puede el hilo principal cerrar cfd?
    • El hilo principal no puede cerrar el cfd. El hilo principal y el hilo secundario comparten un cfd en lugar de copiarlo. Después del cierre, el cfd se cerrará realmente.
  • Varios subprocesos secundarios comparten cfd, lo que hace que cfd sea el valor de la última conexión y se sobrescriba el valor anterior.
struct INFO
{
	int cfd;
	pthread_t threadID;
	struct sockaddr_in client;
};
struct INFO info[100];

//初始化INFO数组
for(i=0; i<100; i++)
{
	info[i].cfd=-1;
}


for(i=0; i<100; i++)
{
	if(info[i].cfd==-1)
	{
		//这块内存可以使用
	}
}

if(i==100)
{
	//拒绝接受新的连接
	close(cfd);
}

Código de muestra

//多线程版本的高并发服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include <pthread.h>
#include "wrap.h"

//子线程回调函数
void *thread_work(void *arg)
{
	int cfd = *(int *)arg;
	printf("cfd==[%d]\n", cfd);
	
	int i;
	int n;
	char buf[1024];
	
	while(1)
	{
		//read数据
		memset(buf, 0x00, sizeof(buf));
		n = Read(cfd, buf, sizeof(buf));
		if(n<=0)
		{
			printf("read error or client closed,n==[%d]\n", n);
			break;
		}
		printf("n==[%d], buf==[%s]\n", n, buf);
		
		for(i=0; i<n; i++)
		{
			buf[i] = toupper(buf[i]);
		}
		//发送数据给客户端
		Write(cfd, buf, n);	
	}
	
	//关闭通信文件描述符
	close(cfd);
	
	pthread_exit(NULL);
}
int main()
{
	//创建socket
	int lfd = Socket(AF_INET, SOCK_STREAM, 0);
	
	//设置端口复用
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
	
	//绑定
	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
	
	//设置监听
	Listen(lfd, 128);
	
	int cfd;
	pthread_t threadID;
	while(1)
	{
		//接受新的连接
		cfd = Accept(lfd, NULL, NULL);
		
		//创建子线程
		pthread_create(&threadID, NULL, thread_work, &cfd);
		
		//设置子线程为分离属性
		pthread_detach(threadID);
	}

	//关闭监听文件描述符
	close(lfd);
	
	return 0;
}
  • [Nota]: consulte el tutorial de dark horse linux C ++

Supongo que te gusta

Origin blog.csdn.net/baidu_41388533/article/details/109083939
Recomendado
Clasificación