Programación de sockets: comunicación de red entre cliente y servidor basada en el protocolo TCP

1. Proceso de creación del servidor

  1. Llame a la función de socket para crear un socket de escucha;
  2. Llame a la función de vinculación para vincular el socket a una tupla doble que consta de una IP y un número de puerto;
  3. Llame a la función de escucha para comenzar a escuchar;
  4. Cuando hay una solicitud de conexión del cliente, llame a la función de aceptación para aceptar la conexión y generar un nuevo socket (socket para comunicarse con el cliente);
  5. Llame a la función de envío o recepción según el socket recién generado para iniciar el intercambio de datos con el cliente;
  6. Una vez finalizada la comunicación, llame a la función de cierre para cerrar el socket.

2. Proceso de creación de clientes

  1. Llame a la función de socket para crear un socket de cliente;
  2. Llame a la función de conexión para intentar conectarse al servidor;
  3. Una vez que la conexión sea exitosa, llame a la función de envío o recepción para comunicarse con el servidor.
  4. Una vez finalizada la comunicación, llame a la función de cierre para cerrar el socket.

3. API comunes

estructura sockaddr y estructura sockaddr_in

Ambas estructuras son direcciones utilizadas para manejar las comunicaciones de red .

/*
 *此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息
 *note:
 *  目标地址和端口信息在一起
 */
#include <sys/socket.h>

struct sockaddr
{
    
    
    // 地址家族,一般“AF_xxx”的形式,通常使用AF_INET
    unsigned short sa_family;
    // 14字节协议地址,目标地址和端口信息
    char           sa_data[14];
}
#include <netinet/in.h>

struct  sockaddr_in 
{
    
    
    short int       sin_family;       //协议族
    unsigned short  int  sin_port;    //端口号(使用网络字节顺序)
    struct in_addr  sin_addr;         //IP地址        
    unsigned char   sin_zero[8];      //sockaddr与sockaddr_in 保持大小相同而保留的空字节
};

struct  in_addr 
{
    
    
    unsigned  long  s_addr;
};

typedef struct in_addr 
{
    
    
    union
    {
    
    
        struct
        {
    
    
            unsigned char s_b1,
                          s_b2,
                          s_b3,
                          s_b4;
        } S_un_b;

        struct 
        {
    
    
            unsigned short s_w1,
                           s_w2;
        } S_un_w;

        unsigned long S_addr;
    } S_un;
} IN_ADDR;

sockaddr_in y sockaddr son estructuras paralelas , y el puntero a la estructura sockaddr_in también puede apuntar a la estructura sockraddr y reemplazarla.

struct sockaddr_in mysock;

bind(sockfd, (struct sockaddr *)&mysock, sizeof(struct sockaddr); /* bind的时候进行转化 */

net.core.somaxconn

net.core.somaxconn es un parámetro del kernel en Linux, que indica el límite superior de la acumulación de monitoreo de socket (escuche). El trabajo pendiente es la cola de escucha del socket. Cuando una solicitud (solicitud) no se ha procesado o establecido, ingresará al trabajo pendiente. El servidor de socket puede procesar todas las solicitudes en el trabajo pendiente a la vez y las solicitudes procesadas ya no están en la cola de escucha. Cuando el servidor procesa las solicitudes con tanta lentitud que la cola de escucha se llena, las nuevas solicitudes entrantes serán rechazadas.

En Hadoop 1.0, el parámetro ipc.server.listen.queue.size controla la longitud de la cola de escucha del socket del servidor, es decir, la longitud del trabajo pendiente, y el valor predeterminado es 128. El valor predeterminado del parámetro de Linux net.core.somaxconn también es 128. Cuando el servidor está ocupado, como NameNode o JobTracker, 128 no es suficiente. De esta manera, es necesario aumentar la acumulación. Por ejemplo, nuestro clúster de 3000 unidades establece ipc.server.listen.queue.size en 32768. Para que todo el parámetro logre el efecto esperado, también es necesario configurar el parámetro del kernel net.core.somaxconn a un valor A mayor o igual a 32768.

En Linux, la herramienta syctl se puede utilizar para ajustar dinámicamente todos los parámetros del kernel. El llamado ajuste dinámico entrará en vigor inmediatamente después de que se modifique el valor del parámetro del kernel. Pero esto sólo tiene efecto a nivel del sistema operativo. Para Hadoop, la aplicación debe reiniciarse para que surta efecto.

Mostrar todos los parámetros y valores del kernel

sysctl -a

Sintaxis para modificar valores de parámetros

sysctl -w net.core.somaxconn=32768

El comando anterior cambia el valor del parámetro del kernel net.core.somaxconn a 32768. Aunque dichos cambios pueden surtir efecto inmediatamente, los valores predeterminados se restaurarán después de reiniciar la máquina. Para mantener los cambios permanentemente, necesita agregar una línea en /etc/sysctl.conf con vi

net.core.somaxconn=4000

Luego ejecuta el comando

sysctl -p

int socket(familia int, tipo int, protocolo int);

  • Rol: crear un descriptor de socket. En un sistema Linux, todo es un archivo. Para representar y distinguir los archivos abiertos, Unix/Linux asignará una ID al archivo, que es un número entero y se denomina descriptor de archivo. Por lo tanto, una conexión de red también es un archivo, que también tiene descriptores de archivo. Cree una conexión de red o abra un archivo de red a través de la función socket(). El valor de retorno de la función socket() es un descriptor de archivo. A través de este descriptor de archivo, podemos utilizar operaciones de archivos normales para transferir datos.

  • parámetro:

    familia: especifica la familia/dominio del protocolo, generalmente AF_INET, AF_INET6, AF_LOCAL, etc.;
    tipo: tipo de socket, principalmente SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, etc.;
    protocolo: generalmente establecido en 0.

  • Valor de retorno: ok (no negativo), error (-1). En caso de éxito, devuelve un pequeño valor entero no negativo, similar a un descriptor de archivo.

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

  • Función: el servidor vincula la dirección y el puerto utilizados para la comunicación al socket.

  • parámetro:

    sockfd: representa el socket que debe vincularse, es decir, el descriptor de archivo devuelto al crear el socket;
    addr: almacena la dirección y el puerto utilizados por el servidor para la comunicación;
    addrlen: representa el tamaño de la estructura addr.

  • Valor de retorno: cuando la función de vinculación devuelve 0, está vinculada correctamente; si devuelve -1, significa que la vinculación falló.

int escucha(int sockfd, int backlog);

  • Función: La función de la función de escucha no es esperar la llegada de una nueva conexión, es la función de aceptación la que realmente espera la conexión. La operación de escucha es que cuando más clientes inician la conexión y el servidor no puede procesar la solicitud de conexión a tiempo, almacenará en caché la conexión de conexión en la cola de espera. La longitud de esta cola de espera la establece el parámetro backlog en listening.

  • parámetro:

    sockfd: el descriptor de archivo creado por el socket anterior;
    backlog: se refiere al número máximo de conexiones que el servidor puede almacenar en caché, es decir, la longitud de la cola de espera.

  • Valor de retorno: cuando la escucha se ejecuta correctamente, devuelve 0; cuando falla, devuelve -1.

int aceptar(int sockfd, struct sockaddr *client_addr, socklen_t *addrlen);

  • Función: La función de aceptación espera la conexión del cliente. Si ningún cliente se conecta, esperará para siempre. Este método se llama bloqueo. Después de aceptar espera la conexión del cliente, crea un nuevo socket y el valor de retorno de la función es este nuevo socket, y el servidor usa este nuevo socket para enviar y recibir mensajes con el cliente.

  • parámetro:

    sockfd: el socket que se ha escuchado;
    client_addr: se utiliza para almacenar la información de la dirección del cliente, incluida la familia de protocolos, la dirección de red y el número de puerto del cliente. Si no necesita la dirección del cliente, puede completar 0;
    addrlen: se usa para almacenar la longitud del segundo parámetro (client_addr).

  • Valor de retorno: ok (nuevo fd), error (-1).

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

  • Rol: el cliente inicia una solicitud de conexión al servidor.

  • parámetro:

    sock_fd: representa el descriptor de archivo devuelto por la función socket();
    serv_addr: representa la familia de protocolos, la dirección de red y el número de puerto del servidor de destino, que es un puntero de tipo sockaddr;
    addrlen: representa el tamaño del contenido del segundo parámetro.

  • Valor de retorno: cuando el valor de retorno es 0, significa que la conexión se realizó correctamente; cuando el valor de retorno es -1, significa que la conexión falla.

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

  • Función: la función recv se utiliza para recibir los datos enviados por el socket del mismo nivel. Ya sea un cliente o un servidor, la aplicación utiliza la función recv para recibir datos enviados desde el otro extremo de la conexión TCP. Si el par del socket no envía datos, la función recv esperará; si el par envía datos, la función devuelve el número de caracteres recibidos.

  • parámetro:

    sockfd: representa el descriptor de socket del extremo receptor, es decir, el descriptor de archivo devuelto por la función socket ();
    buf: la dirección de memoria utilizada para recibir datos, que puede ser la dirección de una variable de tipo de datos básico del lenguaje C, o un cuerpo de matriz o estructura, cadena de caracteres;
    len: indica el número de bytes de datos a recibir. No puede exceder el tamaño de buf, de lo contrario la memoria se desbordará;
    banderas: generalmente se establecen en 0, otros valores tienen poco significado.

  • Valor de retorno: cuando falla, el valor de retorno es menor que 0; cuando se agota el tiempo de espera o el par se cierra activamente, el valor de retorno es igual a 0; cuando tiene éxito, el valor de retorno es la longitud de los datos recibidos.

int enviar(int sockfd, const void *buf, int len, int flags);

  • Función: la función de envío se utiliza para enviar datos al par a través del socket. Ya sea un cliente o un servidor, la aplicación utiliza la función de envío para enviar datos al otro extremo de la conexión TCP.

  • parámetro:

    sockfd: representa el descriptor de socket del extremo emisor, es decir, el descriptor de archivo devuelto por la función socket ();
    buf: indica la dirección de memoria de los datos a enviar, que puede ser la dirección de un tipo de datos básico del lenguaje C variable, o un cuerpo de matriz o estructura, cadena de caracteres;
    len: indica el número de bytes de datos realmente enviados;
    banderas: generalmente se establece en 0, otros valores tienen poco significado.

  • Valor de retorno: cuando falla, el valor de retorno es menor que 0; cuando se agota el tiempo de espera o el par se cierra activamente, el valor de retorno es igual a 0; cuando tiene éxito, el valor de retorno es la longitud de los datos enviados.

int cerrar(int sockfd);

  • Función: cerrar el socket y finalizar la conexión TCP.

  • parámetro:

    sockfd: descriptor de socket.

  • Valor de retorno: ok(0), error(-1).

int inet_pton(int af, const char *src, void *dst);

  • Función: convierte una dirección de texto en una dirección binaria.

  • Archivo de encabezado: #include <arpa/inet.h>

  • parámetro:

    af: dirección familiar. AF_INET: dirección IPv4; AF_INET6: dirección IPv6;
    src: puntero a dirección IPv4 o IPv6 "con puntos", como "192.168.1.100";
    dst: puntero de tipo struct in_addr * o struct in6_addr *.

  • Valor de retorno: éxito: 1; fracaso: 0 significa que la dirección no coincide con la familia de direcciones, -1 significa que la dirección es ilegal.

const char *inet_ntop(int af, const void *src, char *dst, tamaño socklen_t);

  • Función: convierte una dirección binaria en una dirección de texto.

  • Archivo de encabezado: #include <arpa/inet.h>

  • parámetro:

    af: dirección familiar. AF_INET: dirección IPv4; AF_INET6: dirección IPv6;
    src: puntero de tipo struct in_addr * o struct in6_addr *;
    dst: puntero del búfer de dirección;
    tamaño: tamaño del búfer de dirección, al menos INET_ADDRSTRLEN o INET6_ADDRSTRLEN bytes.

  • Valor de retorno: éxito: dst; fracaso: NULL.

función de conversión de orden de bytes

  • Prototipo de función:

    uint32_t htonl(uint32_t hostlong);
    uint16_t htons(uint16_t hostshort);
    uint32_t ntohl(uint32_t netlong);
    uint16_t ntohs(uint16_t netshort);

  • Parámetros:
    hostlong: datos enteros largos en el orden de bytes del host;
    hostshort: datos enteros cortos en el orden de bytes del host;
    netlong: datos enteros largos en el orden de bytes de la red;
    netshort: datos enteros cortos en el orden de bytes de la red.

  • Valor de retorno: los datos de orden de bytes correspondientes.

4. Código del servidor

#include <iostream>
#include <unistd.h>		// POSIX系统API访问
#include <sys/types.h>	// 基本系统数据类型
#include <arpa/inet.h>	// 网络信息转换
#include <string.h>

using namespace std;

#define SERVER_PORT 9991

int main() {
    
    
    // 创建一个监听socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);	

    if (listenfd == -1) {
    
    
        cout << "create listen socket error!" << endl;
        return -1;
    }

    // 初始化服务器地址
    struct sockaddr_in bindaddr;
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bindaddr.sin_port = htons(SERVER_PORT);
	//如果只想在本机上进行访问,bind函数地址可以使用本地回环地址
	//如果只想被局域网的内部机器访问,那么bind函数地址可以使用局域网地址
	//如果希望被公网访问,那么bind函数地址可以使用INADDR_ANY or 0.0.0.0

    // 绑定地址和端口
    if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1) {
    
    
        cout << "bind listen socket error!" << endl;
        return -1;
    }

    // 启动监听
    if (listen(listenfd, SOMAXCONN) == -1) {
    
    
        cout << "listen error!" << endl;
        return -1;
    }
	cout << "start listening..." << endl;

    while (true) {
    
    
        // 创建一个临时的客户端socket
        struct sockaddr_in clientaddr;
        socklen_t clientaddrlen = sizeof(clientaddr);

        // 接受客户端连接
        int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
        if (clientfd != -1) {
    
    
            char recvBuf[32] = {
    
    0};
            // 从客户端接受数据
            int ret = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
            if (ret > 0) {
    
    
                cout << "recv data from cilent successfully, data:" << recvBuf << endl;
                // 将接收到的数据原封不动地发给客户端
                ret = send(clientfd, recvBuf, strlen(recvBuf), 0);
                if (ret != strlen(recvBuf)) {
    
    
                    cout << "send data error!" << endl;
                } else {
    
    
                    cout << "send data to client successfully, data:" << recvBuf <<endl;
                }
            } else {
    
    
                cout << "recv data error!" <<endl;
            }
        }
        close(clientfd);
    }

    // 关闭监听socket
    close(listenfd);
    return 0;
}

5. Código de cliente

#include <iostream>
#include <unistd.h>		// POSIX系统API访问
#include <sys/types.h>	// 基本系统数据类型
#include <arpa/inet.h>	// 网络信息转换
#include <string.h>

using namespace std;

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT 9991

int main(int argc, char* argv[]) {
    
    
    char* message;
    if(argc != 2) {
    
    
        fputs("Usage: ./client message\n", stderr); // 向指定的文件写入一个字符串
        exit(1);
    }

    message = argv[1];
    printf("send message: %s\n", message);

    // 创建一个socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1) {
    
    
        cout << "create client socket error!" << endl;
        return -1;
    }

    // 连接服务器地址
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serveraddr.sin_port = htons(SERVER_PORT);

    if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) {
    
    
        cout << "connect socket error!" << endl;
        return -1;
    }

    // 向服务器发送数据
    
    int ret = send(clientfd, message, strlen(message), 0);
    if (ret != strlen(message)) {
    
    
        cout << "send data error!" << endl;
        return -1;
    } else {
    
    
        cout << "send data to server successfully, data:" << message << endl;
    }

    // 从服务器读取数据
    char recvBuf[32] = {
    
    0};
    ret = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
    if (ret > 0) {
    
    
        cout << "recv data from server successfully, data:" << recvBuf << endl;
    } else {
    
    
        cout << "recv data from server error!" << endl;
    }

    // 关闭socket
    close(clientfd);
    return 0;
}

Supongo que te gusta

Origin blog.csdn.net/crossoverpptx/article/details/132237821
Recomendado
Clasificación