Programación de aplicaciones en entorno Linux (5): Programación de red

 

I. Introducción

       El clásico mecanismo de comunicación entre procesos (IPC) proporcionado por el sistema Linux: tuberías, memoria compartida, colas de mensajes y semáforos. Estos mecanismos permiten que los procesos que se ejecutan en la misma computadora se comuniquen entre sí y para diferentes computadoras (conectadas a través de un red) La comunicación entre procesos introduce un nuevo mecanismo: comunicación entre procesos de red, los procesos pueden comunicarse entre sí a través de la interfaz de comunicación entre procesos de la red de socket y se pueden utilizar muchos protocolos de red diferentes para comunicarse con la interfaz de socket.

1. Pila de protocolos de red de cinco capas:

Protocolos de capa de aplicación: FTP, HTTP, SMTP

Protocolo de capa de transporte: protocolo TCP, protocolo UDP

Protocolo de capa de red: protocolo IP

El protocolo TCP y el protocolo UDP descritos en este capítulo pertenecen a la capa de transporte. Para conocer las funciones específicas de la estructura de cinco capas, consulte: https://blog.csdn.net/weixin_37719279/article/details/82846226

2. La diferencia entre los protocolos TCP y UDP

TCP proporciona servicios confiables orientados a la conexión a la capa superior, y UDP brinda servicios sin conexión y no confiables a la capa superior. Aunque UDP no es tan preciso como la transmisión TCP, a menudo se usa en escenarios con altos requisitos de tiempo real.

3. Apretón de manos de tres vías de TCP y cuatro manos agitadas

(1) Tres apretones de manos

El primer apretón de manos: al establecer una conexión, el cliente envía un paquete de sincronización (syn = j) al servidor y entra en el estado SYN_SENT, esperando que el servidor confirme; SYN: Sincronizar números de secuencia.

El segundo protocolo de enlace: el servidor recibe el paquete syn, debe confirmar el SYN del cliente (ack = j + 1), y al mismo tiempo enviar un paquete SYN (syn = k), es decir, el paquete SYN + ACK, y el el servidor entra en el estado SYN_RECV;

El tercer protocolo de enlace: el cliente recibe el paquete SYN + ACK del servidor y envía un paquete de reconocimiento ACK (ack = k + 1) al servidor. Después de que se envía el paquete, el cliente y el servidor ingresan al ESTABLECIDO (conexión TCP exitosa ) estado, que se completa tres veces se dan la mano.

(2) Saludar cuatro veces

El proceso del cliente envía un mensaje de liberación de conexión y deja de enviar datos. Suelte el encabezado del mensaje de datos, FIN = 1, su número de secuencia es seq = u (igual al número de secuencia del último byte de los datos que se han transmitido antes más 1), en este momento, el cliente ingresa FIN- WAIT-1 (terminar la espera 1) Estado. TCP estipula que incluso si el segmento FIN no transporta datos, consumirá un número de secuencia.
b. El servidor recibe el mensaje de liberación de conexión y envía un mensaje de reconocimiento, ACK = 1, ack = u + 1, y trae su propio número de serie seq = v. En este momento, el servidor ingresa al estado CLOSE-WAIT (espera cerrada) . El servidor TCP informa al proceso de aplicación de alto nivel que el cliente se libera en la dirección del servidor. En este momento, se encuentra en un estado medio cerrado, es decir, el cliente no tiene datos para enviar, pero si el servidor envía datos, el cliente aún tiene que aceptarlos. Este estado continuará por un tiempo, es decir, la duración de todo el estado CLOSE-WAIT.
c. Después de que el cliente recibe la solicitud de confirmación del servidor, en este momento, el cliente entra en el estado FIN-WAIT-2 (terminación en espera 2), esperando que el servidor envíe un mensaje de liberación de conexión (antes de eso, necesita aceptar los últimos datos).
d. Después de que el servidor envía los datos finales, envía un mensaje de liberación de conexión al cliente, FIN = 1, ack = u + 1, debido a que está en el estado semicerrado, es probable que el servidor envíe más datos, asumiendo este tiempo El número de secuencia es seq = W. En este momento, el servidor entra en el estado LAST-ACK (última confirmación), esperando la confirmación del cliente.
e. Después de que el cliente recibe el mensaje de liberación de conexión del servidor, debe enviar un acuse de recibo, ACK = 1, ack = w + 1, y su número de serie es seq = u + 1, en este momento, el cliente ingresa TIME- Estado de ESPERA (tiempo de espera). Tenga en cuenta que la conexión TCP no se ha liberado en este momento, debe pasar 2 ∗∗ MSL (la vida más larga del segmento de mensaje), y cuando el cliente cancela la TCB correspondiente, entra en estado CERRADO.
f) Siempre que el servidor reciba la confirmación del cliente, ingresará inmediatamente al estado CERRADO. De manera similar, después de que se retira la TCB, la conexión TCP finaliza. Como puede ver, el servidor finaliza la conexión TCP antes que el cliente.

Referencia detallada: https://www.cnblogs.com/bj-mr-li/p/11106390.html

Dos: programación de red

1. Direccionamiento

(1) Orden de bytes

Las aplicaciones a veces necesitan cambiar entre el orden de bytes del procesador y el orden de bytes de la red. Para las aplicaciones, hay 4 funciones que se utilizan para convertir entre el orden de bytes del procesador y el orden de bytes de la red.

"H" significa orden de bytes "host", "n" significa orden de bytes "red", "l" significa entero "largo" (es decir, 4 bytes) y "s" significa "corto" (es decir, 2 bytes) Entero

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostint32); 返回值:以网络字节序表示的32位整数
uint16_t htons(uint16_t hostint16); 返回值:以网络字节序表示的16位整数
uint32_t ntohl(uint32_t netint32);  返回值:以主机字节序表示的32位整数
uint16_t ntohs(uint16_t netint16);  返回值:以主机字节序表示的16位整数

(2) Formato de dirección

        Una dirección identifica el punto final del socket de un dominio de comunicación específico. El formato de la dirección está relacionado con este dominio de comunicación específico. Para permitir que las direcciones en diferentes formatos se pasen a la función del socket, la dirección será forzada a una estructura de dirección general:

struct sockaddr{
    unisgned short  sa_family; /*地址族*/
    char sa_data[14];          /*14字节的协议地址,包含该socket的IP地址和端口号。*/
};

Debido a problemas de compatibilidad del sistema, la nueva estructura de direcciones se utiliza para indicar:

struct sockaddr_in{
    unsigned short          sin_family;     /*地址族*/
    unsigned short int      sin_port;       /*端口号*/
    struct in_addr          sin_addr;       /*IP 地址*/
    unsigned char           sin_zero[8];    /*填充0 以保持与struct sockaddr 同样大小*/
}

(3) Conversión de formato de dirección

Generalmente, los usuarios usan la notación decimal con puntos (o una dirección IPv6 separada por dos puntos) cuando expresan direcciones, pero los valores binarios se usan en la programación de socket de uso común, que requiere combinar estos dos. El valor se convierte. Las funciones utilizadas en IPv4 aquí son inet_aton (), inet_addr () e inet_ntoa (), y las funciones compatibles con IPv4 e IPv6 son inet_pton () e inet_ntop ().

#include <arpa/inet.h>

int inet_pton(int family, const char *strptr, void *addrptr)
int inet_ntop(int family, void *addrptr, char *strptr, size_t len)

family:{
    AF_INET:IPv4 协议
    AF_INET6:IPv6 协议
}
strptr:要转化的值
addrptr:转化后的地址
len:转化后值的大小
返回值:成功0、出错-1

inet_aton es un método mejorado para convertir una dirección IP de cadena en una dirección IP serial de red de 32 bits.

1 La cadena de parámetros de entrada contiene la dirección IP ASCII.
2 El parámetro de salida addr es la estructura que se actualizará con la nueva dirección IP.

Si esta función tiene éxito, el valor de retorno de la función es distinto de cero. Si la dirección de entrada es incorrecta, se devolverá cero. No hay ningún código de error almacenado en errno cuando se usa esta función, por lo que su valor será ignorado.

Ejemplo:

struct sockaddr_in socket_server_addr;

socket_server_addr.sin_family   = AF_INET;
/*主机字节序转换为网络字节序*/
socket_server_addr.sin_port     = htons(SERVER_PORT);
if (inet_aton(“192.168.1.100”, &socket_server_addr.sin_addr) == 0)
{
    printf("invalid server ip\n");
    return -1;
}

(4) Conversión de dirección de nombre

Generalmente, las personas se muestran reacias a memorizar direcciones IP largas durante el uso, especialmente en IPv6, la longitud de la dirección es de hasta 128 bits, entonces es aún más imposible memorizar direcciones IP tan largas una y otra vez. Por lo tanto, usar el nombre de host sería una buena opción. En Linux, también existen algunas funciones que pueden realizar la conversión de nombre de host y dirección, las más comunes son gethostbyname (), gethostbyaddr () y getaddrinfo (), etc., todas las cuales pueden realizar la conversión entre IPv4 e IPv6 direcciones y nombres de host. Entre ellos, gethostbyname () convierte un nombre de host en una dirección IP, y gethostbyaddr () es una operación inversa, que convierte una dirección IP en un nombre de host. Además, getaddrinfo () puede identificar automáticamente direcciones IPv4 y direcciones IPv6.

struct hostent
{
    char *h_name;/*正式主机名*/
    char **h_aliases;/*主机别名*/
    int h_addrtype;/*地址类型*/
    int h_length;/*地址字节长度*/
    char **h_addr_list;/*指向 IPv4 或 IPv6 的地址指针数组*/
}

struct addrinfo
{
    int ai_flags;/*AI_PASSIVE, AI_CANONNAME;*/
    int ai_family;/*地址族*/
    int ai_socktype;/*socket 类型*/
    int ai_protocol;/*协议类型*/
    size_t ai_addrlen;/*地址字节长度*/
    char *ai_canonname;/*主机名*/
    struct sockaddr *ai_addr;/*socket 结构体*/
    struct addrinfo *ai_next;/*下一个指针链表*/
}

ai_flags{
    AI_PASSIVE:该套接口是用作被动地打开
    AI_CANONNAME:通知 getaddrinfo 函数返回主机的名字
}
ai_family{
    AF_INET:IPv4 协议
    AF_INET6:IPv6 协议
    AF_UNSPEC:IPv4 或 IPv6 均可
}
ai_socktype {
    SOCK_STREAM:字节流套接字 socket(TCP)
    SOCK_DGRAM:数据报套接字 socket(UDP)
}
ai_protocol{
    IPPROTO_IP:IP 协议
    IPPROTO_IPV4:IPv4 协议 4 IPv4
    IPPROTO_IPV6:IPv6 协议
    IPPROTO_UDP:UDP
    IPPROTO_TCP:TCP
}
#include <netdb.h>

struct hostent *gethostbyname(const char *hostname)
hostname:主机名
返回值:成功返回hostent类型指针、错误-1

int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints,struct addrinfo **result)
node:网络地址或者网络主机名
service:服务名或十进制的端口号字符串
hints:服务线索
result:返回结果
返回值:成功0、错误-1

(1) Normalmente, antes de llamar a getaddrinfo () en el lado del servidor, ai_flags establece AI_PASSIVE para la función bind () (utilizada para vincular el puerto y la dirección, que se discutirán más adelante), y el nombre de host nombre de nodo generalmente se establece en NULL .
(2) Cuando el cliente llama a getaddrinfo (), ai_flags generalmente no establece AI_PASSIVE, pero el nombre de host nodename y el nombre de servicio servname (puerto) no deben estar vacíos.
(3) Incluso si ai_flags no se establece en AI_PASSIVE, la dirección recuperada se puede vincular. En muchos programas, ai_flags se establece directamente en 0, es decir, los tres bits de bandera no se establecen. En este caso, siempre que el nombre de host y servname se establecen sin problemas, se pueden enlazar correctamente.

Ejemplo:

/* getaddrinfo.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    struct addrinfo hints, *res = NULL;
    int rc;
    memset(&hints, 0, sizeof(hints));

    /*设置 addrinfo 结构体中各参数 */
    hints.ai_flags = AI_CANONNAME;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;
    /*调用 getaddinfo 函数*/
    rc = getaddrinfo("localhost", NULL, &hints, &res);
    if (rc != 0)
    {
        perror("getaddrinfo");
        exit(1);
    }
    else
    {
        printf("Host name is %s\n", res->ai_canonname);
    }
    exit(0);
}

2. Programación básica de socket

(1) descriptor de enchufe

Crea un socket:

#include <sys/socket.h>
int socket(int domain, int type,int protocol);

domain:确定通信的特性,包括地址格式
        AF_INET:IPv4因特网域
        AF_INET6:IPv6因特网域
        AF_UNIX:UNIX域
        AF_UPSPEC:未指定
type:确定套接字类型
        SOCK_DGRAM:固定长度的,无连接的,不可靠的报文传递(UDP)
        SOCK_RAW:IP协议的数据报接口
        SOCK_SEQPACKET:固定长度的,有序的,可靠的,面向连接的报文传递
        SOCK_STREAM:有序的,可靠的,双向的,面向连接的字节流(TCP)
protocol:通常为0,表示为给定的域和套接字类型选择默认协议

Cierre el enchufe:

#include <sys/socket.h>
int shutdown(int sockfd, int how);

how:SHUT_RD(关闭读端),无法从套接字读取数据
     SHUT_WR(关闭写端),无法从套接字发送数据
     SHUT_RDWR,无法读取和发送数据

(2) Conector y dirección asociados al servidor

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

示例:
struct sockaddr_in socket_server_addr;
duty_socket = socket(AF_INET, SOCK_STREAM, 0);
/* 服务器端填充 sockaddr_in结构 */
socket_server_addr.sin_family   = AF_INET;
/*端口号转换为网络字节序*/
socket_server_addr.sin_port     = htons(SERVER_PORT);
/*接收本机所有网口的数据*/
socket_server_addr.sin_addr.s_addr  = INADDR_ANY;
memset(socket_server_addr.sin_zero, 0, 8);

/* 捆绑sockfd描述符 */
ret = bind(duty_socket, (const struct sockaddr *)&socket_server_addr, sizeof(struct sockaddr));

(3) El servidor llama a la función de escucha para aceptar la solicitud de conexión

int listen(int sockfd,int backlog);

ret = listen(duty_socket, C_QUEUE);

sockfd是bind后的文件描述符。
backlog设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
listen函数将bind的文件描述符变为监听套接字,返回的情况和bind一样。

(4) El servidor obtiene la solicitud de conexión y establece la conexión.

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


int customer_socket;
customer_socket = accept(duty_socket, (struct sockaddr *)&socket_client_addr, &addr_len);

sockfd是listen后的文件描述符。
addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind,listen和accept是服务器端用的函数。
accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。 accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了,失败时返回-1 。

(5) El cliente establece una conexión

int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);

ret = connect(client_socket, (const struct sockaddr *)&socket_server_addr, sizeof(struct sockaddr));


可以用connect建立一个连接,在connect中所指定的地址是想与之通信的服务器的地址。
sockfd是socket函数返回的文件描述符。
serv_addr储存了服务器端的连接信息,其中sin_add是服务端的地址。
addrlen是serv_addr的长度 
connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符,失败时返回-1。

(6) Enviar datos

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

sockfd 指定发送端套接字描述符;
buf 指明一个存放应用程序要发送数据的缓冲区;
len 指明实际要发送的数据的字节数;
flags 一般置0。
客户或者服务器应用程序都用send函数来向TCP连接的另一端发送数据


ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
sendto和send相似,区别在于sendto允许在无连接的套接字上指定一个目标地址。
dest_addr 表示目地机的IP地址和端口号信息,
addrlen 常常被赋值为sizeof (struct sockaddr)。
sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。

(7) Aceptar datos

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

sockfd 指定接收端套接字描述符;
buf 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
len 指明buf的长度;
flags 一般置0。
客户或者服务器应用程序都用recv函数从TCP连接的另一端接收数据。


ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);


recvfrom通常用于无连接套接字,因为此函数可以获得发送者的地址。
src_addr 是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。
addrlen 常置为sizeof (struct sockaddr)。

Ejemplo de TCP:

La instancia se divide en dos partes: el cliente y el servidor. El servidor primero establece un socket, luego se une al puerto local, y luego comienza a recibir solicitudes de conexión del cliente y establece una conexión con él, y luego recibe el cliente El mensaje enviado. El cliente llama a la función connect () para establecer una conexión después de que se establece el socket.

Servidor:

/*server.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 4321
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5
int main()
{
    struct sockaddr_in server_sockaddr,client_sockaddr;
    int sin_size,recvbytes;
    int sockfd, client_fd;
    char buf[BUFFER_SIZE];
    /*建立 socket 连接*/
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)
    {
        perror("socket");
        exit(1);
    }
    printf("Socket id = %d\n",sockfd);
    /*设置 sockaddr_in 结构体中相关参数*/
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);
    int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
    /*绑定函数 bind()*/
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr,
    sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        exit(1);
    }
    printf("Bind success!\n");
    /*调用 listen()函数,创建未处理请求的队列*/
    if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("Listening....\n");
    /*调用 accept()函数,等待客户端的连接*/
    if ((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr, &sin_size)) == -1)
    {
        perror("accept");
        exit(1);
    }
    /*调用 recv()函数接收客户端的请求*/
    memset(buf , 0, sizeof(buf));
    if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) == -1)
    {
        perror("recv");
        exit(1);
    }
    printf("Received a message: %s\n", buf);
    close(sockfd);
    exit(0);
}

Cliente:

/*client.c*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 4321
#define BUFFER_SIZE 1024
int main(int argc, char *argv[])
{
    int sockfd,sendbytes;
    char buf[BUFFER_SIZE];
    struct hostent *host;
    struct sockaddr_in serv_addr;
    if(argc < 3)
    {
        fprintf(stderr,"USAGE: ./client Hostname(or ip address) Text\n");
        exit(1);
    }
    /*地址解析函数*/
    if ((host = gethostbyname(argv[1])) == NULL)
    {
        perror("gethostbyname");
        exit(1);
    }
    memset(buf, 0, sizeof(buf));
    sprintf(buf, "%s", argv[2]);
    /*创建 socket*/
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    /*设置 sockaddr_in 结构体中相关参数*/
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
    bzero(&(serv_addr.sin_zero), 8);
    /*调用 connect 函数主动发起对服务器端的连接*/
    if(connect(sockfd,(struct sockaddr *)&serv_addr,
    sizeof(struct sockaddr))== -1)
    {
        perror("connect");
        exit(1);
    }
    /*发送消息给服务器端*/
    if ((sendbytes = send(sockfd, buf, strlen(buf), 0)) == -1)
    {
        perror("send");
        exit(1);
    }
    close(sockfd);
    exit(0);
}

resultado:

$ ./server
Socket id = 3
Bind success!
Listening....
Received a message: Hello,Server!
$ ./client localhost(或者输入 IP 地址) Hello,Server!

3. Programación de red avanzada

En situaciones reales, las personas a menudo se encuentran con situaciones en las que varios clientes se conectan al servidor. Dado que connet (), recv () y send () introducidos anteriormente son funciones de bloqueo, si el recurso no está listo, el proceso que llama a esta función entrará en un estado de suspensión, de modo que no pueda manejar la multiplexación de E / S. se utiliza. Esta sección ofrece dos soluciones para resolver la multiplexación de E / S, estas dos funciones son fcntl () y select () aprendidas antes.

(1) fcntl ()

La función fcntl () proporciona las siguientes funciones de programación para la programación de conectores.

  • E / S sin bloqueo: puede establecer cmd en F_SETFL y bloquear en O_NONBLOCK.
  • E / S asíncronas: puede establecer cmd en F_SETFL y bloquear en O_ASYNC.
/* net_fcntl.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#define PORT 1234
#define MAX_QUE_CONN_NM 5
#define BUFFER_SIZE 1024
int main()
{
    struct sockaddr_in server_sockaddr, client_sockaddr;
    int sin_size, recvbytes, flags;
    int sockfd, client_fd;
    char buf[BUFFER_SIZE];
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);
    int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        exit(1);
    }
    if(listen(sockfd,MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("Listening....\n");
    /* 调用 fcntl()函数给套接字设置非阻塞属性 */
    flags = fcntl(sockfd, F_GETFL);
    if (flags < 0 || fcntl(sockfd, F_SETFL, flags|O_NONBLOCK) < 0)
    {
        perror("fcntl");
        exit(1);
    }
    while(1)
    {
        sin_size = sizeof(struct sockaddr_in);
        if ((client_fd = accept(sockfd,(struct sockaddr*)&client_sockaddr, &sin_size)) < 0)
        {
            perror("accept");
            exit(1);
        }
        if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) < 0)
        {
            perror("recv");
            exit(1);
        }
        printf("Received a message: %s\n", buf);
    } /*while*/
    close(client_fd);
    exit(1);
}



$ ./net_fcntl
Listening....
accept: Resource temporarily unavailable
可以看到,当 accept()的资源不可用(没有任何未处理的等待连接的请求)时,程序就会自动返回。

(2) seleccionar ()

Aunque la función fcntl () se puede usar para realizar E / S sin bloqueo o E / S impulsadas por señales, en el uso real, a menudo prueba si el recurso está listo para la circulación, lo que aumenta en gran medida el consumo de recursos de CPU innecesarios. Aquí puede usar la función select () para resolver este problema. Al mismo tiempo, use la función select () para establecer el tiempo de espera, que se puede decir que es más poderoso.

/* net_select.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 4321
#define MAX_QUE_CONN_NM 5
#define MAX_SOCK_FD FD_SETSIZE
#define BUFFER_SIZE 1024
int main()
{
    struct sockaddr_in server_sockaddr, client_sockaddr;
    int sin_size, count;
    fd_set inset, tmp_inset;
    int sockfd, client_fd, fd;
    char buf[BUFFER_SIZE];
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);
    int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        exit(1);
    }
    if(listen(sockfd, MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("listening....\n");
    /*将调用 socket()函数的描述符作为文件描述符*/
    FD_ZERO(&inset);
    FD_SET(sockfd, &inset);
    while(1)
    {
        tmp_inset = inset;
        sin_size=sizeof(struct sockaddr_in);
        memset(buf, 0, sizeof(buf));
        /*调用 select()函数*/
        if (!(select(MAX_SOCK_FD, &tmp_inset, NULL, NULL, NULL) > 0))
        {
            perror("select");
        }
        for (fd = 0; fd < MAX_SOCK_FD; fd++)
        {
            if (FD_ISSET(fd, &tmp_inset) > 0)
            {
                if (fd == sockfd)
                { /* 服务端接收客户端的连接请求 */
                    if ((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr, &sin_size))== -1)
                    {
                        perror("accept");
                        exit(1);
                    }
                    FD_SET(client_fd, &inset);
                    printf("New connection from %d(socket)\n", client_fd);
                }
                else /* 处理从客户端发来的消息 */
                {
                    if ((count = recv(client_fd, buf, BUFFER_SIZE, 0)) > 0)
                    {
                        printf("Received a message from %d: %s\n",
                        client_fd, buf);
                    }
                    else
                    {
                        close(fd);
                        FD_CLR(fd, &inset);
                        printf("Client %d(socket) has left\n", fd);
                    }
                }
            } /* end of if FD_ISSET*/
        } /* end of for fd*/
    } /* end if while while*/
    close(sockfd);
    exit(0);
}


$ ./server
listening....
New connection from 4(socket) /* 接受第一个客户端的连接请求*/
Received a message from 4: Hello,First! /* 接收第一个客户端发送的数据*/
New connection from 5(socket) /* 接受第二个客户端的连接请求*/
Received a message from 5: Hello,Second! /* 接收第二个客户端发送的数据*/
Client 4(socket) has left /* 检测到第一个客户端离线了*/
Client 5(socket) has left /* 检测到第二个客户端离线了*/
$ ./client localhost Hello,First! & ./client localhost Hello,Second

 

Supongo que te gusta

Origin blog.csdn.net/qq_34968572/article/details/107513609
Recomendado
Clasificación