云计算百科
云计算领域专业知识百科平台

构建TCP服务器:从基础到线程池版本的实现与测试详解

目录

1、TcpServerMain.cc

2、TcpServer.hpp

2.1、TcpServer类基本结构

2.2、构造析构函数

2.3、InitServer()

2.4、Loop()

2.4.1、Server 0(不靠谱版本)

2.4.2、Server 1(多进程版本)

2.4.3、Server 2(多线程版本)

2.4.4、Server 3(线程池版本)

3、TcpClientMain.cc

4、测试结果

4.1、不靠谱版本

4.2、多进程版本

4.3、多线程版本

4.4、线程池版本

5、完整代码

5.1、Makefile

5.2、TcpClientMain.cc

5.3、TcpServer.hpp

5.4、TcpServerMain.cc

前面几弹使用UDP协议实现了相关功能,此弹使用TCP协议实现客户端与服务端的通信,相比与UDP协议,TCP协议更加可靠,也更加复杂!与UDP类似,我们先写主函数,然后实现相关函数!

1、TcpServerMain.cc 服务端主函数使用智能指针构造Server对象,然后调用初始化与执行函数,调用主函数使用该可执行程序 + 端口号!

// ./tcpserver 8888 int main(int argc,char* argv[]) {     if(argc != 2)     {         std::cerr << "Usage: " << argv[0] << " local-post" << std::endl;         exit(0);     }       uint16_t port = std::stoi(argv[1]);       std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);       tsvr->InitServer();     tsvr->Loop();     return 0; }

2、TcpServer.hpp TcpServer.hpp封装TcpServer类!

枚举常量:

enum  {     SOCKET_ERROR,     BIND_ERROR,     LISTEN_ERROR }; 全局静态变量:

const static uint16_t gport = 8888; const static int gsockfd = -1; const static int gblcklog = 8; 2.1、TcpServer类基本结构 TcpServer类的基本成员有端口号,文件描述符,与运行状态!

// 面向字节流 class TcpServer { public:     TcpServer(uint16_t port = gport);     void InitServer();     void Loop();     ~TcpServer(); private:     uint16_t _port;     int _sockfd; // TODO     bool _isrunning; }; 2.2、构造析构函数  构造函数初始化成员变量,析构函数无需处理!

注意:此处需要用到两个全局静态变量! 

TcpServer(uint16_t port = gport)     :_port(port),_sockfd(gsockfd),_isrunning(false) {}   ~TcpServer() {} 2.3、InitServer() InitServer() 初始化服务端!

初始化函数主要分为三步:

1、创建socket(类型与UDP不同) 类型需要使用 SOCK_STREAM

2、bind sockfd 和 socket addr 3、获取连接(与UDP不同) 获取连接需要使用listen函数(将套接字设置为监听模式,以便能够接受进入的连接请求)

listen()

#include <sys/types.h>           #include <sys/socket.h>   int listen(int sockfd, int backlog);

参数

sockfd:这是一个已创建的套接字文件描述符,它应该是一个绑定到某个地址和端口的套接字。 backlog:这个参数定义了内核应该为相应套接字排队的最大连接数(此处暂时使用8)。如果队列已满,新的连接请求可能会被拒绝。需要注意的是,这个值只是内核用于优化性能的一个提示,实际实现可能会有所不同。 返回值

成功时,listen 函数返回 0。 失败时,返回 -1,并设置 errno 以指示错误类型。 注意:此处需要用到全局静态变量和枚举常量!

// _sockfd 版本 void InitServer() {     // 1.创建socket     _sockfd = ::socket(AF_INET,SOCK_STREAM,0);     if(_sockfd < 0)     {         LOG(FATAL,"socket create eror\\n");         exit(SOCKET_ERROR);     }     LOG(INFO,"socket create success,sockfd: %d\\n",_sockfd); // 3       struct sockaddr_in local;     memset(&local,0,sizeof(local));     local.sin_family = AF_INET;     local.sin_port = htons(_port);     local.sin_addr.s_addr = INADDR_ANY;       // 2.bind sockfd 和 socket addr     if(::bind(_sockfd,(struct sockaddr*)&local,sizeof(local)) < 0)     {         LOG(FATAL,"bind eror\\n");         exit(BIND_ERROR);     }     LOG(INFO,"bind success\\n");       // 3.因为tcp是面向连接的,tcp需要未来不短地获取连接     // 老板模式,随时等待被连接     if(::listen(_sockfd,gblcklog) < 0)     {         LOG(FATAL,"listen eror\\n");         exit(LISTEN_ERROR);     }     LOG(INFO,"listen success\\n"); }

为了测试该函数,先将Loop函数设计成死循环!

Loop()

// 测试 void Loop() {     _isrunning = true;     while(_isrunning)     {         sleep(1);     }     _isrunning = false; }

2.4、Loop() Loop() 函数一直执行服务!

执行服务函数主要分为两步:

1、获取新连接(accept函数[从已完成连接队列的头部返回下一个已完成连接,如果队列为空,则阻塞调用进程]) accept()

#include <sys/types.h>          /* See NOTES */ #include <sys/socket.h>   int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);  

参数

sockfd:这是一个监听套接字的文件描述符,它应该是一个已经通过 socket 函数创建,并通过 bind 函数绑定到特定地址和端口,以及通过 listen 函数设置为监听模式的套接字。 addr:这是一个指向 sockaddr 结构的指针,该结构用于存储接受连接的客户端的地址信息。如果不需要这个信息,可以传递 NULL。 addrlen:这是一个指向 socklen_t 类型的变量的指针,用于存储 addr 结构的大小。在调用 accept 之前,应该将该变量的值设置为 addr 结构的大小。在调用返回后,该变量将包含实际返回的地址信息的长度。如果 addr 是 NULL,则这个参数也可以是 NULL。 返回值

成功时,accept 函数返回一个新的套接字文件描述符,用于与接受的连接进行通信。这个新的套接字是原始监听套接字的子套接字,它继承了许多属性(如套接字选项),但与原始套接字是独立的。 失败时,返回 -1,并设置 errno 以指示错误类型。 因此TcpServer类的_sockfd应该改为_listensockfd!!!

TcpServer类

// 面向字节流 class TcpServer { public:     TcpServer(uint16_t port = gport):_port(port),_listensockfd(gsockfd),_isrunning(false)     {}          void InitServer()     {         // 1.创建socket         _listensockfd = ::socket(AF_INET,SOCK_STREAM,0);         if(_listensockfd < 0)         {             LOG(FATAL,"socket create eror\\n");             exit(SOCKET_ERROR);         }         LOG(INFO,"socket create success,sockfd: %d\\n",_listensockfd); // 3           struct sockaddr_in local;         memset(&local,0,sizeof(local));         local.sin_family = AF_INET;         local.sin_port = htons(_port);         local.sin_addr.s_addr = INADDR_ANY;           // 2.bind sockfd 和 socket addr         if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local)) < 0)         {             LOG(FATAL,"bind eror\\n");             exit(BIND_ERROR);         }         LOG(INFO,"bind success\\n");           // 3.因为tcp是面向连接的,tcp需要未来不短地获取连接         // 老板模式,随时等待被连接         if(::listen(_listensockfd,gblcklog) < 0)         {             LOG(FATAL,"listen eror\\n");             exit(LISTEN_ERROR);         }         LOG(INFO,"listen success\\n");     }     ~TcpServer()     {} private:     uint16_t _port;     int _listensockfd;     bool _isrunning; };

2、执行服务(前提是获取到新连接) 执行服务总共有四个版本!

2.4.1、Server 0(不靠谱版本) Server 0版本直接执行长服务!

Loop()

Loop()函数先获取新连接,获取成功则执行服务函数!

void Loop() {     _isrunning = true;     while(_isrunning)     {         struct sockaddr_in client;         socklen_t len = sizeof(client);         // 1.获取新连接         int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);         // 获取失败继续获取         if(sockfd < 0)         {             LOG(WARNING,"sccept reeor\\n");             continue;         }         InetAddr addr(client);         LOG(INFO,"get a new link,client info: %s,sockfd:%d\\n",addr.AddrStr().c_str(),sockfd); // 4                  // 获取成功         // version 0 — 不靠谱版本         Server(sockfd,addr);     }     _isrunning = false; }

Server()

注意:tcp协议可以直接使用read,write函数读写文件描述符的内容(因为tcp是面向字节流的)!

Server()执行服务,先从文件描述符中读数据,再写数据到文件描述符中!

赞(0)
未经允许不得转载:网硕互联帮助中心 » 构建TCP服务器:从基础到线程池版本的实现与测试详解
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!