[Arquitectura de comunicación de red concurrente alta] 1. El servidor tcp para la conexión de un solo cliente bajo Linux

Tabla de contenido

1. Lista de funciones

1. método de enchufe

2. método de enlace

3. método de escucha

4. método de aceptación (función de bloqueo)

5. método recv (función de bloqueo)

6. método de envío

7. método de cierre

8. método htonl

9. método de toneladas

10. método fcntl

Segundo, implementación de código

1. Servidor de bloqueo

El flujo general del programa del servidor TCP

El flujo general del programa cliente TCP

código completo

2. Servidor sin bloqueo

Flujo general del servidor TCP sin bloqueo

código completo


1. Lista de funciones

1. método de enchufe

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

Función

  • Crea un socket para la comunicación y devuelve un descriptor de archivo que apunta a ese socket.

parámetro

  1. dominio: especifica la familia de protocolos del socket. Los valores comunes son AF_INET (IPv4) y AF_INET6 (IPv6).
  2. type: Especifica el tipo de socket. Los valores comunes son SOCK_STREAM (flujo de bytes confiable orientado a la conexión) y SOCK_DGRAM (datagrama sin conexión).
  3. protocolo: especifica el protocolo. Por lo general, se usa 0, lo que significa la selección predeterminada.

valor de retorno

  • Si tiene éxito, devuelve el descriptor de archivo para el nuevo socket. Si ocurre un error, se devuelve -1 y se establece errno.

2. método de enlace

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

Función

  • Vincule un socket a una dirección IP y un puerto específicos.

parámetro

  1. sockfd: el descriptor de socket devuelto por socket.
  2. addr: una estructura que apunta a la dirección local para vincular (generalmente una estructura sockaddr_in o sockaddr_in6).
  3. addrlen: La longitud de la dirección local (normalmente sizeof(struct sockaddr_in) o sizeof(struct sockaddr_in6)).

valor de retorno

  • Devuelve 0 si tiene éxito. Si ocurre un error, se devuelve -1 y se establece errno.

3. método de escucha

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

Función

  • Comience a escuchar solicitudes de conexión en el socket especificado.

parámetro

  1. sockfd: el descriptor de socket devuelto por socket.
  2. backlog: La longitud máxima de la cola de espera para las conexiones. Si la cola está llena cuando llega la solicitud de conexión, el cliente puede recibir un error indicado por ECONNREFUSED, y si el protocolo subyacente admite la retransmisión, la solicitud puede ignorarse para que la conexión se pueda volver a intentar más tarde si la conexión tiene éxito.

valor de retorno

  • Devuelve 0 si tiene éxito. Si ocurre un error, se devuelve -1 y se establece errno.

4. método de aceptación (función de bloqueo)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

Función

  • Acepte una solicitud de conexión y devuelva un nuevo descriptor de socket para comunicarse con el cliente.

parámetro

  1. sockfd: el descriptor de socket devuelto por socket.
  2. addr: Puntero a la estructura utilizada para almacenar la dirección del cliente. Por lo general, se especifica como una estructura struct sockaddr_in.
  3. addrlen: se usa para pasar la longitud de la estructura addr.

valor de retorno

  • Si tiene éxito, estas llamadas al sistema devuelven el descriptor de archivo del socket aceptado (un número entero no negativo). Si se produce un error, devuelva -1, establezca errno de forma adecuada y mantenga addrlen sin cambios.

5. método recv (función de bloqueo)

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

Función

  • Recibir datos de un enchufe conectado.

parámetro

  1. sockfd: el descriptor de socket devuelto por accept.
  2. buf: búfer para recibir datos.
  3. len: La longitud del búfer.
  4. banderas: banderas para recibir operaciones, generalmente establecidas en 0.

valor de retorno

  • Devuelve el número de bytes recibidos o -1 si se produjo un error. Si ocurre un error, se establece errno para indicar el error. Cuando se cierra la conexión del cliente, el valor de retorno será 0.

6. método de envío

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

Función

  • Enviar datos a un socket conectado.

parámetro

  1. sockfd: el descriptor de socket devuelto por accept.
  2. buf: El búfer que contiene los datos a enviar.
  3. len: la longitud de los datos a enviar.
  4. banderas: Banderas de la operación de envío, generalmente establecidas en 0.

valor de retorno

  • Si tiene éxito, estas llamadas devolverán la cantidad de bytes enviados. Si ocurre un error, se devuelve -1 y se establece errno.

7. método de cierre

#include <unistd.h>

int close(int fd);

Función

  • Cierre el descriptor de archivo y libere los recursos relacionados

parámetro

  1. fd: El descriptor de archivo para cerrar.

valor de retorno

  • Devuelve cero en caso de éxito. Si ocurre un error, se devuelve -1 y se establece errno.

8. método htonl

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

Función

  • Convierte un entero sin signo de 32 bits (4 bytes) en orden de bytes de host en un entero en orden de bytes de red.

9. método de toneladas

#include <arpa/inet.h>

uint16_t htons(uint16_t hostshort);

Función

  • Convierte un entero corto con signo de 16 bits (2 bytes) en orden de bytes de host en un entero en orden de bytes de red.

10. método fcntl

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

Función

  • El comportamiento y los atributos de los descriptores de archivos operativos se pueden establecer en E/S sin bloqueo.

parámetro

  1. fd: El descriptor de archivo a establecer.
  2. cmd: el comando para realizar operaciones en fd, los comandos comunes son los siguientes, generalmente usan F_GETFL, F_SETFL.
    1. F_DUPFD: Copiar descriptor de archivo.
    2. F_GETFD: Obtener indicadores de descriptores de archivos.
    3. F_SETFD: establece el indicador del descriptor de archivo.
    4. F_GETFL: Obtener indicadores de estado del archivo.
    5. F_SETFL: establece el indicador de estado del archivo.
    6. F_GETLK: Adquirir un archivo de bloqueo.
    7. F_SETLK: Establecer bloqueo de archivos.
    8. F_SETLKW: establezca un bloqueo de archivo y espere si el bloqueo no está disponible.

valor de retorno

  • Para una llamada exitosa, el valor devuelto depende del comando de operación, y si ocurre un error, se devuelve -1 y errno se establece de manera adecuada.

Segundo, implementación de código

1. Servidor de bloqueo

El flujo general del programa del servidor TCP

  1. Crear socket: socketcree un socket TCP mediante una llamada al sistema. Un socket es un punto final para la comunicación de red.

  2. Vincular dirección y puerto (Bind): Vincule la dirección IP y el número de puerto del servidor al socket y use bindllamadas al sistema para completar la operación de vinculación.

  3. Solicitud de conexión de escucha (Listen): Ponga el socket en estado de escucha, esperando la solicitud de conexión del cliente. Use listenuna llamada al sistema para establecer la longitud de la cola de escucha para un socket.

  4. Aceptar solicitud de conexión (Accept): cuando un cliente solicita una conexión, utilice acceptuna llamada al sistema para aceptar la solicitud de conexión. Esto creará un nuevo socket para comunicarse con el cliente, mientras que el socket de escucha original continúa escuchando nuevas solicitudes de conexión.

  5. Comunicar: use el socket recibido para la comunicación de datos. Los datos se pueden enviar y recibir mediante llamadas read/recval sistema write/sendu otras funciones de E/S avanzadas.

  6. Cierra el socket (Cerrar): cuando finaliza la comunicación, usa closela llamada del sistema para cerrar el socket y liberar los recursos relacionados.

Código de muestra

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
    // 创建套接字
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);

    // 绑定地址和端口
    struct sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;    //ipv4
    serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddress.sin_port = htons(8888);
    bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress));

    // 监听连接请求
    listen(serverSocket, 5);

    // 接受连接请求
    int clientSocket = accept(serverSocket, NULL, NULL);

    // 进行通信
    char buffer[1024];
    read(clientSocket, buffer, sizeof(buffer));
    printf("Received message: %s\n", buffer);

    // 关闭套接字
    close(clientSocket);
    close(serverSocket);

    return 0;
}

El flujo general del programa cliente TCP

  1. Crear socket: socketcree un socket TCP mediante una llamada al sistema.

  2. Establecer la dirección del servidor y el número de puerto: use struct sockaddr_inuna estructura para representar la dirección y el número de puerto del servidor. Rellene esta estructura con la dirección IP y el número de puerto del servidor.

  3. Conéctese al servidor (Conectar): use connectla llamada del sistema para conectar el socket al servidor. Pase la dirección del servidor y el número de puerto como parámetros a connectla función.

  4. Comunicación de datos (Comunicar): use el enchufe conectado para leer y escribir datos. Los datos se pueden leer y enviar usando readlas llamadas al sistema y .write

  5. Cerrar el socket (Cerrar): cuando se completa la comunicación, use closela llamada del sistema para cerrar el socket y liberar los recursos relacionados.

Código de muestra

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
    // 创建套接字
    int clientSocket = socket(AF_INET, SOCK_STREAM, 0);

    // 设置服务器地址和端口号
    struct sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8888);
    serverAddress.sin_addr.s_addr = inet_addr("服务器IP地址");

    // 连接服务器
    connect(clientSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress));

    // 进行数据通信
    char *message = "Hello, server!";
    send(clientSocket, message, strlen(message),0);

    // 关闭套接字
    close(clientSocket);

    return 0;
}

código completo

  • Tanto accept como recv son funciones de bloqueo, al aceptar se bloquea la conexión del cliente, y al recv se bloquea para leer los datos del cliente conectado.
  • Para lograr una comunicación continua con el cliente, recv debe colocarse en un bucle maestro para leer los datos enviados por el cliente todo el tiempo.
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>


#define BUFFER_LENGTH   1024

//初始化服务端,返回其文件描述符
int init_server(int port){
    //返回服务端fd,通常为3,前面0,1,2用于表示标准输入,输出,错误值
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    if(-1 == sfd){
        printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    struct sockaddr_in servAddr;
    memset(&servAddr,0,sizeof(struct sockaddr_in));
    servAddr.sin_family = AF_INET;  //ipv4
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);   //0.0.0.0
    servAddr.sin_port = htons(port);    //端口号
    
    //绑定IP和端口号
    if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in)))
    {
        printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    //监听该套接字上的连接
    if(-1 == listen(sfd,SOMAXCONN))
    {
        printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    return sfd;
}

int main(int argc,char *argv[]){

    if(argc < 2)return -1;

    int port = atoi(argv[1]);   //atoi:将字符串转换为整数
    int sfd = init_server(port);
    printf("server fd: %d\n",sfd);

    struct sockaddr_in clientAddr;
    socklen_t len = sizeof(struct sockaddr_in);
    int cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len);   //阻塞函数
    printf("client fd: %d\n",cfd);
    while (1)
    {
        char data[BUFFER_LENGTH]={0};
        int recvLen = recv(cfd,data,BUFFER_LENGTH,0);    //阻塞函数
        if(recvLen < 0){
            printf("recv client fd %d errno: %d\n",cfd,errno);
        }else if(recvLen == 0){
            printf("client fd %d close\n",cfd);
            close(cfd);     //关闭客户端文件描述符,释放资源
            break;
        }else{
            printf("recv client fd %d data: %s\n",cfd,data);
            send(cfd,data,recvLen,0);
        }
    }

    close(sfd);     //关闭服务端文件描述符,释放资源
    printf("server fd %d close\n",sfd);
    
    return 0;
}

resultado de ejecución

  • Herramienta de prueba: NetAssist  simula la herramienta del cliente para probar el código del servidor.

2. Servidor sin bloqueo

Flujo general del servidor TCP sin bloqueo

  1. Crear socket: socketcree un socket TCP mediante una llamada al sistema.

  2. Establezca el socket en modo sin bloqueo: use fcntlla función F_SETFLpara establecer el indicador de estado de archivo del socket en modo sin bloqueo a través del comando, es decir, use O_NONBLOCKel indicador.

  3. Vincular dirección y puerto (Bind): Vincule la dirección IP y el número de puerto del servidor al socket y use bindllamadas al sistema para completar la operación de vinculación.

  4. Solicitud de conexión de escucha (Escuchar): coloque el socket en el estado de escucha, espere la solicitud de conexión del cliente y use la listenllamada del sistema para establecer la longitud de la cola de escucha del socket.

  5. Aceptar solicitud de conexión (Accept): use acceptllamadas al sistema para aceptar solicitudes de conexión. Esto creará un nuevo socket para comunicarse con el cliente, mientras que el socket de escucha original continúa escuchando nuevas solicitudes de conexión.

  6. Establezca el nuevo zócalo en modo sin bloqueo: de manera similar, use fcntlla función para configurar el nuevo zócalo en modo sin bloqueo.

  7. Comunicación de datos (Comunicar): use sockets sin bloqueo para leer y escribir datos. Los datos se pueden intercambiar read/recvcon llamadas al sistema u otras funciones de E/S sin bloqueo.write/send

  8. Cierra el socket (Cerrar): cuando finaliza la comunicación, usa closela llamada del sistema para cerrar el socket y liberar los recursos relacionados.

Código de muestra

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>

int main() {
    // 创建套接字
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);

    // 设置套接字为非阻塞模式
    int flags = fcntl(serverSocket, F_GETFL, 0);
    fcntl(serverSocket, F_SETFL, flags | O_NONBLOCK);

    // 绑定地址和端口
    struct sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddress.sin_port = htons(8888);
    bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress));

    // 监听连接请求
    listen(serverSocket, 5);

    while (1) {
        // 接受连接请求
        struct sockaddr_in clientAddress;
        socklen_t clientAddressLength = sizeof(clientAddress);
        int clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddress, &clientAddressLength);

        if (clientSocket > 0) {
            // 设置新的套接字为非阻塞模式
            flags = fcntl(clientSocket, F_GETFL, 0);
            fcntl(clientSocket, F_SETFL, flags | O_NONBLOCK);

            // 进行数据通信
            char buffer[1024];
            ssize_t bytesRead = read(clientSocket, buffer, sizeof(buffer));
            if (bytesRead > 0) {
                // 读取到数据
                printf("Received message from client: %s\n", buffer);
            }

            // 关闭客户端套接字
            close(clientSocket);
        }
    }

    // 关闭服务端套接字
    close(serverSocket);

    return 0;
}

código completo

  • Establezca el zócalo en modo sin bloqueo.
  • Lo siguiente se usa para probar si el código anterior configura el servidor para que no bloquee.
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <fcntl.h>


#define BUFFER_LENGTH   1024

int init_server(int port){
    //获取服务端fd,通常为3,前面0,1,2用于指定输入,输出,错误值
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    if(-1 == sfd){
        printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    //设置服务端套接字为非阻塞模式
    int flags = fcntl(sfd,F_GETFL,0);
    fcntl(sfd,F_SETFL,flags | O_NONBLOCK);

    struct sockaddr_in servAddr;
    memset(&servAddr,0,sizeof(struct sockaddr_in));
    servAddr.sin_family = AF_INET;  //ipv4
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);   //0.0.0.0
    servAddr.sin_port = htons(port);
    //服务端绑定ip地址和端口号
    if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in)))
    {
        printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    //监听该套接字上的连接请求
    if(-1 == listen(sfd,SOMAXCONN))
    {
        printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    return sfd;
}

int main(int argc,char *argv[]){

    if(argc < 2)return -1;

    int port = atoi(argv[1]);
    int sfd = init_server(port);
    printf("server fd: %d\n",sfd);

    //接受连接请求
    struct sockaddr_in clientAddr;
    socklen_t len = sizeof(struct sockaddr_in);
    int cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len);
    if(cfd == -1){
        printf("accept error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }
    printf("client fd: %d\n",cfd);
    //设置新的套接字为非阻塞模式
    int flags = fcntl(cfd,F_GETFL,0);
    fcntl(cfd,F_SETFL,flags | O_NONBLOCK);

    while (1)
    {
        char data[BUFFER_LENGTH]={0};
        int recvLen = recv(cfd,data,BUFFER_LENGTH,0);
        if(recvLen < 0){
            printf("recv client fd %d errno: %d\n",cfd,errno);
        }else if(recvLen == 0){
            //客户端断开连接
            printf("client fd %d close\n",cfd);
            close(cfd);     //关闭客户端文件描述符,释放资源
            break;
        }else{
            printf("recv client fd %d data: %s\n",cfd,data);
            send(cfd,data,recvLen,0);
        }
    }

    close(sfd);     //关闭服务端文件描述符,释放资源
    printf("server fd %d close\n",sfd);
    
    return 0;
}

Efecto de carrera

  • Después de compilar y ejecutar correctamente, se informa el siguiente error, lo que hace que el servidor no funcione normalmente.

análisis del problema

  • El código de error correspondiente de "Recurso temporalmente no disponible" (recurso temporalmente no disponible) en Linux es EAGAIN (valor de error 11) o EWOULDBLOCK. Este código de error generalmente ocurre durante las operaciones de E/S sin bloqueo, lo que indica que no hay recursos disponibles actualmente o que la operación está en curso.
  • En la programación de redes, cuando se usa un socket en modo sin bloqueo para una operación de lectura o escritura, si no hay datos disponibles o la operación de escritura no se puede completar de inmediato, es posible que se devuelva este código de error. Esto se debe a que las operaciones de E/S en modo sin bloqueo no son de bloqueo, es decir, se completan inmediatamente o devuelven un código de error sin esperar.

Solución

  • E/S asíncrona (E/S asíncrona): al usar operaciones de E/S asíncronas, puede regresar después de todas las operaciones de E/S sin bloquear el subproceso actual. Linux proporciona funciones de E/S asíncronas como aio_read y aio_write. Con las operaciones de E/S asíncronas, puede registrar funciones de devolución de llamada para recibir una notificación cuando finalice la operación.

Estudio de seguimiento a tratar. . .

Supongo que te gusta

Origin blog.csdn.net/weixin_43729127/article/details/131578334
Recomendado
Clasificación