[Red de computadoras] Encapsulación de calcetines y eco basada en TCP

prefacio

En el artículo anterior simplemente implementamos una sala de chat en línea usando Udp, hoy aprenderemos a usar sockets TCP.

Eco basado en TCP

Variables miembro

//端口号
uint16_t _port;
//要执行的回调
func_t _func;
//listen套接字
int _socklisten;

función miembro

  • Init
    completa la creación, vinculación y monitoreo del socket
void initServer()
{
    
    
    // 1 创建socket接口,打开网络文件
    _socklisten = socket(AF_INET, SOCK_STREAM, 0);
    if (_socklisten < 0)
    {
    
    
        logMessage(Error, "create socket error");
        exit(SOCKED_ERR);
    }
    logMessage(Info, "create socket success");

    // 2 给服务器指明IP地址和Port
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));

    local.sin_family = AF_INET;
    local.sin_port = htons(_port);      // host to network short
    local.sin_addr.s_addr = INADDR_ANY; // bind本主机任意ip
    if (bind(_socklisten, (sockaddr*)&local, sizeof(local)) < 0)
    {
    
    
        logMessage(Error, "bind socket error code:%d info:%s", errno, strerror(errno));
        exit(BIND_ERR);
    }
    logMessage(Info, "bind socket success");

    // 3 监听
    if (listen(_socklisten, backlog) < 0)
    {
    
    
        logMessage(Error, "listen socket error");
        exit(LISTEN_ERR);
    }
    logMessage(Info, "listen socket success");
}
  • Para empezar
    lo dividimos en cuatro versiones para lograr el negocio final:
    • 1. Uso de prueba de servicio, no hay mucha explicación.

    • 2. Versión multiproceso
      Al utilizar el método multiproceso, varios procesos pueden manejar diferentes solicitudes de servicio, pero hay dos cuestiones a considerar al utilizar este método:

      • a.¿Debería esperar el proceso hijo? ¿Quién esperará?
        Por supuesto, el proceso hijo debe esperar, pero no queremos que sea el proceso padre, porque el proceso padre tiene que ocuparse de los asuntos de otros clientes. Aquí hay dos formas de resolver este problema:

        1. Utilice señales para ignorar
          la señal SIGCHLD(SIGCHLD,SIG_IGN);
        2. Cuando
          el proceso padre sale, el proceso hijo se convierte en un proceso huérfano.
      • b.¿Es necesario cerrar los fds que no son necesarios para el proceso hijo y el proceso padre?
        El fd del proceso padre debe cerrarse, mientras que el fd del proceso hijo puede optar por cerrarse. Si el proceso principal no cierra el fd utilizado para la comunicación, pronto generará más y más fds y luego se producirán fugas de descriptores de archivos.

    • 3. Versión original del hilo.
      Lo más importante a lo que debemos prestar atención aquí es que la tarea del hilo necesita pasar muchos parámetros, por lo que necesitamos crear una clase ThreadData.

      ThreadDate *td = new ThreadData(sock, clientip, clientport, this);
      en la tarea del subproceso, busque una manera de ejecutar la función que se ejecutará en la clase mediante conversión de tipos.
      Si no desea reciclar el hilo, puede usar pthread_detach(pthread_self());

    • 4. Versión del grupo de subprocesos
      El grupo de subprocesos resuelve principalmente solicitudes breves y frecuentes. En el capítulo sobre subprocesos múltiples, hablaremos en detalle sobre el diseño del grupo de subprocesos.

void start()
{
    
    
    while (true)
    {
    
    
        struct sockaddr_in client;
        socklen_t len = sizeof(client);

        // 4 获取连接,accept
        int sock = accept(_socklisten, (struct sockaddr *)&client, &len);
        if (sock < 0)
        {
    
    
            logMessage(Warning, "获取连接失败,code:%d,error string:%s", errno, strerror(errno));
            continue;
        }

        std::string clientip = inet_ntoa(client.sin_addr);
        uint16_t clientport = ntohs(client.sin_port);

        // 5 获取连接成功开始进行业务处理
        logMessage(Info, "获取连接成功:%d from %d,client:%s-%d", sock, _socklisten, clientip.c_str(), clientport);

        // 1 test
        //  service(sock,clientip,clientport);

        // 2 多进程
        //  pid_t id = fork();
        //  if(id < 0)
        //  {
    
    
        //      logMessage(Warning,"创建线程失败 code: %d error:%s",errno,strerror(errno));
        //      continue;
        //  }
        //  else if(id == 0)
        //  {
    
    
        //      //子进程可以选择关闭不需要的fd
        //      close(_socklisten);
        //      if(fork() > 0) exit(0);

        //     //现在是孙子进程被bash1领养了,不需要等待了
        //     service(sock,clientip,clientport);
        //     exit(0);
        //  }

        // //父进程必须关闭不用的fd防止fd泄露
        // close(sock);

        // 3 源生线程
        // pthread_t tid;
        // ThreadDate *td = new ThreadDate(sock, clientip, clientport, this);
        // pthread_create(&tid, nullptr, threadRoutine, td);


        // 4 线程池版本
        Task t(sock,clientip,clientport,std::bind(&TcpServer::service,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
        ThreadPool<Task>::GetInstance()->pushTask(t);
    }
}

Clase SOCK encapsulada

Variables miembro

Solo se necesita una variable miembro: en el lado del servidor, puede ser un fd de escucha y en el lado del cliente, puede usarse como un fd de comunicación.

int _sock;

función miembro

  • El socket
    es el mismo que udp, primero es necesario crear el socket.
void Socket()
{
    
    
    _sock = socket(AF_INET, SOCK_STREAM, 0);
    if (_sock < 0)
    {
    
    
        logMessage(Error, "create socket error id:%d info:%s", errno, strerror(errno));
        exit(SOCKET_ERR);
    }
}

  • Bind
    es lo mismo que udp: el socket necesita la ip + número de puerto correspondiente para vincularse.

void Bind(const uint16_t port)
{
    
    
    struct sockaddr_in local;  
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = INADDR_ANY;

    if (bind(_sock, (sockaddr *)&local, sizeof(local)) < 0)
    {
    
    
        logMessage(Error, "Bind error id:%d info:%s", errno, strerror(errno));
        exit(BIND_ERR);
    }
    
    logMessage(Info, "Bind success");

}

  • listening
    establece el socket TCP en estado de escucha para que pueda recibir solicitudes de conexión de los clientes. gbacklog es el número máximo de solicitudes de conexión en espera de ser aceptadas, no el número máximo de conexiones. En términos generales, la longitud de la cola es gbacklog+1.
void Listen()
{
    
    
    if (listen(_sock, gbacklog) < 0)
    {
    
    
        logMessage(Error, "Listen error id:%d info:%s", errno, strerror(errno));
        exit(LISTEN_ERR);
    }
    logMessage(Info, "Bind success");
}

  • aceptar
    se utiliza para aceptar la solicitud de conexión del cliente y crear un nuevo socket para comunicarse con el cliente.
// 将客户端信息存起来
int Accept(std::string *clientip, uint16_t *clientport)
{
    
    
    sockaddr_in temp;
    socklen_t len = sizeof(temp);
    int sock = accept(_sock, (sockaddr *)&temp, &len);
    if (sock < 0)
    {
    
    
        logMessage(Error, "Accept error id:%d info:%s", errno, strerror(errno));
    }
    else 
    {
    
    
        *clientip = inet_ntoa(temp.sin_addr);
        *clientport = ntohs(temp.sin_port);
    }

    logMessage(Info, "accept success");

    return sock;
}

  • El cliente Connect
    inicia una solicitud de conexión al servidor

int Connect(const std::string serverip,const uint16_t& serverport)
{
    
    
    sockaddr_in server;
    memset(&server,0,sizeof(server));

    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());

    return connect(_sock,(sockaddr*)&server,sizeof(server));
}

  • Fd、Cerrar
int Fd()
{
    
    
    return _sock;
}

void Close()
{
    
    
    close(_sock);
}

Conclusión

En este punto, hemos completado la encapsulación Sock de Tcp.

Supongo que te gusta

Origin blog.csdn.net/m0_73209194/article/details/132114558
Recomendado
Clasificación