【计算机网络】基于Tcp的echo和Sock封装

前言

上一篇文章我们用Udp简单实现了一个网络聊天室,今天我们一起来学习使用TCP套接字。

基于Tcp的echo

成员变量

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

成员函数

  • Init
    完成套接字的创建、绑定、监听
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");
}
  • start
    我们分为四个版本来实现最终的业务:
    • 1、service 测试使用,不多讲解

    • 2、多进程版本
      使用多进程的方式,可以让多个进程处理不同的service请求,但使用这个方式需要考虑两个问题:

      • a、子进程要不要等待?谁来等待?
        子进程当然需要等待,但我们不希望是父进程,因为父进程还要去处理其他客户端的事情,这里就有两种方式来解决这个问题:

        1. 使用信号忽略掉SIGCHLD
          signal(SIGCHLD,SIG_IGN);
        2. 托孤给bash进程
          父进程退出,子进程就变成了孤儿进程。
      • b、子进程和父进程不需要的fd是不是都需要关闭?
        父进程的fd必须要关掉,而子进程的fd可以选择关掉。如果父进程不关闭用于通信的fd,很快就会导致fd越来越多,进而发生文件描述符泄露

    • 3、源生线程版本
      源生线程这里最需要注意的就是线程任务需要传入的参数很多,因此我们要构建一个ThreadData类

      ThreadDate *td = new ThreadData(sock, clientip, clientport, this);
      在线程任务内再通过类型转换想办法执行类内要执行的函数。
      若不想回收线程,可以使用pthread_detach(pthread_self());

    • 4、线程池版本
      线程池主要解决简短、频繁的请求。在多线程章节我们再详细聊聊线程池的设计。

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

封装SOCK类

成员变量

仅需要一个成员变量,在服务端它可以是监听fd,在客户端他可以被当作通信fd使用。

int _sock;

成员函数

  • Socket
    和udp一样,套接字需要先被创建出来。
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
    和udp一样,套接字需要bind对应的ip+端口号

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
    将TCP套接字设置为监听状态,使其可以接收客户端的连接请求。gbacklog是最大的等待被接受的连接请求数,而不是最多的连接数,一般而言这个队列长度是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");
}

  • accept
    用于接受客户端的连接请求,并创建一个新的套接字与客户端通信
// 将客户端信息存起来
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;
}

  • Connect
    客户端向服务端发起连接请求

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、Close
int Fd()
{
    
    
    return _sock;
}

void Close()
{
    
    
    close(_sock);
}

结语

至此,Tcp的Sock封装我们就全部搞定了,

Guess you like

Origin blog.csdn.net/m0_73209194/article/details/132114558