目录
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()执行服务,先从文件描述符中读数据,再写数据到文件描述符中!
评论前必须登录!
注册