[Rede de Computadores] Eco baseado em TCP e encapsulamento Sock

Prefácio

No artigo anterior, simplesmente implementamos uma sala de chat online usando Udp. Hoje aprenderemos como usar soquetes TCP.

Eco baseado em TCP

Variáveis ​​de membro

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

função de membro

  • Init
    completa a criação, vinculação e monitoramento do soquete
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");
}
  • começar
    dividimos em quatro versões para chegar ao negócio final:
    • 1. Uso de teste de serviço, sem muita explicação

    • 2. Versão multiprocesso
      Usando o método multiprocesso, vários processos podem lidar com diferentes solicitações de serviço, mas há duas questões a serem consideradas ao usar este método:

      • a. O processo filho deve esperar? Quem vai esperar?
        É claro que o processo filho precisa esperar, mas não queremos que seja o processo pai, porque o processo pai tem que lidar com assuntos de outros clientes. Aqui estão duas maneiras de resolver esse problema:

        1. Use sinais para ignorar
          sinal SIGCHLD(SIGCHLD,SIG_IGN);
        2. Quando
          o processo pai é encerrado, o processo filho se torna um processo órfão.
      • b. Os fds que não são necessários ao processo filho e ao processo pai precisam ser fechados?
        O fd do processo pai deve ser fechado, enquanto o fd do processo filho pode optar por ser fechado. Se o processo pai não fechar o fd usado para comunicação, isso logo levará a mais e mais fds e, então, ocorrerão vazamentos no descritor de arquivo.

    • 3. Versão nativa do thread.
      A coisa mais importante a observar aqui é que a tarefa do thread precisa passar muitos parâmetros, então precisamos construir uma classe ThreadData.

      ThreadDate *td = new ThreadData(sock, clientip, clientport, this);
      Na tarefa thread, encontre uma forma de executar a função a ser executada na classe por meio da conversão de tipo.
      Se você não quiser reciclar o thread, você pode usar pthread_detach(pthread_self());

    • 4. Versão do pool de threads
      O pool de threads resolve principalmente solicitações curtas e frequentes. No capítulo multithreading, falaremos detalhadamente sobre o design do pool de threads.

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);
    }
}

Classe SOCK encapsulada

Variáveis ​​de membro

Apenas uma variável de membro é necessária. No lado do servidor, pode ser um fd de escuta e, no lado do cliente, pode ser usado como um fd de comunicação.

int _sock;

função de membro

  • Soquete
    é igual a udp. O soquete precisa ser criado primeiro.
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
    é o mesmo que udp. O soquete precisa do número da porta ip + correspondente ao bind.

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");

}

  • listen
    define o soquete TCP para o estado de escuta para que possa receber solicitações de conexão de clientes. gbacklog é o número máximo de solicitações de conexão aguardando aceitação, não o número máximo de conexões. De modo geral, o comprimento da fila é 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");
}

  • aceitar
    é usado para aceitar a solicitação de conexão do cliente e criar um novo soquete para se comunicar com o 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;
}

  • O cliente Connect
    inicia uma solicitação de conexão com o 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、Fechar
int Fd()
{
    
    
    return _sock;
}

void Close()
{
    
    
    close(_sock);
}

Conclusão

Neste ponto, concluímos o encapsulamento Sock do Tcp.

Acho que você gosta

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