Linux《Socket编程Tcp》 - 指南

news/2025/12/9 18:12:39/文章来源:https://www.cnblogs.com/yangykaifa/p/19327839

在之前的篇章当中我们学习了socket编程的基本概念并且了解了Udp套接字进行通信的方式,实现了翻译字典和简单聊天室等的基于UDP套接字的客服端和服务器之间的通信。那么在了解了socket套接字UDP之后接下来就继续来实现socket套接字TCP,在此和之前学习UDP的类似也是先学习对应的接口再使用这些的接口来实现对应的实例。但是和之前不同的是在此已经有了UDP的基础之后学习TCP套接字的接口就会非常的类似。通过本篇的学习将能够使用TCP对应的接口来实现客户端和服务器之间的通信,在此我们还会了解到和之前学习的UDP的不同点,接下来就开始本篇的学习吧!!!



1. TCP套接字接口

实际上在TCP当中套接字对应的接口和之前我们学习的UDP是很类似的,只不过相比UDP,TCP是需要进行连接之后才能进行通过的,那么相比UDP就多出了建立连接的接口。

接口如下所示:
创建套接字:

// 创建套接字
int socket(int domain, int type, int protocol);
参数:domain   :协议族(常用 AF_INET 表示 IPv4)type     :套接字类型(TCP 使用 SOCK_STREAM)protocol :协议编号,通常为 0(自动选择)
返回值:成功返回套接字描述符(int),失败返回 -1。
作用:创建一个通信端点,是网络通信的起点。

bind绑定:

// 绑定本地地址和端口(服务器端使用)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:sockfd   :由 socket() 创建的套接字描述符addr     :指向包含本地 IP 和端口的 sockaddr_in 结构体addrlen  :addr 结构体的大小
返回值:成功返回 0,失败返回 -1。
作用:将套接字与指定的 IP 地址和端口号绑定。(服务器端必须绑定,客户端通常不需要显式 bind)

listen监听:

// 将套接字设置为监听状态
int listen(int sockfd, int backlog);
参数:sockfd  :绑定好地址的套接字backlog :允许的最大等待连接队列长度
返回值:成功返回 0,失败返回 -1。
作用:让当前套接字进入监听状态,准备接受客户端连接。

接收连接请求:

// 客户端:主动连接服务器
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:sockfd   :客户端套接字描述符addr     :服务器的地址结构体(IP + 端口)addrlen  :地址结构体长度
返回值:成功返回 0,失败返回 -1。
作用:发起与服务器的 TCP 连接请求。

发起连接请求:

// 服务器端:接受连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:sockfd   :监听套接字(由 listen() 创建)addr     :用于保存客户端地址信息(可为 NULL)addrlen  :保存地址长度(可为 NULL)
返回值:成功返回新的套接字描述符(用于通信)失败返回 -1。
作用:从连接队列中取出一个已完成连接,返回新的“已连接套接字”。注意:服务器通过这个新套接字与客户端通信。

数据接收和发送:

// 发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:sockfd :已连接的套接字描述符buf    :要发送的数据缓冲区len    :数据长度(字节)flags  :一般为 0(默认发送)
返回值:成功返回实际发送的字节数,失败返回 -1。
作用:通过 TCP 连接发送数据(可靠传输)。
// 接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:sockfd :已连接的套接字描述符buf    :用于接收数据的缓冲区len    :缓冲区大小flags  :一般为 0
返回值:成功返回接收到的字节数;返回 0 表示对方关闭连接;失败返回 -1。
作用:从 TCP 连接中接收数据。

除了可以使用以上的接口来实现数据的传输,实际上在TCP当中一个进程进行数据传输的时候,那么实际上就可以类似之前打开系统当中的一个文件,因此使用read和write也能实现数据的传输。

#include 
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

通过以上的讲解就可以看出TCP套接字当中的接口很多和之前UDP当中的都是一样的,只不过相比之前多了accept和connect来进行连接和获取连接。

2. 基于TCP套接字实现简单通信

以上我们了解到了TCP套接字当中对应的通信接口,那么接下来就试着使用以上的接口来实现简单的客户端和服务器之间的通信。

2.1 普通echo_server版本

在此和之前的UDP当中实现简单的通信类似还是使用Server.hpp当中实现进行通信的类,在Server.cc当中实现通信当中的服务器,在Client.cc当中实现通信的客户端。实现的代码如下所示:

TcpServer.hpp

#include 
#include "Common.hpp"
#include "InetAddr.hpp"
#include "log.hpp"
#include 
using namespace LogModule;
using func_t = std::function;
//默认等待队列最大长度
const int default_ln = 5;
class Server
{
public:Server(int port, func_t func): _port(port),_isrunning(false),_func(func){}//初始化void Init(){//创建套接字_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(LogLevel::ERROR) << "socket error";exit(ExitCode::SOCkET_ERR);}LOG(LogLevel::INFO) << "socket success!";InetAddr addr(_port);//进行bind绑定int n = bind(_listensockfd, addr.GetAddr(), addr.SockLen());if (n < 0){LOG(LogLevel::ERROR) << "bind error";exit(ExitCode::BIND_ERR);}LOG(LogLevel::INFO) << "bind success!";//进行listen监听n = listen(_listensockfd, default_ln);if (n < 0){LOG(LogLevel::ERROR) << "listen error";exit(ExitCode::LISTEN_ERR);}LOG(LogLevel::INFO) << "listen success!";}//启动服务器void Start(){_isrunning = true;char buffer[1024];while (_isrunning){sockaddr_in addr;unsigned int len = sizeof(addr);//使用accept发消息int sockfd = accept(_listensockfd, COW(addr), &len);if (sockfd < 0){LOG(LogLevel::WARNING) << "accept error";continue;}LOG(LogLevel::INFO) << "accept success";InetAddr peer(addr);while (1){//从sockfd当中获取对应的数据ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n < 0){LOG(LogLevel::ERROR) << "read error";close(sockfd);break;}else if (n == 0){LOG(LogLevel::INFO) << "clients quit";close(sockfd);break;}else{buffer[n] = 0;std::string ret = "client say:";ret += buffer;LOG(LogLevel::INFO) << peer.StringAddr() << ",Client say:" << buffer;//将数据写入到sockfd对应的文件ssize_t n = write(sockfd, ret.c_str(), ret.size());}}}// close(_listensockfd);}
private://套接字int _listensockfd;//端口号uint16_t _port;//是否运行标志位bool _isrunning;//回调函数func_t _func;
};

TcpServer.cc

#include 
#include 
#include "log.hpp"
#include "TcpServer.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include 
// 回调函数
std::string StringEcho(std::string &str)
{std::string ret = "client say:";return ret + str;
}
// server port
int main(int argc, char *argv[])
{// 判断命令行参数是否符合要求if (argc != 2){std::cout << "Server ip" << std::endl;exit(ExitCode::USAGE_ERR);}uint16_t port = std::stoi(argv[1]);// 创建对应的Server对象std::shared_ptr server = std::make_shared(port, StringEcho);server->Init();server->Start();return 0;
}

Client.cc

#include 
#include "log.hpp"
#include "InetAddr.hpp"
#include 
#include "Common.hpp"
using namespace LogModule;
int main(int argc, char *argv[])
{// 判断命令行参数是否符合要求if (argc != 3){std::cout << "Client IP Port" << std::endl;exit(ExitCode::USAGE_ERR);}uint16_t port = std::stoi(argv[2]);std::string ip = argv[1];// 创建客户端套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);// 客户端无需进行显示的绑定InetAddr addr(ip, port);// 建立于服务器的连接int n = connect(sockfd, addr.GetAddr(), addr.SockLen());if (n < 0){std::cout << "connect error" << std::endl;exit(ExitCode::CONNECT_ERR);}// 存储接收数据的缓冲区char buffer[4096];while (1){std::cout << "please input:";std::string input;std::getline(std::cin, input);if (input == "QUIT"){break;}// 发送数据到服务器int n = write(sockfd, input.c_str(), input.size());if (n < 0){std::cout << "write error" << std::endl;}// 从服务器当中得到数据n = read(sockfd, buffer, sizeof(buffer));if (n < 0){std::cout << "read error" << std::endl;exit(ExitCode::READ_ERR);}else if (n > 0){buffer[n] = 0;std::cout << "Server say:" << buffer << std::endl;}else{std::cout << "Server quit" << std::endl;exit(ExitCode::READ_ERR);}}close(sockfd);return 0;
}

将以上的代码编写之后,接下来就再将对应的makefile文件进行编写,以实现自动化编译

.PHONY:all
all:Server Client
Server:TcpServer.ccg++  -o $@ $^ -std=c++17
Client:TcpClient.ccg++ -o $@ $^ -std=c++17
.PHONY:clean
clean:rm -f Server Client

实现之后将以上的代码进行殡编译之后运行查看是否符合我们的要求,运行的结果如下所示:

通过的输出结果就可以看出客户端和服务器是能建立对应的连接的,并且在建立连接之后服务器是会阻塞的等待用户的输入,当用户输入之后会将对应的数据传输给服务器,服务器当中得到数据之后进行对应的处理后将数据再发送给客户端。

2.2 多进程echo_server版本

以上我们已经实现了基本的客户端和服务器之间的通信,但是实际上当前还是存在一定的问题的,就是当前是只能是同时进行一个进程的通信,这时就会造成当多个进程和服务器进行通信的时候会让第一个进程之后的进程再用户输入数据之后在发送的阶段会一直阻塞。

要解决以上的问题就需要让服务器当中进行数据接收和发送的不能是再由一个进程来实现,而是要让一个客户端和服务器之间的通信就由一个进程来实现。因此基于以上的分析就需要将原来我们实现的客户端修改为多进程的版本。

实现的代码如下所示:

TcpServer.hpp

#include 
#include "Common.hpp"
#include "InetAddr.hpp"
#include "log.hpp"
#include 
#include 
#include 
using namespace LogModule;
using func_t = std::function;
// 默认等待队列最大长度
const int default_ln = 5;
class Server
{
public:Server(int port, func_t func): _port(port),_isrunning(false),_func(func){}// 初始化void Init(){// 创建套接字_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(LogLevel::ERROR) << "socket error";exit(ExitCode::SOCkET_ERR);}LOG(LogLevel::INFO) << "socket success!";InetAddr addr(_port);// 进行bind绑定int n = bind(_listensockfd, addr.GetAddr(), addr.SockLen());if (n < 0){LOG(LogLevel::ERROR) << "bind error";exit(ExitCode::BIND_ERR);}LOG(LogLevel::INFO) << "bind success!";// 进行listen监听n = listen(_listensockfd, default_ln);if (n < 0){LOG(LogLevel::ERROR) << "listen error";exit(ExitCode::LISTEN_ERR);}LOG(LogLevel::INFO) << "listen success!";}void Service(int sockfd, InetAddr &peer){char buffer[4096];while (1){// 从sockfd当中获取对应的数据ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n < 0){LOG(LogLevel::ERROR) << "read error";close(sockfd);break;}else if (n == 0){LOG(LogLevel::INFO) << "clients quit";close(sockfd);break;}else{buffer[n] = 0;std::string ret = "client say:";ret += buffer;LOG(LogLevel::INFO) << peer.StringAddr() << ",Client say:" << buffer;// 将数据写入到sockfd对应的文件ssize_t n = write(sockfd, ret.c_str(), ret.size());}}}// 启动服务器void Start(){_isrunning = true;char buffer[1024];while (_isrunning){sockaddr_in addr;unsigned int len = sizeof(addr);// 使用accept发消息int sockfd = accept(_listensockfd, COW(addr), &len);if (sockfd < 0){LOG(LogLevel::WARNING) << "accept error";continue;}LOG(LogLevel::INFO) << "accept success";InetAddr peer(addr);// 通过创建子进程的方式来实现数据的收发pid_t fd = fork();if (fd < 0){LOG(LogLevel::ERROR) << "fork error";exit(ExitCode::FORK_ERR);}else if (fd == 0){// 在子进程当中关闭对应的listensockfd,并且再创建对应的子进程之后将当前的进程退出// 这时就不再需要原来的父进程对该进程进行等待的操作close(_listensockfd);if (fork() > 0){exit(ExitCode::OK);}// 进行数据的收发Service(sockfd, peer);exit(ExitCode::OK);}else{// 父进程当中关闭当前的sockfdclose(sockfd);int ret = waitpid(fd, nullptr, 0);(void)ret;}// Service(sockfd, peer);}// close(_listensockfd);}
private:// 套接字int _listensockfd;// 端口号uint16_t _port;// 是否运行标志位bool _isrunning;// 回调函数func_t _func;
};

其余的代码和其他的实现还是一样的,那么这时候只需要将以上的TcpServer.hpp修改为以上的多进程形式,这时就可以实现多客户端同时对服务器的访问。

以上实现的的大致原理就是进行对应的accept操作之后接下来再使用fork创建出子进程之后,在子进程当中再创建出对应的孙子进程,在此实现成这样的原因是该原来再父进程当中是需要进行阻塞式等待,那么这时父进程还是会出现阻塞的情况,因此创建出孙子进程之后将子进程退出之后,这时孙子进程就会被一号进程领养,那么这时父进程就不再会被阻塞。这时候就能让不同的客户端给同时的访问服务器。

重新编译以上的代码,在不同的客户端下运行如下所示:
注:在此要在可执行程序传输到其他的客服端下,那么这时就需要将程序采取静态编译的方式。

通过以上的输出就可以看出当前确实能实现对应的多客户端同时的运行。

2.3 多线程echo_server版本

以上我们基于多进程的方式来实现对应的多客户端的方式,那么接下来试着来使用多线程的方式实现,实际上实现当中其他部分的代码都不需要进行修改只需要在Stat函数当中将对应的使用accept创建连接之后的通过子进程来实现服务的方式修改为通过线程来实现。

#include 
#include "Common.hpp"
#include "InetAddr.hpp"
#include "log.hpp"
#include 
#include 
#include
using namespace LogModule;
using func_t = std::function;
// 默认等待队列最大长度
const int default_ln = 5;
class Server
{
public://…………void Service(int sockfd, InetAddr &peer){char buffer[4096];while (1){// 从sockfd当中获取对应的数据ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n < 0){LOG(LogLevel::ERROR) << "read error";close(sockfd);break;}else if (n == 0){LOG(LogLevel::INFO) << "clients quit";close(sockfd);break;}else{buffer[n] = 0;std::string ret = "client say:";ret += buffer;LOG(LogLevel::INFO) << peer.StringAddr() << ",Client say:" << buffer;// 将数据写入到sockfd对应的文件ssize_t n = write(sockfd, ret.c_str(), ret.size());}}}//线程执行函数,为实现参数的匹配需要设置为静态成员函数static void* Routine(void* args){pthread_detach(pthread_self());ThreadData* td=static_cast(args);td->_resv->Service(td->_sockfd,td->_addr);delete td;return nullptr;}// 启动服务器void Start(){_isrunning = true;char buffer[1024];while (_isrunning){sockaddr_in addr;unsigned int len = sizeof(addr);// 使用accept发消息int sockfd = accept(_listensockfd, COW(addr), &len);if (sockfd < 0){LOG(LogLevel::WARNING) << "accept error";continue;}LOG(LogLevel::INFO) << "accept success";InetAddr peer(addr);//V2多线程版本//创建存储服务器信息的结构体ThreadData* data=new ThreadData(sockfd,peer,this);pthread_t p;//创建线程pthread_create(&p,nullptr,Routine,(void*)data);//Service(sockfd, peer);}// close(_listensockfd);}
private:// 套接字int _listensockfd;// 端口号uint16_t _port;// 是否运行标志位bool _isrunning;// 回调函数func_t _func;
};

以上就是实现的对应代码,在此我们是通过pthread库当中提供的接口来实现,而未使用我们自己创建的线程类来调用,这是为了能更好的向你展示出整体的调用过程是如何实现的。

首先创建应该名为ThreadData的结构体在该结构体的内部成员变量有对应的套接字、InerAddr对象、以及Server指针。通过pthread_ctreate创建出线程之后接下来将创建出的ThreadData对象作为参数。通过在线程的执行函数Routine内调用对应的数据处理函数Service。

在此引入一个工具netstat来查看网络状态。在此使用该工具的时候有以下的选项

• n 拒绝显示别名, 能显示数字的全部转化成数字
• l 仅列出有在 Listen (监听) 的服務状态
• p 显示建立相关链接的程序名
• t (tcp)仅显示 tcp 相关选项

在此将Server和Client运行起来之后接下来使用netstat来将系统当中的有有端口号信息为8080的进程

通过以上的输出结果就可以看出在当前的主机当中分别存在端口号一个发送端和一个接收方为8080的进程,这是因为当中我们是将客户端和服务器是在同一个主机当中运行的。并且通过以上也能验证在TCP连接中是全双工的,能实现同时的接和发。

2.4 线程池echo_server版本

以上在实现了多进程和多线程的版本的echo_server,那么接下来再来实现线程池版本的。实现的代码如下所示:

#include 
#include "Common.hpp"
#include "InetAddr.hpp"
#include "log.hpp"
#include 
#include 
#include 
#include "ThreadPool.hpp"
using namespace ThreadPoolModule;
using namespace LogModule;
using func_t = std::function;
// 默认等待队列最大长度
const int default_ln = 5;
class Server
{
public:Server(int port): _port(port),_isrunning(false){}// 初始化void Init(){// 创建套接字_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(LogLevel::ERROR) << "socket error";exit(ExitCode::SOCkET_ERR);}LOG(LogLevel::INFO) << "socket success!";InetAddr addr(_port);// 进行bind绑定int n = bind(_listensockfd, addr.GetAddr(), addr.SockLen());if (n < 0){LOG(LogLevel::ERROR) << "bind error";exit(ExitCode::BIND_ERR);}LOG(LogLevel::INFO) << "bind success!";// 进行listen监听n = listen(_listensockfd, default_ln);if (n < 0){LOG(LogLevel::ERROR) << "listen error";exit(ExitCode::LISTEN_ERR);}LOG(LogLevel::INFO) << "listen success!";}void Service(int sockfd, InetAddr &peer){char buffer[4096];while (1){// 从sockfd当中获取对应的数据ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n < 0){LOG(LogLevel::ERROR) << "read error";close(sockfd);break;}else if (n == 0){LOG(LogLevel::INFO) << "clients quit";close(sockfd);break;}else{buffer[n] = 0;std::string ret = "client say:";std::string str = buffer;//std::string ret = _func(str, peer);ret += buffer;LOG(LogLevel::INFO) << peer.StringAddr() << ",Client say:" << buffer;// 将数据写入到sockfd对应的文件ssize_t n = write(sockfd, ret.c_str(), ret.size());}}}class ThreadData{public:ThreadData(int sockfd, InetAddr &addr, Server *resv): _sockfd(sockfd),_addr(addr),_resv(resv){}int _sockfd;InetAddr _addr;Server *_resv;};// 线程执行函数,为实现参数的匹配需要设置为静态成员函数static void *Routine(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast(args);td->_resv->Service(td->_sockfd, td->_addr);delete td;return nullptr;}// 启动服务器void Start(){_isrunning = true;char buffer[1024];while (_isrunning){sockaddr_in addr;unsigned int len = sizeof(addr);// 使用accept发消息int sockfd = accept(_listensockfd, COW(addr), &len);if (sockfd < 0){LOG(LogLevel::WARNING) << "accept error";continue;}LOG(LogLevel::INFO) << "accept success";InetAddr peer(addr);// V3线程池版本ThreadPool::GwetInstance()->Enqueue([this,sockfd,&peer](){this->Service(sockfd,peer);});}// close(_listensockfd);}
private:// 套接字int _listensockfd;// 端口号uint16_t _port;// 是否运行标志位bool _isrunning;// 回调函数func_t _func;
};

以上的代码就通过引入之前我们实现的线程池之后将每个客户端和服务器建立连接之后,接下来将对应的任务插入到线程池当中的任务队列当中,那么这时线程池就会自动的在内部进行任务的调度,在此就实现服务器和客户端之间连接和数据处理的解耦,不同的功能就在不同对的模块当中实现。

编译以上的代码,运行的结果如下所示:


2.4 实时翻译字典

以上我在实现了简单的echo_server的代码,那么接下来就继续来试着实现基于TCP套接字的实时翻译的字典,在之前学习UDP套接字当中我们已经实现对应翻译模块的代码,那么在此就只需要将Server和Client端的代码进行修改即可,并且将之前实现的Dict.hpp代码拷贝到当前的目录当中。

在此我们需要将Server.hpp当中的代码修改为以下的形式:

#include 
#include "Common.hpp"
#include "InetAddr.hpp"
#include "log.hpp"
#include 
#include 
#include 
#include "ThreadPool.hpp"
using namespace ThreadPoolModule;
using namespace LogModule;
//具体翻译功能的回调函数
using task_t = std::function;
//线程池任务队列当中插入的任务
using func_t = std::function;
// 默认等待队列最大长度
const int default_ln = 5;
class Server
{public:Server(int port, task_t task): _port(port),_isrunning(false),_task(task){}// 初始化void Init(){// 创建套接字_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(LogLevel::ERROR) << "socket error";exit(ExitCode::SOCkET_ERR);}LOG(LogLevel::INFO) << "socket success!";InetAddr addr(_port);// 进行bind绑定int n = bind(_listensockfd, addr.GetAddr(), addr.SockLen());if (n < 0){LOG(LogLevel::ERROR) << "bind error";exit(ExitCode::BIND_ERR);}LOG(LogLevel::INFO) << "bind success!";// 进行listen监听n = listen(_listensockfd, default_ln);if (n < 0){LOG(LogLevel::ERROR) << "listen error";exit(ExitCode::LISTEN_ERR);}LOG(LogLevel::INFO) << "listen success!";}void Service(int sockfd, InetAddr &peer){char buffer[4096];while (1){// 从sockfd当中获取对应的数据ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n < 0){LOG(LogLevel::ERROR) << "read error";close(sockfd);break;}else if (n == 0){LOG(LogLevel::INFO) << "clients quit";close(sockfd);break;}else{buffer[n] = 0;std::string str = buffer;std::string ret = _task(str, peer);if(ret!="None")ret += buffer;LOG(LogLevel::INFO) << peer.StringAddr() << ",Client say:" << buffer;// 将数据写入到sockfd对应的文件ssize_t n = write(sockfd, ret.c_str(), ret.size());}}}class ThreadData{public:ThreadData(int sockfd, InetAddr &addr, Server *resv): _sockfd(sockfd),_addr(addr),_resv(resv){}int _sockfd;InetAddr _addr;Server *_resv;};// 线程执行函数,为实现参数的匹配需要设置为静态成员函数static void *Routine(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast(args);td->_resv->Service(td->_sockfd, td->_addr);delete td;return nullptr;}// 启动服务器void Start(){_isrunning = true;char buffer[1024];while (_isrunning){sockaddr_in addr;unsigned int len = sizeof(addr);// 使用accept发消息int sockfd = accept(_listensockfd, COW(addr), &len);if (sockfd < 0){LOG(LogLevel::WARNING) << "accept error";continue;}LOG(LogLevel::INFO) << "accept success";InetAddr peer(addr);// V3线程池版本ThreadPool::GwetInstance()->Enqueue([this,sockfd,&peer](){this->Service(sockfd,peer);});}// close(_listensockfd);}
private:// 套接字int _listensockfd;// 端口号uint16_t _port;// 是否运行标志位bool _isrunning;// 回调函数task_t _task;
};


将Server.cc修改以下的形式:

#include 
#include 
#include "log.hpp"
#include "TcpServer.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include 
#include "Dict.hpp"
// 回调函数
std::string StringEcho(std::string &str)
{std::string ret = "client say:";return ret + str;
}
// server port
int main(int argc, char *argv[])
{// 判断命令行参数是否符合要求if (argc != 2){std::cout << "Server ip" << std::endl;exit(ExitCode::USAGE_ERR);}uint16_t port = std::stoi(argv[1]);// 创建对应的Dict对象Dict dict;// 将dictionary.txt字典文件当中的内容加载dict.LoadDict();// 创建对应的Server对象std::shared_ptr server = std::make_shared(port, [&dict](std::string &words, InetAddr &peer){ return dict.Translate(words, peer); });// std::shared_ptr server=std::make_shared(port);server->Init();server->Start();return 0;
}

以上代码实现的大体逻辑就是在Server.cc当中创建出对应得到Dict对象之后接下来将对象当中的成员函数Translate函数通过回调的方式作为参数传给创建出的Server对象,接下来在该Server对象当中调用对应Init函数喝Start函数,那么在Stat函数就会创建出一个线程池,这时将Service作为线程池要执行的任务,在该任务当中集体又进行客户端喝服务器之间的数据传输,并且在得到客户端的数据之后将该数据通过回调函数_task进行翻译的功能。

编译代码运行的结构如下所示:

2.5 远程命令执行

以上我们实现了不同版本的echo_server和实时翻译字典,那么接下来再来基于对应的TCP套接字实现一个远程命令执行的功能。在此要实现的是用户再远程的客户端当中输入对应的命令之后,服务器会通过网络得到对应的指令之后在服务器当前的路径下执行对应的命令,并且将执行的结果通过网络返回给对服务器。

在此就在对应的Command.hpp当中实现对应的命令操作,在该文件当中实现对应的Command类在该类当中再实现Excte函数,在服务器当中通过得到用户输入的命令之后再服务器当中将该命令在服务器当前的路径下执行,并且将执行的结果在该Excute当中将数据发送给指定的客户端。实现的代码如下所示:

#pragma once
#include 
#include "log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
class command
{
public:command(){}~command(){}//命令执行结果发送功能函数std::string Excute(const std::string &cmd, InetAddr &addr){std::string who = addr.StringAddr();//使用open得到对应命令的执行结果储存在fd文件描述符指向的管道当中FILE *fp = popen(cmd.c_str(), "r");if (fp == nullptr){return std::string("你要执行的命令不存在") + cmd;}std::string res;char line[1024];//读取管道当中的数据,通过数据创建出返回的字符串reswhile (fgets(line, sizeof(line), fp)){res += line;}std::string result = who + "excutedone,result is:\n" + res;LOG(LogLevel::DEBUG) << result;return result;}
};

以上使用到了一个C库当中提供的函数popen,其实现的功能是将用户传入的参数当中的指令进行执行,并且将执行的结果通过对应的管道返回。

#include 
FILE *popen(const char *command, const char *type);
参数:command:要执行的 shell 命令字符串,比如 "ls -l" 或 "grep something file.txt"。type:打开管道的方向:"r":从命令的标准输出读取(子进程 → 父进程)"w":向命令的标准输入写入(父进程 → 子进程)
返回值:是一个类似文件的指针(FILE*),你可以用 fgets、fputs 等函数来读写

使用以上的函数就不再需要我们使用之前传统的方式创建子进程只会再将子进程执行的结果写入到对应的文件当中。实际上在以上实现远程命令还可以创建对应的白名单,因为实际的远程执行的用户可能不是你自己,那么这时正在执行对的用户可能会执行一些非法的操作,这时就会造成了数据的丢失等问题。

那么为了解决以上的问题就可以在在Command类当中创建一个unordered_set的成员变量,那么在构造函数当中将远程可以执行的指令写入到插入到对应的unordered_set当中,实现的代码如下所示:

#pragma once
#include 
#include "log.hpp"
#include "InetAddr.hpp"
#include 
using namespace LogModule;
class command
{
public:command(){st.insert("pwd");st.insert("ls");st.insert("whoaimi");st.insert("ls -l");st.insert("touch test.txt");st.insert("cd ..");st.insert("cd -");}~command(){}bool IsSafe(const std::string str){auto it = st.find(str);return it == nullptr ? false : true;}// 命令执行结果发送功能函数std::string Excute(const std::string &cmd, InetAddr &addr){if (!IsSafe(cmd)){return "Nono";}std::string who = addr.StringAddr();// 使用open得到对应命令的执行结果储存在fd文件描述符指向的管道当中FILE *fp = popen(cmd.c_str(), "r");if (fp == nullptr){return std::string("你要执行的命令不存在") + cmd;}std::string res;char line[1024];// 读取管道当中的数据,通过数据创建出返回的字符串reswhile (fgets(line, sizeof(line), fp)){res += line;}std::string result = who + "excutedone,result is:\n" + res;LOG(LogLevel::DEBUG) << result;return result;}
private:std::unordered_set st;
};

以上将Command.hpp的代码实现之后,那么接下来就可以就可以在Server.cc文件当中创建出Comman对象之后再将创建出的Server对象的第二次参数传对应的lambda表达式。

#include 
#include 
#include "log.hpp"
#include "TcpServer.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include 
#include"Dict.hpp"
#include"Command.hpp"
// 回调函数
std::string StringEcho(std::string &str)
{std::string ret = "client say:";return ret + str;
}
// server port
int main(int argc, char *argv[])
{// 判断命令行参数是否符合要求if (argc != 2){std::cout << "Server ip" << std::endl;exit(ExitCode::USAGE_ERR);}uint16_t port = std::stoi(argv[1]);//创建对应的Dict对象//  Dict dict;//  //将dictionary.txt字典文件当中的内容加载//  dict.LoadDict();//创建Command对象command cm;std::shared_ptr server = std::make_shared(port, [&cm](std::string& words,InetAddr& peer){return cm.Excute(words,peer);});//std::shared_ptr server=std::make_shared(port);server->Init();server->Start();return 0;
}

编译以上的程序执行查看是否能满足我们的要求:

通过以上的输出结果就可以看出当前实现的远程命令行是符合我们的要求的。

以上就是本篇的全部内容了,接下来我们将继续来学习序列化和反序列化以及自定义协议相关的概念,通过下一篇的学习我们将对TCP有更深刻的理解欸,未完待续……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/995332.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年中国珍珠奶茶加盟TOP10一线品牌榜

2025年珍珠奶茶行业步入结构化竞争新阶段,全球市场规模达 24.8 亿美元,国内现制茶饮市场逼近 3000亿元。消费端健康化诉求凸显,低糖、植物基、高蛋白产品成为主流,Z世代主导定制化消费与社交传播。技术层面,AI 配…

大学生必备APP精选:助力学业与生活的实用工具

大学生必备APP精选:助力学业与生活的实用工具工欲善其事,必先利其器。一款得心应手的APP,能让大学生活事半功倍。 在当今数字化校园中,选择合适的应用程序能极大提升学习效率和生活便利性。本文将为你介绍几款在语…

什么是 Spring AOP - Higurashi

AOP(面向切面编程)是 Spring 两大核心之一,它是一种编程思想,是对 OOP 的一种补充。它通过横向抽取共性功能(如日志、事务),解决代码重复和耦合问题,提升代码复用性和可维护性。它的底层是通过动态代理实现的。…

2025最新油田助剂厂家推荐榜:实力企业赋能油气开发,全国优质供应商精选

在油气勘探开发的钻井、采油、压裂等关键环节,油田助剂的性能直接关系到作业效率、采收率与作业安全。选择技术成熟、供应稳定、服务完善的厂家,是油田企业实现降本增效的重要保障。以下结合企业综合实力、产品适配性…

如何在Flutter中使用CustomPainter实现自定义绘制?

在 Flutter 中,CustomPainter是实现自定义绘制的核心组件,可灵活绘制图形、路径、文本、渐变甚至复杂动效,其核心逻辑是通过重写paint()(定义绘制逻辑)和shouldRepaint()(控制重绘时机)来实现自定义视觉效果。以…

Linux 中文本显示字体以颜色突出

Linux 中文本显示字体以颜色突出 001、绿色002、红色

博弈论模型中的学习与算法设计

本文探讨了博弈论模型中的学习问题,特别是算法博弈论的应用。文章分析了在重复博弈中,参与者如何通过学习最大化自身奖励,以及如何设计游戏结构以同时优化个人与集体利益,并研究了存在遗留效应环境下的学习算法。v…

《Zephyr RTOS 深度学习指南与生成式AI结合方法探讨》第六章 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025 年 12 月上海逃生装备厂家权威推荐榜:聚焦逃生滑道、缓降管、应急器材,解析智能与柔性技术的安全守护之选

2025 年 12 月上海逃生装备厂家权威推荐榜:聚焦逃生滑道、缓降管、应急器材,解析智能与柔性技术的安全守护之选 随着城市化进程的加速,高层及超高层建筑日益增多,火灾等突发性公共安全事件的应急逃生需求变得前所未…

HiAgent vs Coze:企业级智能体平台大对比

HiAgent vs Coze:企业级智能体平台大对比Posted on 2025-12-09 18:00 Java后端的Ai之路 阅读(0) 评论(0) 收藏 举报HiAgent vs Coze:企业级智能体平台的深度对比 专业术语解释 HiAgent HiAgent是字节跳动火山引…

关于敏感信息检测技术的理论知识

在之前的文章中,探索了不同的检测敏感信息的方法,并通过Demo进行了学习,对算法、模型等一些概念有一些初步认知,这片文章想更加完整的学习涉及的概念,以及知识框架。 信息识别 “敏感信息检测”本质上是一种信息识…

自定义拦截器不生效问题记录

新项目里面我把之前的告警添加了进来,添加后发现有个问题:我新增的拦截器一直不生效:我的代码如下Configuration public class OraDingdingConfigurer implements WebMvcConfigurer, Interceptor {/*** 拦截器参数校…

2025年地毯品牌最新推荐榜,聚焦企业技术创新、原料品质与市场口碑深度解析羊毛,无胶,可拆洗双层,客厅,卧室,中古风,儿童房,可拆洗,床边,无胶防水地毯公司推荐

引言 随着家居消费升级,健康环保与设计美学成为地毯选购核心诉求,为精准筛选优质品牌,本次推荐榜依托中国家用纺织品行业协会(CNTAC)2024-2025 年度地毯品类测评数据,结合第三方检测机构 SGS 的 128 项指标检测结…

中美跨境国际快递配送清单:轻小件低价寄,带电_特货合规清关

2025 年中美跨境电商轻小件需求同比增长 45%,饰品、3C 配件等 0.5-10KG 包裹占比超 60%,但 “低价难寻合规渠道、带电特货清关险、轨迹追踪不透明” 仍是核心痛点。第三方数据显示,48% 卖家曾因 “敏感货扣关” 损失…

Elasticsearch:如何为 Elastic Stack 部署 E5 模型 - 下载及隔离环境 - 详解

Elasticsearch:如何为 Elastic Stack 部署 E5 模型 - 下载及隔离环境 - 详解2025-12-09 17:51 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-…

JVM运营内存清空查看

ps -ef | grep "java" 找到定开服务PID 然后 jmap -heap PID 可以看到对应jvm 内存分配情况

Flutter 应该如何实现 iOS 26 的 Liquid Glass

要在 Flutter 中实现 iOS 26 的Liquid Glass(液态玻璃) 视觉交互效果,需先明确 Liquid Glass 的核心特征:iOS 26 推出的液态玻璃质感聚焦「动态流体形变、玻璃拟态(Glassmorphism)进阶版、触控反馈的液态柔化、层…

IIS反向代理

模块安装 首先安装代理需要的模块,Application Request Routing Cache和URL重写(URL Rewrite)两个模块 下载地址: Application Request Routing Cache URL重写(URL Rewrite) 注:Application Request Routing …

102302122许志安作业4

数据采集第四次作业 作业一:基于 Selenium + MySQL 的沪深 A 股股票数据爬取 要求: ▪ 熟练掌握 Selenium 查找HTML元素、爬取Ajax网页数据、等待HTML元素等内 容。 ▪ 使用Selenium框架+ MySQL数据库存储技术路线爬…