Desarrollo de servidor de servidor web C++

Este artículo son las notas del video tutorial de desarrollo de servidores de alta concurrencia Linux de Niuke.com.


1. Conocimientos preliminares

1.1 Linux y remoto

Use ssh para controlar el sistema Linux en Windows, use vscode para controlar el código,
compile con g ++

1.1 Biblioteca estática y biblioteca dinámica

La producción y diferencia entre bibliotecas estáticas y bibliotecas dinámicas.

1.2 archivo MAKE

La operación del archivo Makefile consiste en especificar el orden de compilación de todos los archivos fuente, porque un proyecto formal tendrá muchos, muchos archivos fuente, es imposible g++ manualmente uno por uno y la compilación de varios archivos puede tener una relación secuencial, por lo que Debe ser un comando dedicado para determinar el orden de compilación y la relación de compilación. Este comando es make . archivo make

1.3 herramienta de depuración de GDB

tiempo de compilación -g

1.4 Archivo io

La implementación de la biblioteca c estándar io es llamar al método de operación de archivos del propio Linux, que implica la interacción entre la memoria y el disco duro. Se puede implementar en diferentes plataformas usando la biblioteca c estándar, pero la biblioteca c vuelve a llamar los métodos correspondientes de diferentes plataformas. La biblioteca c tiene un búfer para operaciones de archivos, las operaciones de archivos primero operarán en el búfer y finalmente se escribirán en el disco, por lo que se debe prestar especial atención al uso del comando de descarga.

1.5 Creación, apertura, lectura y escritura de archivos

Utilice open() para abrir el archivo. Si la apertura es exitosa, se devolverá el descriptor del archivo. Si la apertura falla, se devolverá el código de error más reciente errno. errno es el número de error incorporado de Linux. Puede ver la descripción del error correspondiente a error a través de perror.
También puede crear un nuevo archivo usando open(). Simplemente agregue un parámetro O_CREAT a la función open.
Después de que la función open abre un archivo, se puede leer y escribir a través de las funciones de lectura y escritura.
El siguiente código es para copiar el contenido de un archivo Copiar a un archivo que no existe:

#include<unistd.h>
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
using namespace std;
int main()
{
    
    
    //1、读取待复制的文件
    int srcfd=open("/home/lanpangzi/Desktop/Linux/lession02/test01.cpp",O_RDONLY);
    if(srcfd==-1){
    
    
        perror("open");
        return -1;
    }
    printf("文件打开成功\n");
    //2、创建一个待粘贴的新的空文件,用来写入
    int targetfd=  open("cpy.cpp",O_WRONLY|O_CREAT,0664);
    if(targetfd==-1){
    
    
        perror("open");
        return -1;
    }
    printf("文件创建成功\n");
    //3、循环读写将文件完全拷贝进去
    char buff[1024]{
    
    };
    int read_re = 0;
    while((read_re=read(srcfd,buff,sizeof(buff)))>0){
    
    
        write(targetfd, buff,read_re);
    }
    printf("文件复制成功\n");

    //4、关闭所有打开的文件
    close(srcfd);
    close(targetfd);
    printf("文件关闭成功\n");
    return 0;
}

2. Desarrollo multiproceso de Linux

2.1 Crear y usar subprocesos

Tenga en cuenta que el proceso hijo necesita usar wait () para reciclarse; de ​​lo contrario, es fácil generar procesos zombies.

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>

int main(){
    
    
    printf("公共段1\n"); //这一句只会执行两遍
    // 创建子进程
    
    pid_t n_pid = fork(); // fork之后的语句父子进程都会执行
    printf("公共段2\n"); //这一句会执行两遍
    // 原来的父进程和新创建的子进程pid不同,通过判断n_pid来判断当前是父进程还是子进程
    if(n_pid>0){
    
    
        printf("当前是父进程!\n");
        printf("当前进程的pid: %d, 当前进程的父进程pid: %d\n", getpid(),getppid());
    }
    else if(n_pid==0){
    
    
        printf("当前是子进程!\n");
        printf("当前进程的pid: %d, 当前进程的父进程pid: %d\n", getpid(),getppid());
    }
    printf("卧槽真牛逼!\n"); //这一句会执行两遍
    return 0;
}

2.2 Depuración multiproceso GDB

GDB solo puede rastrear un proceso, pero puede establecer si la herramienta de depuración de GDB rastrea el proceso principal o el proceso secundario antes de la bifurcación, y el estado del otro proceso al rastrear este proceso.

2.3 Llamar a otros programas

Usando la función execl(), puede saltar directamente del programa actual y ejecutar otros programas directamente. La familia de funciones exec son algunas funciones cortas. Todas sus funciones son llamar a un programa, excepto el método de transferencia de parámetros o la configuración de variables de entorno. son diferentes.

2.4 Comunicación entre procesos

2.4.1 Comunicación mediante canalizaciones anónimas

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
    
    
    // 创建管道标识,用于接收但会的两个文件描述符
    // pipefd[0]->读取pipefd[1]->写入
    int pipefd[2];
    // 创建管道,返回值用来判断是否创建成功
    int ret = pipe(pipefd);
    if(ret==-1){
    
    
        perror("pipe");
        exit(0);
    }
    pid_t pid = fork();
    if(pid>0){
    
    
        // 父进程,向管道中写入信息
        char * str = "我是父进程写入的信息";
        char buf[1024]={
    
    0};
        while (1)
        {
    
    
            read(pipefd[0],buf,sizeof(buf));
            printf("这里是父进程,读取的信息为:%s\n",buf);

            write(pipefd[1],str,strlen(str));
            printf("这里是父进程, pid:%d,正在写入文件\n", getpid());
            sleep(1);
        }
    }
    else{
    
    
        // 子进程
        // 从管道中读取信息
        char * str = "我是子进程写入的信息";
        char buf[1024]={
    
    0};
        while (1)
        {
    
    
            write(pipefd[1],str,strlen(str));
            printf("这里是子进程, pid:%d,正在写入文件\n", getpid());
            sleep(1);
            read(pipefd[0],buf,sizeof(buf));
            printf("这里是子进程,读取的信息为:%s\n",buf);
        }
    }
    return 0;
}   

2.4.2 Comunicación mediante canalización con nombre FIFO

Lado de escritura:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>

int main()
{
    
    
    // 创建之前需要先判断文件是否已经存在
    int ret = access("fifo1",F_OK);
    if(ret==-1){
    
    
        // 如果不存在则创建管道
        ret = mkfifo("fifo1",0664);
        if (ret==-1){
    
    
            perror("fifo");
            exit(0);
        }

    }
    
    //打开管道准备写入
    int fd=open("fifo1",O_WRONLY);
    if(fd==-1){
    
    
        perror("open");
        exit(0);
    }

    for(int i=0;i<100;i++){
    
    
        char buf[1024];
        sprintf(buf, "hello, %d\n", i);
        printf("write data : %s\n", buf);
        write(fd, buf, strlen(buf));
    }
    close(fd);
    return 0;
}

Fin de la lectura:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>

int main()
{
    
    
    int fd = open("fifo1",O_RDONLY);
    if(fd==-1){
    
    
        perror("read");
        exit(0);
    }
    while(1){
    
    
        char buf[1024]={
    
    0};
        int len = read(fd,buf,sizeof(buf));
        if(len==0){
    
    
            printf("写端断开连接\n");
            break;
        }
        printf("read buf: %s\n", buf);
    }
    return 0;
}

2.4.3 Uso del mapeo de memoria para la comunicación

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);

El mapeo de memoria consiste en asignar archivos en el disco a la memoria y modificarlos directamente, lo cual es más eficiente.

2.4.4 Comunicación de señal utilizada

Es similar a la SEÑAL de QT, que le dice a un proceso lo que ha sucedido. También se le llama interrupción de software. La señal es ejecutada por el núcleo. Es necesario registrar la señal antes de usarla, para que la función correspondiente se pueda ejecutar cuando se produce la señal.
Un conjunto de señales es una estructura de datos que almacena un conjunto de señales y se puede utilizar una serie de funciones para operar el conjunto de señales.

2.4.5 Comunicación de memoria compartida

Varios procesos acceden a la misma memoria a través de un identificador de memoria único. Puede usar ftok () para generar un identificador basado en la clave y luego usar este identificador para crear o acceder a una determinada pieza de memoria compartida.

2.4.6 Proceso demonio

Antes de comprender el proceso del demonio, es necesario comprender la terminal, el grupo de procesos (un conjunto de procesos) y la sesión (un conjunto de grupos de procesos). El proceso demonio es un proceso que se ha estado ejecutando en segundo plano. Todas las conexiones entre él y la consola se cortan, e incluso las entradas y salidas se redirigen a archivos específicos.

3. Desarrollo multiproceso de Linux

El subproceso secundario es como ejecutar una nueva función en el subproceso principal, pero la ejecución de esta función no bloquea el subproceso principal. Dado que es un archivo cpp con el hilo principal, todas sus variables se comparten. Si el hilo principal sale del hilo principal a través de pthread_exit (), no afectará a otros hilos que se ejecutan normalmente.

Otros términos: separación de subprocesos (pthread_detach()), cancelación de subprocesos (pthread_cancel()), atributos de subprocesos (pthread_attr_***), sincronización de subprocesos (), bloqueo mutex, interbloqueo, bloqueo de lectura-escritura, modelo productor-consumidor, semáforo ( sem_t)

4. Programación de red

4.1 Modelo de estructura de red

C/S, B/S

4.2 dirección MAC, dirección IP, puerto

4.3 Modelo de red

4.3.1 Modelo de red de siete capas

OSI

4.3.1 Modelo de cuatro capas TCP/IP

Protocolos comunes
Protocolos de capa de aplicación: FTP, HTTP, NFS
Protocolos de capa de transporte: TCP, UDP
Protocolos de capa de red: IP, ICMP, IGMP
Protocolos de capa de interfaz de red: ARP, RARP

El protocolo se refleja en última instancia en el formato de los datos transmitidos.

zócalo 4.4

Socket (socket), traducido literalmente como socket, es un tipo de archivo especial que se utiliza para representar la comunicación de red entre procesos en Linux. En otras palabras, es un archivo que se utiliza para realizar la comunicación entre procesos, pero los procesos que se comunican están en diferentes Entre los hosts , los hosts están conectados a través de la red.

4.5 Endianidad

Al igual que leer texto, la unidad de procesamiento de datos de la computadora son los bytes, al igual que las palabras una por una, entonces, al procesar, ¿se procesa de izquierda a derecha o de derecha a izquierda? Este es el orden de bytes. No hay orden de bytes en un byte. Hay dos tipos de orden de bytes, Big-Endian y Little-Endian. Diferentes computadoras pueden usar un orden de bytes diferente, por lo que debe determinar el orden de bytes después de recibir el archivo. El orden es el mismo que el orden de bytes nativo.
Tenga en cuenta que el high-endian y el low-endian son relativos a un byte, por lo que el juicio no se basa en la dirección de crecimiento del número, sino en lo que se coloca en diferentes posiciones en una variable int. Además, puede usar la matriz The Cuanto menor sea el índice, menor será la dirección de memoria.

    union 
    {
    
    
        short value;
        char bytes[sizeof(short)];
    } test;
    test.value=0x0102;// 01是这个数的高位,02是这个数的低位
    // 01(高位)在内存中地址更小,所以是大端字节序,整数的高位在内存的低处
    if(test.bytes[0]==1 && test.bytes[1]==2) printf("大端字节序\n");
    // 01(高位)在内存中地址更大,所以是小端字节序,整数的高位在内存的高处
    else if (test.bytes[0]==2 && test.bytes[1]==1) printf("小端字节序\n");

Para evitar problemas causados ​​por un orden de bytes diferente, todos los datos se convierten al orden de bytes big-endian antes de cargarlos en la red y luego ingresan a la red. De esta manera, cada vez que el receptor recibe el archivo, es big-endian Y luego, según su propio orden de bytes, determine si es necesaria la conversión.

4.6 dirección de socket

La dirección del socket encapsula la IP y el puerto para facilitar la comunicación.

4.7 Proceso de comunicación TCP

Insertar descripción de la imagen aquí

4.8 Crear un servidor y un cliente localmente para comunicarse

Un servidor y un cliente locales que pueden comunicarse en dos direcciones, pero cada persona solo puede pronunciar una oración, y la primera oración debe ser iniciada por el servidor del
cliente.cpp

// TCP通信服务器端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<iostream>

int main(){
    
    
    // 1、创建监听的socket
    int lfd = socket(AF_INET, SOCK_STREAM,0);
    if (lfd==-1){
    
    
        perror("socket");
        exit(-1);
    }

    // 2、绑定本地的IP和端口,为了统一ip和端口的格式,将其封装在 sockaddr_in类的结构体中
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    // ip可以是任意的,这样这个主机才可以接受到来自任意ip的客户端的连接
    saddr.sin_addr.s_addr = INADDR_ANY; 
    saddr.sin_port=htons(9999); // 端口为9999,通过htons转换为网络字节序
    int bind_ret = bind(lfd, (sockaddr *)&saddr, sizeof(saddr));
    if (bind_ret==-1){
    
    
        perror("bind");
        exit(-1);
    }
    
    // 3、监听,监听这个socket上有无连接进来,8是未连接和已连接的客户端数量之和的最大值
    // 相当于开启一个后开进程,不会阻塞当前程序
    int listen_ret=listen(lfd,8);
    if(listen_ret==-1){
    
    
        perror("listen");
        exit(-1);
    }

    // 4、接收客户端连接
    struct sockaddr_in clientaddr; // 定义客户端的地址,将来接收到客户端相关信息后放到这个里面
    socklen_t len=sizeof(clientaddr);
    //   accept是阻塞函数,直到有客户端连接上,否则一直阻塞
    int cfd = accept(lfd,(struct sockaddr*)&clientaddr, &len);
    if(cfd==-1){
    
    
        perror("accept");
        exit(-1);
    }

    // 输出客户端信息
    char client_ip[16];
    // 将字符串转换为可以192.168.1.1 的字符串形式
    inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, client_ip, sizeof(client_ip));
    unsigned short clientport = ntohs(clientaddr.sin_port);
    printf("connected! client ip is %s, port is %d\n", client_ip, clientport);
    while(1){
    
    
        // 5、获取客户端的数据并发送数据
        char rec_buf[1024]={
    
    0};
        //  read是阻塞函数,连接上之后accept继续往下走,但是如果客户端没有信息发送过来read会阻塞住
        int read_ret = read(cfd, rec_buf, sizeof(rec_buf));
        if (read_ret==-1){
    
    
            perror("read");
            exit(-1);
        } else if (read_ret>0){
    
    
            printf("cliend: %s", rec_buf);
        }else if(read_ret==0){
    
    
            printf("client is closed!\n");
            break;
        }
        // 给客户端发送数据
        char data[1024];
        fgets(data, sizeof(data),stdin);
        write(cfd, data,strlen(data));
    }
    // 6、关闭所有打开的文件描述符
    close(cfd);
    close(lfd);
    return 0;
   
}

cliente.cpp

// TCP通信客户端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<iostream>

int main(){
    
    
    // 1、创建socket,
    int fd = socket(AF_INET, SOCK_STREAM,0);
    if (fd==-1){
    
    
        perror("socket");
        exit(-1);
    }

    // 2、设置服务器的 IP 192.168.18.128和端口 9999,这样客户端才知道要去哪里找
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.18.128",&serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9998);

    // 3、连接服务器
    int con_ret = connect(fd, (struct sockaddr*)&serveraddr,sizeof(serveraddr));
    if (con_ret==-1){
    
    
        perror("connect");
        exit(-1);
    }
    //   如果进行到这里说明客户端已经成功连接到了主机
    printf("connected!\n");
    while(1){
    
    
        // 4、向服务器端发送数据,客户端是先发送send,再接受recv
        char data[1024];
        fgets(data, sizeof(data),stdin);
        write(fd,data,strlen(data));// 写进去了socket会自动发送
        // 5、获取服务器发送回来的数据
        char recv_buf[1024]={
    
    0};
        int read_ret = read(fd, recv_buf, sizeof(recv_buf));// 返回的是读取到的文件的长度
        if (read_ret==-1){
    
    
            perror("read");
            exit(-1);
        } else if (read_ret>0){
    
    
            printf("server: %s", recv_buf);
        }else if(read_ret==0){
    
    
            printf("server is closed!\n");
            break;
        }
    }
    // 6、关闭所有打开的文件描述符
    close(fd);
    return 0;
}

4.9 Usar múltiples procesos para lograr simultaneidad

#include<iostream>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<wait.h>
#include<error.h>

void recyleChild(int arg){
    
    
    while(1){
    
    
        int wait_ret = waitpid(-1,NULL,WNOHANG);
        if(wait_ret==-1){
    
    
            // 所有在子进程都已回收完毕,没有还活着的了
            break;
        }
        else if(wait_ret==0){
    
    
            // 所有能回收的子进程都已经回收了,没有需要回收的了,但是还有子进程还活着
            break;
        } else if(wait_ret>0){
    
    
            // 有子进程被回收了
            printf("子进程%d被回收了\n",wait_ret);
        }
    }
}

int main(){
    
    
    struct sigaction act;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = recyleChild;

    // 注册信号捕捉,用来回收结束了的子进程
    // 如果子进程exit会向父进程发送一个SIGCHLD信号,这个注册的函数act就是用来处理这个信号的
    sigaction(SIGCHLD,&act,NULL);


    int lfd = socket(AF_INET,SOCK_STREAM,0);
    if(lfd==-1){
    
    
        perror("socket");
        exit(-1
        );
    }
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port=htons(9999);
    int bind_ret = bind(lfd,(sockaddr *) &saddr,sizeof(saddr));
    if(bind_ret==-1){
    
    
        perror("bind");
        exit(-1);
    }
    // 开启监听的开关,不会阻塞,就相当于打开了一个开关
    int listen_ret = listen(lfd,8);
    if(listen_ret==-1){
    
    
        perror("listen");
        exit(-1);
    }
 
    // 将accept放在循环里面,每一次连接都创建一个子进程来进行通讯
    while(1){
    
    
        struct sockaddr_in clientaddr;
        socklen_t clientaddr_len = sizeof(clientaddr);
        int cfd = accept(lfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
        if(cfd==-1){
    
    
            // 如果accept被信号打断会报EINTR错误,直接continue即可
            // 因为这个程序会需要使用信号来回收结束的子进程,但是子进程会打断accept的阻塞状态
            if(errno==EINTR){
    
    
                continue;
            }
            perror("accept");
            exit(-1);
        }
        // 有连接到的客户端,创建一个新的子进程进行通讯
        pid_t pid=fork();
        if(pid==0){
    
    
            // 进入子进程
            printf("服务器接收到客户端连接,并创建了我这个子进程,我的pid: %d\n", getpid());
            // 输出客户端信息,并进行通讯
            char client_ip[16];
            inet_ntop(AF_INET,&clientaddr.sin_addr,client_ip,sizeof(client_ip));
            unsigned short clientport = ntohs(clientaddr.sin_port);
            printf("connected! client ip is %s, port is %d\n", client_ip,clientport);
            // 开始进行通讯
            while(1){
    
    
                // 获取客户端的数据并发送数据
                char rec_buf[1024]={
    
    0};
                //  read是阻塞函数只有当cfd中有数据过来时才会继续,否则while直接循环起飞了
                int read_ret = read(cfd, rec_buf, sizeof(rec_buf));
                if (read_ret==-1){
    
    
                    perror("read");
                    exit(-1);
                } else if (read_ret>0){
    
    
                    printf("cliend: %s", rec_buf);
                }else if(read_ret==0){
    
    
                    printf("client is closed!\n");
                    break;
                }
                // 给客户端发送数据
                char data[1024];
                fgets(data, sizeof(data),stdin);
                write(cfd, data,strlen(data));
            }
            close(cfd);
            exit(0);// 退出当前子进程,如果不进行其他的操作这个子进程的资源就无法回收了 
        }
    }
    close(lfd);// 关闭服务器socket
    return 0;
}

4.10 Uso de subprocesos múltiples para lograr concurrencia

#include<iostream>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<thread>

struct sockInfo
{
    
    
    int fd;
    pthread_t tid;
    struct sockaddr_in addr;
};

// 为了能让working函数使用那些局部变量,将这个局部变量统统存放在这个数组中
// 128表示最多连接128个客户端,并将它们的数据存在在这个数组中
struct sockInfo sockinfos[128];

void* working(void * arg){
    
    
    // 进入子线程
    // 子线程函数,用于和客户端通信,需要传入相关的参数
    struct sockInfo * pinfo=(struct sockInfo *) arg;
    std::cout<<"服务器接收到客户端连接,并创建了我这个子线程,我的id:"<<std::this_thread::get_id()<<std::endl;
    // 输出客户端信息,并进行通讯
    char client_ip[16];
    inet_ntop(AF_INET,&pinfo->addr.sin_addr,client_ip,sizeof(client_ip));
    unsigned short clientport = ntohs(pinfo->addr.sin_port);
    printf("connected! client ip is %s, port is %d\n", client_ip,clientport);
    // 开始进行通讯
    while(1){
    
    
        // 获取客户端的数据并发送数据
        char rec_buf[1024]={
    
    0};
        //  read是阻塞函数只有当cfd中有数据过来时才会继续,否则while直接循环起飞了
        int read_ret = read(pinfo->fd, rec_buf, sizeof(rec_buf));
        if (read_ret==-1){
    
    
            perror("read");
            exit(-1);
        } else if (read_ret>0){
    
    
            printf("cliend: %s", rec_buf);
        }else if(read_ret==0){
    
    
            printf("client is closed!\n");
            break;
        }
        // 给客户端发送数据
        char data[1024];
        fgets(data, sizeof(data),stdin);
        write(pinfo->fd, data,strlen(data));
    }
    close(pinfo->fd);
    pinfo->fd=-1;
    return NULL;
}

int main(){
    
    
    
    int lfd = socket(AF_INET,SOCK_STREAM,0);
    if(lfd==-1){
    
    
        perror("socket");
        exit(-1
        );
    }
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port=htons(9999);
    int bind_ret = bind(lfd,(sockaddr *) &saddr,sizeof(saddr));
    if(bind_ret==-1){
    
    
        perror("bind");
        exit(-1);
    }
    // 开启监听的开关,不会阻塞,就相当于打开了一个开关
    int listen_ret = listen(lfd,8);
    if(listen_ret==-1){
    
    
        perror("listen");
        exit(-1);
    }

    // 初始化数据,每一个,addr初始化成0,fd和tid初始化成-1
    int max = sizeof(sockinfos)/ sizeof(sockinfos[0]);
    for(int i=0;i<max;i++){
    
    
        bzero(&sockinfos[i],sizeof(sockinfos[i]));
        sockinfos[i].fd=-1;
        sockinfos[i].tid=-1;
    }


    while(1){
    
    
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);
        int cfd=accept(lfd, (struct sockaddr*)&client_addr, &len);
        // 寻找一个全部变量去放接受到的客户端的相关信息
        struct sockInfo *pinfo;
        for(int i=0;i<max;i++){
    
    
            if(sockinfos[i].fd==-1){
    
    
                pinfo=&sockinfos[i];
                break;
            }
            if(i==max-1){
    
    
                // 128个位置全部满了,等待一秒再从头开始
                sleep(1);
                i=-1;// for这个循环体结束后会自动+1
            }
        }
        
        // 这个时候pinfo里面对应的就是一个全局的变量的
        pinfo->fd=cfd;
        memcpy(&pinfo->addr, &client_addr,len);// 结构体不能直接相等,要使用memcpy拷贝内存
     
        // 创建子线程用于通信
        // 有个问题,创建子线程的时候传入的参数是定义在这个while函数中的,但是working函数是在main函数之外的
        //  因此working函数并不能使用这些参数,因为working函数执行的时候这个主线程的这个while已经结束了,这些
        //  局部变量全部都已经释放了
        pthread_create(&pinfo->tid,NULL,working,pinfo);

        // 设置线程分离,再不阻塞的情况下实现线程的自动回收
        pthread_detach(pinfo->tid);

        
    }
    close(lfd); 
    return 0;
}

4.11 Transición de estado TCP

Apretón de manos de tres vías para establecer conexión, transmisión de datos, apretón de manos de cuatro vías para desconectar

4.12 Reutilización portuaria semicerrada

El medio cierre se puede utilizar para transmisión unidireccional y se pueden utilizar funciones int shutdown();
la reutilización de puertos se puede setsockptimplementar mediante funciones. La reutilización de puertos puede resolver el problema de que el puerto que utilizará el programa actual todavía está ocupado por otros programas, es decir es decir, permite que todos utilicen un puerto de comunicación al mismo tiempo.

4.13 Multiplexación de E/S (multiplexación de E/S)

La entrada y salida de E/S se refiere a operaciones en la memoria.

La multiplexación de E / S permite que el programa monitoree múltiples descriptores de archivos al mismo tiempo, lo que puede mejorar el rendimiento del programa. Las principales formas de implementar la multiplexación en Linux son seleccionar, epoll y poll.

4.13.1 Uso de seleccionar

servidor.cpp

#include<iostream>
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>

int main(){
    
    

    int lfd=socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family=AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    int bind_ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
    if(bind_ret==-1){
    
    
        perror("bind");
        exit(-1);
    }

    int listen_ret = listen(lfd,8);
    if(listen_ret==-1){
    
    
        perror("listen");
        exit(-1);
    }

    // 创建fd_set,用来存放要监测的文件描述符
    fd_set rdset,tmp; // 一个用来保存原来的状态,一个用来送给内核
    FD_ZERO(&rdset);
    FD_SET(lfd,&rdset);
    int maxfd=lfd;
    while(1){
    
    
        tmp=rdset;
        // 调用select,让内核来检测哪些文件描述符有数据
        int select_ret = select(maxfd+1,&tmp,NULL,NULL,NULL);
        if(select_ret==-1){
    
    
            perror("select");
            exit(-1);
        }else if(select_ret==0){
    
    
            continue;
        }else if(select_ret>0){
    
    
            // 有select_ret个文件描述符产生了变化
            // 这个if用来判断有没有新的连接产生,如果有则创建新的cfd用于通信
            if (FD_ISSET(lfd,&tmp)){
    
    
                // lfd这个文件描述符专门用来检测是否有新的客户端链接进来了,如果有
                // lfd就会发生变化,但是lfd并不进行后续的通信,通信交给下面创建的cfd
                printf("client connected!\n");
                struct sockaddr_in clientaddr;
                socklen_t len = sizeof(clientaddr);
                int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len);
                FD_SET(cfd,&rdset); // 将新的文件描述符添加进集合中
                FD_SET(cfd,&tmp);
                maxfd = maxfd>cfd?maxfd:cfd;
            }
            // 遍历所有的fd,从lfd+1开始,因为lfd只是用来判断是否有新的连接,不用来进行通信
            printf("start detect fd\n");
            for(int i=lfd+1;i<=maxfd;++i){
    
    
                printf("now fd is:%d\n",i);
                if(FD_ISSET(i,&tmp)){
    
    
                    // 进了这个if说明文件描述符i发生了变化,可以进行通讯
                    char buf[1024]={
    
    0};
                    // read会阻塞住,直到client向server发送数据
                    int len =read(i,buf,sizeof(buf));
                    if(len==-1){
    
    
                        perror("read");
                        exit(-1);
                    }else if(len==0){
    
    
                        printf("client closed!\n");
                        close(i);
                        FD_CLR(i,&rdset);
                    }else if(len>0){
    
    
                        printf("read buf: %s\n",buf);
                        write(i,buf,strlen(buf)+1); // 回射
                    }
                }
            }
        }
    }

    close(lfd);

    return 0;
}

4.13.2 Uso de la encuesta

En comparación con select, poll parece resolver solo el problema numérico de 1024. Todavía existen otros problemas como el recorrido y la conversión del modo de usuario al modo kernel. La encuesta es simplemente más flexible y fácil de administrar que seleccionar.

#include<iostream>
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/poll.h>

int main(){
    
    
    int lfd=socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family=AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    int bind_ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
    if(bind_ret==-1){
    
    
        perror("bind");
        exit(-1);
    }

    int listen_ret = listen(lfd,8);
    if(listen_ret==-1){
    
    
        perror("listen");
        exit(-1);
    }
    // 定义一个存放所有pollfd的数组,这个数组和select中的文件描述符不同的是我可以设置的很大,可以超过1024
    struct pollfd pfds[2048];
    // 初始化
    for(int i=0;i<2048;++i){
    
    
        pfds[i].fd=-1; // 初始化为-1表示还没有监听
        pfds[i].events=POLLIN; // 默认监听读时间  
    }
    pfds[0].fd=lfd; // 将最开始的监听描述符放进去,这玩意仅仅用来监听是否有连接进来,不用于通信
    int nfds = 0;  // 和select一样,需要记录最大的那个文件描述符的索引

    while(1){
    
    
        // 调用poll,让内核来检测哪些文件描述符有数据
        int poll_rect = poll(pfds, nfds+1, -1);

        if(poll_rect==-1){
    
    
            perror("poll");
            exit(-1);
        }else if(poll_rect==0){
    
    
            continue;
        }else if(poll_rect>0){
    
    
            // 有poll_ret个文件描述符产生了变化
            // 这个if用来判断有没有新的连接产生,如果有则创建新的cfd用于通信
            // 注意是将监听描述符与检测的状态进行与运算
            if (pfds[0].revents&POLLIN){
    
    
                // lfd这个文件描述符专门用来检测是否有新的客户端链接进来了,如果有
                // lfd就会发生变化,但是lfd并不进行后续的通信,通信交给下面创建的cfd
                printf("client connected!\n");
                struct sockaddr_in clientaddr;
                socklen_t len = sizeof(clientaddr);
                int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len);
                // 将新的文件描述符添加进集合中
                for(int i=1;i<2048;++i){
    
    
                    if (pfds[i].fd==-1){
    
    
                        pfds[i].fd=cfd;
                        pfds[i].events=POLLIN;
                        if(nfds<i){
    
    
                            nfds=i;
                        }
                        break;
                    }
                }
            }
            // 遍历所有的fd,从lfd+1开始,因为lfd只是用来判断是否有新的连接,不用来进行通信
            printf("start detect fd\n");
            for(int i=1;i<=nfds;++i){
    
    
                printf("now fd is:%d\n",pfds[i].fd);
                if(pfds[i].revents&POLLIN){
    
    
                    // 进了这个if说明文件描述符pfds[i].fd发生了变化,可以进行通讯
                    char buf[1024]={
    
    0};
                    // read会阻塞住,直到client向server发送数据
                    int len =read(pfds[i].fd,buf,sizeof(buf));
                    if(len==-1){
    
    
                        perror("read");
                        exit(-1);
                    }else if(len==0){
    
    
                        printf("client closed!\n");
                        close(pfds[i].fd);
                        pfds[i].fd=-1;
                    }else if(len>0){
    
    
                        printf("read buf: %s\n",buf);
                        write(pfds[i].fd,buf,strlen(buf)+1); // 回射
                    }else{
    
    
                        printf("不知道什么原因,信息未能发送\n");
                    }
                }
            }
        }
    }

    close(lfd);

    return 0;
}

4.13.3 Uso de epoll

epoll tiene dos modos de trabajo, a saber:
modo LT (disparador horizontal) y modo ET (disparador de flanco). El modo LT significa que si el kernel le notifica que un descriptor de archivo está listo pero usted no lo ha procesado, el kernel siempre notificarte. ET, por otro lado, solo te notificará una vez, independientemente de si has procesado este operador de archivos o no.

El modo LT es el modo de activación predeterminado de epoll.
Al usar el modo ET, debe tener en cuenta que la lectura es de bloqueo de forma predeterminada. Debe establecer el atributo del descriptor de archivo leído por lectura en no bloqueo.

epoll_server.cpp

#include<iostream>
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/epoll.h>

int main(){
    
    
    int lfd=socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family=AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    int bind_ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
    if(bind_ret==-1){
    
    
        perror("bind");
        exit(-1);
    }

    int listen_ret = listen(lfd,8);
    if(listen_ret==-1){
    
    
        perror("listen");
        exit(-1);
    }

    // 使用epoll检测哪些文件描述符发生了变化
    // 使用create创建epoll实例
    int epfd = epoll_create(1);
    // 将监听描述符lfd加入到epfd中
    struct epoll_event epev;
    epev.events = EPOLLIN;
    epev.data.fd = lfd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
    // epevs用来保存epoll检测到的发生变化的文件描述符
    struct epoll_event epevs[1024]; 
    while(1){
    
    
        // epoll_wait_ret表示有几个文件描述符发生了变化,具体是哪些发生了变化见epevs数组
        // 1024表示最大检测的个数是1024,-1 表示阻塞
       int epoll_wait_ret =  epoll_wait(epfd,epevs,1024,-1);
       if (epoll_wait_ret==-1){
    
    
        perror("epoll_wait");
        exit(-1);
       }
       for(int i=0;i<epoll_wait_ret;++i){
    
    
        if (epevs[i].data.fd==lfd){
    
    
            // 如果发生变化的文件描述符中包括lfd,表示有新的客户端连接进来了
            printf("client connected!\n");
            struct sockaddr_in clientaddr;
            socklen_t len = sizeof(clientaddr);
            int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len);
            epev.events = EPOLLIN;
            epev.data.fd=cfd;
            epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev); // 将cfd添加进epoll实例中
        }else{
    
    
            // 文件描述符epevs[i].data.fd发生了变化,表示有文件过来了
            char buf[1024]={
    
    0};
            // read会阻塞住,直到client向server发送数据
            int len =read(epevs[i].data.fd,buf,sizeof(buf));
            if(len==-1){
    
    
                perror("read");
                exit(-1);
            }else if(len==0){
    
    
                printf("client closed!\n");
                // 将epevs[i].data.fd这个文件描述符移出epoll实例
                epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);
                close(epevs[i].data.fd);
            }else if(len>0){
    
    
                printf("read buf: %s\n",buf);
                write(epevs[i].data.fd,buf,strlen(buf)+1); // 回射
            }else{
    
    
                printf("不知道什么原因,信息未能发送\n");
            }
        }
       }

    }

    close(lfd);
    close(epfd); //注意创建的epoll实例对应的文件描述符也需要关闭
    return 0;
}

4.14 comunicación UDP

El proceso de comunicación UDP es mucho más simple que el protocolo de enlace y el saludo de TCP: simplemente cree el socket directamente y luego envíe uno y acéptelo
Insertar descripción de la imagen aquí
udp_server.cpp

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string>
#include<arpa/inet.h>
#include<string.h>

int main(){
    
    
    // UDP使用的是SOCK_DGRAM不是TCP的SOCK_STREAM
    int fd = socket(PF_INET,SOCK_DGRAM,0);
    if (fd==-1){
    
    
        perror("socket");
        exit(-1);
    }
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family=AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 绑定
    int bind_ret = bind(fd,(struct sockaddr *)&saddr,sizeof(saddr));
    if(bind_ret==-1){
    
    
        perror("bind");
        exit(-1);
    }

    // 通信
    while(1){
    
    
        // 接收数据
        char recvbuf[128];
        char ipbuf[16];
        struct sockaddr_in clientaddr;
        socklen_t len = sizeof(clientaddr);
        int num = recvfrom(fd,recvbuf,sizeof(recvbuf),0,(struct sockaddr *)&clientaddr, &len);
        if(num==-1){
    
    
            perror("recvfrom");
            exit(-1);
        }else{
    
    
            inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ipbuf,sizeof(ipbuf));
            unsigned short clientport = ntohs(clientaddr.sin_port);
            printf("client IP: %s,port: %d\n",ipbuf,clientport);
            printf("client: %s\n",recvbuf);
        }
        // 发送数据
        sendto(fd,recvbuf,strlen(recvbuf)+1,0,(struct sockaddr *)&clientaddr, sizeof(clientaddr));
    }
    close(fd);
    return 0;
}

udp_client.cpp

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string>
#include<arpa/inet.h>
#include<string.h>

int main(){
    
    
    // UDP使用的是SOCK_DGRAM不是TCP的SOCK_STREAM
    int fd = socket(PF_INET,SOCK_DGRAM,0);
    if (fd==-1){
    
    
        perror("socket");
        exit(-1);
    }
    // 2、设置服务器的 IP 192.168.18.128和端口 9999,这样客户端才知道要去哪里找
    struct sockaddr_in serveraddr;
    serveraddr.sin_port = htons(9999);
    serveraddr.sin_family=AF_INET;
    inet_pton(AF_INET, "192.168.18.128",&serveraddr.sin_addr.s_addr);

    int num=0;
    // 通信
    while(1){
    
    
        // 发送数据
        char sendbuf[128];
        sprintf(sendbuf,"hello I'm cliend, this is %d\n",num++);  // 格式化数据
        sendto(fd,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr *)&serveraddr, sizeof(serveraddr));

        // 接受数据
        char recvbuf[128];
        socklen_t len = sizeof(serveraddr);
        // 接受数据是通过fd接受的,和ip啥的没有关系了,直接设置为NULL也行
        int num = recvfrom(fd,recvbuf,sizeof(recvbuf),0,NULL, NULL);
        if(num==-1){
    
    
            perror("recvfrom");
            exit(-1);
        }else{
    
    
            printf("server: %s\n",recvbuf);
        }
        sleep(1);

    }
    close(fd);
    return 0;
}

4.14 Transmisión

1. La transmisión es cuando el host envía un mensaje a todas las computadoras en la subred. El mensaje de transmisión contiene una dirección IP especial. La parte del indicador del host de esta dirección IP es todo binario 1. 2. ¿Cuál es la parte del indicador del host de la IP? dirección: 192.168.23.21
, el primer 192.168.23 es el ID de la red, el segundo 21 es el ID del host y la parte del indicador del host es todo 1, que es 255. Por supuesto, los primeros dígitos son la ID de la red y los últimos dígitos son la ID del host, que está determinada por la máscara de subred.
3. Cabe señalar que las transmisiones solo se pueden usar en la LAN y el cliente debe tener ha estado vinculado al puerto utilizado por el servidor de transmisión para recibirlo al mensaje de transmisión, porque aunque el host envía el mensaje a todas las computadoras en la LAN, por supuesto no lo recibirá si no monitorea este puerto.

Nota: Si desea que un socket envíe transmisiones, debe usar setsockopt() para configurarlo, al igual que configurar la reutilización de puertos.

servidor_amplio.cpp

// TCP通信服务器端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<iostream>

int main(){
    
    
    // 1、创建监听的socket,注意使用的是UDP不是TCP,因此是SOCK_DGRAM,实际上广播使用哪个都可以,只是通常时UDP
    int lfd = socket(PF_INET,SOCK_DGRAM,0);
    if (lfd==-1){
    
    
        perror("socket");
        exit(-1);
    }

    // 2、要想进行广播,需要设置socket的广播属性
    int op=1;
    setsockopt(lfd,SOL_SOCKET,SO_BROADCAST,&op,sizeof(op));

    // 3、创建广播的地址
    struct sockaddr_in saddrs;
    saddrs.sin_family = AF_INET;
    saddrs.sin_port=htons(9999); // 端口为9999,通过htons转换为网络字节序
    inet_pton(AF_INET, "192.168.18.255",&saddrs.sin_addr.s_addr); // 255是特殊的,表示向192.168.18.内所有设备广播
    // 服务端只是发送广播,不需要bind,每次sendto时操作系统会自己指定一个网络端口进行发送

    // 4、通信,发送广播
    int num=0;
    while(1){
    
    
        char brosend[128];
        sprintf(brosend,"hello I'm server, this is %d brocast\n",num++);  // 格式化数据
        // 直接向saddrs对应的地址发送消息,因为最后一位是255,所以是广播
        // 打印我广播的目标ip
        char ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &(saddrs.sin_addr.s_addr), ip, INET_ADDRSTRLEN);
        printf("Bro IP: %s\n", ip);
        printf("Bro Port: %d\n", ntohs(saddrs.sin_port));
        // 发送广播信息,由于最后一位是255,因为这个消息会发送给这个网络id下所有的设备
        sendto(lfd,brosend,strlen(brosend)+1,0,(struct sockaddr *)&saddrs, sizeof(saddrs));
        sleep(1);
    }
    // 5、关闭所有打开的文件描述符
    close(lfd);
    return 0;
   
}

broatclient.cpp

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string>
#include<arpa/inet.h>
#include<string.h>

// 这个客户端只接受来自服务器的广播消息,不发送信息
int main(){
    
    
    // UDP使用的是SOCK_DGRAM不是TCP的SOCK_STREAM
    int fd = socket(PF_INET,SOCK_DGRAM,0);
    if (fd==-1){
    
    
        perror("socket");
        exit(-1);
    }
    // 2、设置可能发送消息过来的ip地址和端口,才可以接收到这个ip主机发送的广播信息
    struct sockaddr_in addr;
    addr.sin_port = htons(9999);
    addr.sin_family=AF_INET;
    // 注意,这里是将网络接口设置为这个计算机上的任意网络接口,表示我接受来自这台计算机上所有接口的数据
    addr.sin_addr.s_addr = INADDR_ANY; 

    // 因为要在同一台主机上进行测试但是每一次运行这个程序都会使用bind相同的addr
    // 因此都会绑定到0.0.0.0,虽然这个ip很特殊,但是也不能被绑定两次,除非设置成可以复用
    int reuse = 1;// 设置为可以复用
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
    
    
        perror("setsockopt");
        exit(-1);
    }
    // 绑定
    int bind_rect = bind(fd,(struct sockaddr*)&addr,sizeof(addr));
    if(bind_rect==-1){
    
    
        perror("bind");
        exit(-1);
    }

    // 查看这个进程绑定的ip和端口
    struct sockaddr_in taddr;
    socklen_t addrlen = sizeof(taddr);
    // 获取套接字绑定的本地地址
    if (getsockname(fd, (struct sockaddr *)&taddr, &addrlen) == 0) {
    
    
        char ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &(taddr.sin_addr.s_addr), ip, INET_ADDRSTRLEN);
        printf("Local IP: %s\n", ip);
        printf("Local Port: %d\n", ntohs(taddr.sin_port));
    } else {
    
    
        perror("getsockname");
    }

    // 不需要监听listen,listen通常时服务端使用,用于监听是否有新的连接请求,广播一般使用的时UDP,不需要建立连接
    // 即使是tcp连接,客户端也不需要listen,因为client是发起连接的一方,监测有无新的连接请求是服务器干的事情
    // 通信
    while(1){
    
    
        // 接受数据
        char recvbuf[128];
        // recvfrom默认是阻塞的,因此不用sleep
        int num=recvfrom(fd,recvbuf,sizeof(recvbuf),0,NULL,NULL);
        printf("server broadcast message: %s\n",recvbuf);
    }
    close(fd);
    return 0;
}

4.15 Multidifusión (Multidifusión)

1. Similar a la transmisión, pero el objetivo es un grupo específico de clientes, y la multidifusión se puede aplicar tanto a LAN como a WAN
2. Solo los clientes que se hayan unido al grupo de multidifusión recibirán datos de multidifusión
3. La multidifusión también tiene restricciones de IP específicas

4.16 Los sockets locales implementan la comunicación entre procesos

Utilice sockets locales para la comunicación entre procesos locales.
Nota: Antes de crear un socket local, debe utilizar unlink para eliminar el archivo de socket local que pueda existir y no se utilice. El archivo de socket local creado no se eliminará automáticamente

ipc_server.cpp

// 使用tcp进行本地进程间通讯
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<sys/un.h>

int main(){
    
    
    // 上来先清楚可能重名的本地套接字
    unlink("server.sock");
    // 1、 创佳套接字
    int lfd = socket(AF_LOCAL, SOCK_STREAM,0);
    if(lfd==-1){
    
    
        perror("socket");
        exit(-1);
    }
    // 2、创建并绑定本地的套接字地址
    struct sockaddr_un addr;
    addr.sun_family=AF_LOCAL;
    strcpy(addr.sun_path,"server.sock");
    int bind_rect = bind(lfd, (struct sockaddr*)&addr,sizeof(addr));
    if(bind_rect== -1){
    
    
        perror("bind");
        exit(-1);
    }

    // 3、 监听
    int listen_rect=listen(lfd,100);
    if (listen_rect==-1){
    
    
        perror("listen");
        exit(-1);
    }

    // 4、等待客户端连接
    struct sockaddr_un clientaddr;
    socklen_t len=sizeof(clientaddr);
    int cfd = accept(lfd,(struct sockaddr*)&clientaddr,&len);
    if(cfd==-1){
    
    
        perror("accept");
        exit(-1);
    }
    printf("client socket filename:%s\n",clientaddr.sun_path);

    // 5、通信
    while(1){
    
    
        char buf[128];
        int recv_rect = recv(cfd,buf,sizeof(buf),0);
        if (recv_rect==-1){
    
    
            perror("recv");
            exit(-1);
        }else if(recv_rect==0){
    
    
            printf("client is closed\n");
            close(cfd);
            break;
        }else{
    
    
            printf("client: %s\n", buf);
            send(cfd,buf,len,0);
        }
    }
    close(lfd);
    return 0;
}


ipc_client.cpp

// 使用tcp进行本地进程间通讯
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<sys/un.h>

int main(){
    
    
    // 上来先清楚可能重名的本地套接字
    unlink("client.sock");
    // 1、 创佳套接字
    int cfd = socket(AF_LOCAL, SOCK_STREAM,0);
    if(cfd==-1){
    
    
        perror("socket");
        exit(-1);
    }
    // 2、创建并绑定本地的套接字地址
    struct sockaddr_un addr;
    addr.sun_family=AF_LOCAL;
    strcpy(addr.sun_path,"client.sock");
    int bind_rect = bind(cfd, (struct sockaddr*)&addr,sizeof(addr));
    if(bind_rect== -1){
    
    
        perror("bind");
        exit(-1);
    }

    // 3、指定服务器并连服务器,因此客户端需要提前知道服务器生成的本地的套接字的名称
    struct sockaddr_un serveraddr;
    serveraddr.sun_family=AF_LOCAL;
    strcpy(serveraddr.sun_path,"server.sock");

    // connect默认是阻塞的,直到连接建立
    int con_rect = connect(cfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
    if(con_rect==-1){
    
    
        perror("connect");
        exit(-1);
    }
    printf("connected!\n");

    // 4、通信
    int num=0;
    while(1){
    
    
        // 先发送数据
        char buf[128];
        sprintf(buf,"hello I am client %d\n",num++);
        send(cfd,buf,strlen(buf)+1,0);
        // 接受数据
        int recv_rect = recv(cfd,buf,sizeof(buf),0);
        if (recv_rect==-1){
    
    
            perror("recv");
            exit(-1);
        }else if(recv_rect==0){
    
    
            printf("server is closed\n");
            close(cfd);
            break;
        }else{
    
    
            printf("server: %s\n", buf);
        }
        sleep(1);
    }
    return 0;
}


5. Proyecto de combate real.

5.1 Bloqueo/sin bloqueo, síncrono/asíncrono (red IO)

1. La IO asíncrona no es bloqueante y la IO síncrona es bloqueante. El tutorial siempre ha utilizado io sincrónico. Cuando se utilizan funciones como recv para leer información de cfd, el proceso en realidad se bloquea. Incluso si ya hay datos en cfd, la velocidad de lectura es muy rápida y no te das cuenta. Si es asincrónico, el proceso no se bloqueará al leer los datos. Cuando se complete la lectura, se notificará al proceso que se puede usar y el proceso puede usarlo directamente. 2. La IO asincrónica requiere el uso de un sistema
especial funciones de llamada, como aio_read(), aio_write(), io asíncrono es muy problemático de usar y generalmente no se usa
3. Una llamada típica a la interfaz io de red se divide en dos etapas, "datos listos" (esperando que los datos lleguen). venir) y "lectura de datos" (movidos del búfer tcp a la memoria)) Ambas etapas toman tiempo.

5.2 5 modelos IO

  1. Bloqueo, lectura regular, recepción, el proceso se detiene y espera la llegada del archivo.
  2. Llamada sin bloqueo y sin bloqueo, el resultado siempre se devuelve y el resultado devuelto se utiliza para determinar si el archivo ha llegado.
  3. Multiplexación de IO La multiplexación, selección, sondeo y epoll de IO realizan la multiplexación de IO y detectan si los datos han llegado en varios descriptores de archivos al mismo tiempo
  4. Impulsado por señal Impulsado por señal, cuando el evento io está listo, se envía la señal SIGIO para indicarle al proceso que lo maneje
  5. Asíncrono, utilice la interfaz io asíncrona para procesar archivos

5.3 servidor web y protocolo HTTP

El servidor web (servidor web) es un software de servidor cuya función principal es responder a las solicitudes http enviadas por el cliente.

5.4 Marco básico de programación de servidores

Hay muchos tipos de programas de servidor, pero el marco básico es el mismo, la única diferencia es el procesamiento lógico,
generalmente hay 4 bloques:

  1. Unidad de procesamiento de E/S, espera y procesa solicitudes enviadas por los clientes, lee y escribe datos de red.
  2. La unidad de procesamiento lógico realiza algún procesamiento lógico basado en la información analizada, que es para determinar qué hacer.
  3. Una unidad de almacenamiento en red es una base de datos o algo así.
  4. Cola de solicitudes, método de comunicación entre varias unidades.

5.5 Modo de procesamiento de eventos

Hay principalmente tres tipos de trabajo realizado por el servidor: E/S, señales y eventos cronometrados. Hay dos modos eficientes de procesamiento de eventos, a saber, Reactor y Proactor.

1. Modo reactor:
el hilo principal solo es responsable de monitorear si hay eventos en el descriptor de archivo. Si hay eventos, notificará a otros hilos de trabajo para su procesamiento. Además, el hilo principal no hace otras cosas.

2. Modo Proactor:
todas las operaciones de E / S se entregan al subproceso principal para su procesamiento, y el subproceso de trabajo solo es responsable de la lógica empresarial.

Ambos modos se pueden implementar usando IO síncrono o asíncrono, pero en Linux, debido a que la interfaz IO asíncrona no es perfecta, el IO síncrono generalmente se usa para simular proactor y reactor.

5.6 Grupo de subprocesos

Un grupo de subprocesos es una serie de subprocesos. Cuando no hay ninguna tarea, todos los subprocesos duermen y se despiertan uno por uno para realizar la tarea, porque despertar un subproceso es más rápido que crear un nuevo subproceso. Características
:

  1. espacio para el tiempo
  2. es un conjunto de recursos estáticos
  3. Todos los recursos ya están asignados cuando se crean y no es necesario asignarlos dinámicamente.
  4. Cuando el servidor termina de procesar una conexión de cliente, puede volver a colocar los recursos directamente en el grupo sin ejecutar una llamada al sistema para liberar los recursos.

おすすめ

転載: blog.csdn.net/qq_41926099/article/details/131077503