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

C++项目:基于Linux系统和TCP协议的远程命令行终端

        前面我们基于TCP实现了简单了互动服务器。但是那是远远不够的,本期我们将实现一个基于TCP的远程命令行终端

        相关代码提交至作者的个人gitee:楼田莉子/Linux学习喜欢请点个赞谢谢

目录

日志

线程池相关

网络客户端

命令

服务器

        服务器代码

        服务器运行

        客户端运行

        测试


日志

#pragma once

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string>
#include <memory>
#include <sstream>
#include <chrono>
#include <iomanip>
#include <ctime>
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
//RAII风格代码:资源获取即初始化
// 互斥锁类,封装了pthread_mutex_t
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
pthread_mutex_t *Ptr()
{
return &_lock;
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
~Mutex()
{
pthread_mutex_destroy(&_lock);
}
private:
pthread_mutex_t _lock;
};

class LockGuard // RAII风格代码
{
public:
LockGuard(Mutex &lock):_lockref(lock)
{
_lockref.Lock();
}
~LockGuard()
{
_lockref.Unlock();
}
private:
Mutex &_lockref;
};

//日志本体
namespace Logger
{
enum class LogLevel
{
INFO, ///< 信息性消息,记录应用程序的正常运行状态(如启动、配置加载等)
WARNING, ///< 警告,表示潜在的问题或非预期的情形,但应用程序仍能继续运行
ERROR, ///< 错误,表示发生了严重的操作失败,但不影响整个应用程序的继续运行
FATAL, ///< 致命错误,表示严重的故障,通常会导致应用程序终止
DEBUG ///< 调试信息,用于开发和排错阶段,记录详细的内部状态或流程
};
std::string LogLevelToString(LogLevel level)
{
switch (level)
{
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
case LogLevel::DEBUG:
return "DEBUG";
default:
return "UNKNOWN";
}
}
std::string GetCurrentDateTime()
{
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);

std::tm tm_info; // 用户提供的缓冲区
localtime_r(&now_c, &tm_info); // POSIX 线程安全函数

std::ostringstream oss;
oss << std::put_time(&tm_info, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
// 输出角度 — 刷新策略
// 1. 显示器打印
// 2. 文件写入

// 日志的生成:
// 1. 构建日志字符串
// 2. 根据不同的策略,进行刷新

//策略模式接口
class LogStrategy
{
public:
virtual void LogRefresh(const std::string &message) = 0;
virtual ~LogStrategy() = default;
};
// 控制台日志刷新策略, 日志将来要向显示器打印
class ConsoleStrategy : public LogStrategy
{
public:
// 显示器打印策略刷新
void LogRefresh(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cerr << message << std::endl; // ??
}
~ConsoleStrategy()
{
}

private:
Mutex _mutex;
};

const std::string defaultpath = "./log";
const std::string defaultfilename = "log.txt";
namespace fs = std::filesystem;

// 文件策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &name = defaultfilename)
: _logpath(path),
_logfilename(name)
{
if (std::filesystem::exists(_logpath))
return;

try
{
std::filesystem::create_directories(_logpath);
}
catch(const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\\n';
}
if(!_logpath.empty()&&_logpath.back() != '/')
_logpath += '/';
}
// 文件策略刷新
void LogRefresh(const std::string &message) override
{
{
std::string targetlogfile = _logpath + _logfilename;
LockGuard lockguard(_mutex);
std::ofstream logFile(targetlogfile, std::ios::app);// 以追加模式打开文件
if (!logFile.is_open())
{
std::cerr << "无法打开日志文件: " << targetlogfile << std::endl;
return;
}
logFile << message << "\\n";
logFile.close();
}

}

~FileLogStrategy()
{
}

private:
std::string _logpath;
std::string _logfilename;
Mutex _mutex;
};

// 根据日志等级分类保存的策略类
class LevelFileLogStrategy : public LogStrategy {
public:
/**
* @brief 构造函数,指定日志根目录
* @param log_dir 存放等级日志文件的目录,默认为 "./logs"
*/
LevelFileLogStrategy(const std::string &log_dir = "./logs") : _log_dir(log_dir) {
// 确保目录存在
if (!std::filesystem::exists(_log_dir)) {
std::filesystem::create_directories(_log_dir);
}
// 规范化目录路径,末尾添加 '/'
if (!_log_dir.empty() && _log_dir.back() != '/') {
_log_dir += '/';
}
}

/**
* @brief 刷新日志:根据等级写入对应文件
* @param message 完整的日志消息(格式由 Logger::LogMessage 生成)
*/
void LogRefresh(const std::string &message) override {
// 1. 从消息中提取日志等级
LogLevel level = extractLogLevel(message);
// 2. 构造对应的文件名(例如:INFO.log)
std::string filename = _log_dir + LogLevelToString(level) + ".log";

// 3. 线程安全地追加写入文件
LockGuard lockguard(_mutex);
std::ofstream logFile(filename, std::ios::app);
if (!logFile.is_open()) {
std::cerr << "无法打开日志文件: " << filename << std::endl;
return;
}
logFile << message << "\\n";
logFile.close();
}

private:
std::string _log_dir; // 日志根目录
Mutex _mutex; // 文件写入互斥锁

/**
* @brief 从日志消息中解析出等级
* @param msg 完整日志消息,格式为 "[时间][等级][PID][文件:行号] 用户内容"
* @return 对应的 LogLevel 枚举值,解析失败时默认返回 INFO
*/
LogLevel extractLogLevel(const std::string &msg) {
// 寻找第一个 ']' 的位置
size_t first_close = msg.find(']');
if (first_close == std::string::npos) {
return LogLevel::INFO; // 格式错误,默认 INFO
}
// 寻找第二个 '[' 的位置
size_t second_open = msg.find('[', first_close);
if (second_open == std::string::npos) {
return LogLevel::INFO;
}
// 寻找第二个 ']' 的位置
size_t second_close = msg.find(']', second_open);
if (second_close == std::string::npos) {
return LogLevel::INFO;
}
// 提取等级字符串(如 "INFO")
std::string level_str = msg.substr(second_open + 1, second_close – second_open – 1);

// 映射到 LogLevel 枚举
if (level_str == "INFO") return LogLevel::INFO;
if (level_str == "WARNING") return LogLevel::WARNING;
if (level_str == "ERROR") return LogLevel::ERROR;
if (level_str == "FATAL") return LogLevel::FATAL;
if (level_str == "DEBUG") return LogLevel::DEBUG;
return LogLevel::INFO; // 未知等级,默认 INFO
}
};
class Logger
{
public:
Logger()
{}
void UseConsoleStrategy()
{
_strategy = std::make_unique<ConsoleStrategy>();
}
void UseFileStrategy()
{
_strategy = std::make_unique<FileLogStrategy>();
}
void UseLevelFileStrategy(const std::string &log_dir = "./logs")
{
_strategy = std::make_unique<LevelFileLogStrategy>(log_dir);
}
void Debug(const std::string &message)
{
if (_strategy!= nullptr)
{
_strategy->LogRefresh("[DEBUG] " + GetCurrentDateTime() + " – " + message);
}
}
//日志内容
//一条完整的日志信息=> [日志级别] + 当前时间 + 进程ID + 文件名 + 行号 + 日志信息
//我们想以RAII形式刷新日志信息
class LogMessage
{
public:
LogMessage(LogLevel level, const std::string &filename, size_t line, Logger &logger)
: _level(level), _filename(filename), _line(line),_logger(logger)
{
_cur_time = GetCurrentDateTime();
_pid=getpid();
// 构建日志左半部分信息
std::stringstream oss;
oss <<"["<<_cur_time<<"]"
<<"["<<LogLevelToString(_level)<<"]"
<<"["<<_pid<<"]"
<<"[" << filename << ":"
<< line << "]";
_LogInfo = oss.str();
}
template<typename T>
LogMessage& operator<<(const T&info)
{
std::stringstream oss;
oss<< info;
_LogInfo += oss.str();
return *this;
}
~LogMessage()
{
if (_logger._strategy != nullptr)
{
_logger._strategy->LogRefresh(_LogInfo);
}
}
private:
std::string _cur_time;
LogLevel _level;
pid_t _pid;
std::string _filename;
size_t _line;
std::string _LogInfo;
Logger &_logger ;//方便进行后续策略方式刷新
};
//对LogMessage进行()重载
//必须用拷贝,否则会导致<<重载的时候内容消失
LogMessage operator()(LogLevel level, const std::string filename, size_t line)
{
return LogMessage(level, filename, line, *this);
}
~Logger()
{}
private:
std::unique_ptr<LogStrategy> _strategy;
};
//日志对象全局使用
Logger logger;
#define ENABLE_LOG_CONSOLE() logger.UseConsoleStrategy()
#define ENABLE_LOG_FILE() logger.UseFileStrategy()
#define Log(level) logger(level, __FILE__, __LINE__)
#define ENABLE_LOG_LEVEL_FILE(log_dir) logger.UseLevelFileStrategy(log_dir)
}

线程池相关

        Mutex.hpp

#pragma once

#include <iostream>
#include <string>
#include <pthread.h>
#include "../Log.h"
namespace LockModule
{
// 对锁进行封装,可以独立使用
class Mutex
{
public:
// 删除不需要的拷贝和赋值
Mutex(const Mutex &) = delete;
const Mutex &operator=(const Mutex &) = delete;

// 构造函数
Mutex()
{
int n = pthread_mutex_init(&_mutex, nullptr);
(void)n;
}

// 加锁
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}

// 解锁
void Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}

// 获取原始指针
pthread_mutex_t *GetMutexOriginal()
{
return &_mutex;
}

// 析构函数
~Mutex()
{
int n = pthread_mutex_destroy(&_mutex);
(void)n;
}

private:
pthread_mutex_t _mutex;
};

// 采用RAII风格,进行锁管理
class LockGuard
{
public:
// 构造函数,创建时自动加锁
LockGuard(Mutex &mutex) : _mutex(mutex)
{
_mutex.Lock();
}

// 析构函数,自动解锁
~LockGuard()
{
_mutex.Unlock();
}

private:
Mutex &_mutex; // 引用被管理的互斥锁
};
}

        Cond.hpp

#pragma once

#include <iostream>
#include <string>
#include <pthread.h>
#include "Mutex.hpp"

namespace CondModule
{
class Cond
{
public:
Cond()
{
pthread_cond_init(&_cond, nullptr);
}
void Wait(LockModule::Mutex &mutex)
{
int n = pthread_cond_wait(&_cond, mutex.GetMutexOriginal());
(void)n;
}
void Signal()
{
int n = pthread_cond_signal(&_cond);
(void)n;
}
void Broadcast()
{
int n = pthread_cond_broadcast(&_cond);
(void)n;
}
~Cond()
{
pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond;
};
}

        Thread.cpp

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include "../Log.h"

namespace ThreadPool
{
using namespace Logger;
static int gnumber = 1;
using callback_t = std::function<void()>;

enum class TSTATUS
{
THREAD_NEW,
THREAD_RUNNING,
THREAD_STOP
};

std::string Status2String(TSTATUS s)
{
switch (s)
{
case TSTATUS::THREAD_NEW:
return "THREAD_NEW";
case TSTATUS::THREAD_RUNNING:
return "THREAD_RUNNING";
case TSTATUS::THREAD_STOP:
return "THREAD_STOP";
default:
return "UNKNOWN";
}
}

std::string IsJoined(bool joinable)
{
return joinable ? "true" : "false";
}

class Thread
{
private:
void ToRunning()
{
_status = TSTATUS::THREAD_RUNNING;
}
void ToStop()
{
_status = TSTATUS::THREAD_STOP;
}
static void *ThreadRoutine(void *args)
{
Thread *self = static_cast<Thread *>(args);
pthread_setname_np(self->_tid, self->_name.c_str());
self->_cb();
self->ToStop();
return nullptr;
}

public:
Thread(callback_t cb)
: _tid(-1), _status(TSTATUS::THREAD_NEW), _joinable(true), _cb(cb), _result(nullptr)
{
_name = "New-Thread-" + std::to_string(gnumber++);
}
bool Start()
{
int n = pthread_create(&_tid, nullptr, ThreadRoutine, this);
if (n != 0)
return false;

ToRunning();
return true;
}
void Join()
{
if (_joinable)
{
int n = pthread_join(_tid, &_result);
if (n != 0)
{
std::cerr << "join error: " << n << std::endl;
return;
}
(void)_result;
_status = TSTATUS::THREAD_STOP;
Log(LogLevel::DEBUG) << _name << " join success";
}
else
{
std::cerr << "error, thread join status: " << IsJoined(_joinable) << std::endl;
}
}
// 暂停
// void Stop() // restart()
// {
// // 让线程暂停
// }
void Die()
{
if (_status == TSTATUS::THREAD_RUNNING)
{
pthread_cancel(_tid);
_status = TSTATUS::THREAD_STOP;
}
}
void Detach()
{
if (_status == TSTATUS::THREAD_RUNNING && _joinable)
{
pthread_detach(_tid);
_joinable = false;
}
else
{
std::cerr << "detach " << _name << " failed" << std::endl;
}
}
void PrintInfo()
{
std::cout << "thread name : " << _name << std::endl;
std::cout << "thread _tid : " << _tid << std::endl;
std::cout << "thread _status : " << Status2String(_status) << std::endl;
std::cout << "thread _joinable : " << IsJoined(_joinable) << std::endl;
}

~Thread()
{
}

private:
std::string _name;
pthread_t _tid;
TSTATUS _status;
bool _joinable;
// 线程要有自己的任务处理,即回调函数
callback_t _cb;

// 线程退出信息
void *_result;
};
}

        ThreadPool.hpp

#pragma once
#include "../Log.h"
#include "Thread.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include <iostream>
#include <memory>
#include <vector>
#include <queue>

namespace ThreadPool
{
using namespace Logger;
const int default_thread_num = 5;

// 测试用函数
void test()
{
while(1)
{
Log(LogLevel::DEBUG) << "我是一个新线程,我再运行ing….";
sleep(1);
}
}

template<typename T>
class threadpool
{
private:
void HandlerTask()
{
char name[128];
pthread_getname_np(pthread_self(), name, sizeof(name));
while (true)
{
T task;
{
LockModule::LockGuard lockguard(_mutex); // 使用 RAII 管理锁,避免手动解锁
while (_tasks.empty() && _isRunning)
{
_Slaves_Sleep_Count++;
_cond.Wait(_mutex);
_Slaves_Sleep_Count–;
}

if (!_isRunning && _tasks.empty())
{
// 退出前解锁(LockGuard 析构时会自动解锁,无需手动)
break;
}

task = _tasks.front();
_tasks.pop();
}

Log(LogLevel::INFO) << name << "处理任务:";
task();
// Log(LogLevel::DEBUG) << task.Result();
}
Log(LogLevel::INFO) << name << " quit…";
}

// 构造函数私有,禁止外部直接创建
threadpool(int slave_num = default_thread_num)
: _isRunning(false), _Slavernum(slave_num), _Slaves_Sleep_Count(0)
{
_Slaves.reserve(_Slavernum);
auto fun = std::bind(&threadpool::HandlerTask, this);
for (size_t i = 0; i < _Slavernum; ++i)
{
_Slaves.emplace_back(fun);
}
}

// 禁止拷贝和赋值(修正:类名应为 threadpool)
threadpool(const threadpool &) = delete;
threadpool &operator=(const threadpool &) = delete;

public:
static threadpool<T>* Instance()
{
if (_instance == nullptr)
{
LockModule::LockGuard lockguard(_lock);
if (_instance == nullptr)
{
// 使用 reset(new …) 替代 std::make_unique,因为构造函数私有
_instance.reset(new threadpool<T>());
_instance->Init(); // 调用 Init 而非 Start
Log(LogLevel::INFO) << "第一次使用线程池,创建线程池对象";
}
}
return _instance.get();
}

~threadpool()
{
}

void Init()
{
if (_isRunning)
{
Log(LogLevel::WARNING) << "线程池已经运行";
return;
}
_isRunning = true;
for (auto &slave : _Slaves)
{
slave.Start();
}
}

void Stop()
{
if (!_isRunning)
{
Log(LogLevel::WARNING) << "线程池未运行";
return;
}
// 停止线程池
_mutex.Lock();
_isRunning = false;
if (_Slaves_Sleep_Count > 0)
_cond.Broadcast();
_mutex.Unlock();

// 等待所有线程退出(可选)
// Wait();
}

void Wait()
{
for (auto &slave : _Slaves)
{
slave.Join();
}
}

void Enqueue(T in)
{
_mutex.Lock();
_tasks.push(in);
if (_Slaves_Sleep_Count > 0)
_cond.Signal();
_mutex.Unlock();
}

private:
bool _isRunning;
size_t _Slavernum;
std::vector<Thread> _Slaves;
std::queue<T> _tasks; // 临界资源
LockModule::Mutex _mutex;
CondModule::Cond _cond;
size_t _Slaves_Sleep_Count; // 休眠的线程数量

// 单例模式静态成员
static std::unique_ptr<threadpool<T>> _instance;
static LockModule::Mutex _lock; // 类型改为 LockModule::Mutex
};

// 静态成员定义(必须放在命名空间内)
template<typename T>
std::unique_ptr<threadpool<T>> threadpool<T>::_instance = nullptr;

template<typename T>
LockModule::Mutex threadpool<T>::_lock;

} // namespace ThreadPool

网络客户端

#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//网络客户端封装
class InetAddr
{
private:
std::string IP_;
uint16_t port_;
struct sockaddr_in address_;
socklen_t len_;
public:
InetAddr()=default;
InetAddr(const struct sockaddr_in& address) : address_(address),len_(sizeof(address))
{
IP_ = inet_ntoa(address_.sin_addr); // 将二进制IP转换为字符串
port_ = ntohs(address_.sin_port); // 端口转换正确
}
InetAddr(uint16_t port,const std::string ip="0.0.0.0"):IP_(ip),port_(port)
{
bzero(&address_, sizeof(address_));
address_.sin_family = AF_INET;
address_.sin_addr.s_addr = inet_addr(IP_.c_str());
address_.sin_port = htons(port_);
len_=sizeof(address_);
}
struct sockaddr_in*GetNetAddress()
{
return &address_;
}
~InetAddr()=default;
socklen_t len()
{
return len_;
}
bool operator==(const InetAddr&addrs)
{
return (this-> IP_==addrs.IP_)&&(this->port_==addrs.port_);
}
std::string ToString()
{
return "[" + IP_ + ":" + std::to_string(port_) + "]";
}

};

命令

#pragma once

#include <iostream>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdio>
#include <sys/types.h>
#include <pwd.h>
#include <unistd.h>

class Command
{
private:
void LoadCommand()
{
_whitelist.emplace_back("ls -a -l");
_whitelist.emplace_back("ls -l");
_whitelist.emplace_back("pwd");
_whitelist.emplace_back("whoami");
_whitelist.emplace_back("who");
_whitelist.emplace_back("ps -al");
_whitelist.emplace_back("netstat -nltp");
_whitelist.emplace_back("netstat -nutp");
_whitelist.emplace_back("netstat -natp");
_whitelist.emplace_back("netstat -naup");
}
bool IsSafe(const std::string &cmd)
{
for (auto &elem : _whitelist)
{
if (elem == cmd)
return true;
}
return false;
}

public:
Command()
{
LoadCommand();
}
~Command() {}
// rm *.c *.cc *.hpp
// rm move
// ls -al && rm -rf /
std::string Excute(const std::string &cmd)
{
if (!IsSafe(cmd))
{
return "bad man!";
}

FILE *fp = popen(cmd.c_str(), "r");
if (fp == nullptr)
return "execute error!";
std::string result;
char buffer[1024];
while (fgets(buffer, sizeof(buffer), fp))
{
result += buffer;
}

pclose(fp);

return result;
}
std::string GetCommandString()
{
// whb@iv-ye4ege8iyo5i3z3clix9:~$

struct passwd *pw = getpwuid(getuid());
std::string username = (pw?pw->pw_name : "unknown");

char hostname[256];
if (gethostname(hostname, sizeof(hostname)) != 0)
{
snprintf(hostname, sizeof(hostname), "unknown");
}

char cwd[1024];
if (getcwd(cwd, sizeof(cwd)) == NULL)
{
snprintf(cwd, sizeof(cwd), "unknown");
}

return "[" + username + "@" + hostname + " " + cwd + "]# ";
}

private:
std::vector<std::string> _whitelist;
};

服务器

        服务器代码

#pragma once

#include "Log.h"
#include "InetAddr.h"
#include "ThreadPool/ThreadPool.hpp"
#include "Command.hpp" // 新增:包含 Command 类
#include <unistd.h>
#include <functional>
#include <memory> // 新增:std::unique_ptr

using namespace Logger;
using namespace ThreadPool;

using task_t = std::function<void()>;
using handler_t = std::function<std::string(std::string)>;

enum
{
SUCCESS = 0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
USAGE_ERR
};

static const int gblocklist = 16;
static const uint16_t defaultport = 8080;

class TCPServer
{
private:
uint16_t port_;
int ListenSocketfd_;
handler_t hanlder_;
std::unique_ptr<Command> cmd_; // 新增:持有 Command 对象

public:
TCPServer(uint16_t port = defaultport) : port_(port) {}

void Init()
{
ListenSocketfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (ListenSocketfd_ < 0)
{
Log(LogLevel::ERROR) << "服务器创建套接字失败";
exit(SOCKET_ERR);
}
Log(LogLevel::DEBUG) << "创建套接字成功" << ListenSocketfd_;

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;

int n = bind(ListenSocketfd_, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
Log(LogLevel::ERROR) << "绑定失败";
exit(BIND_ERR);
}
Log(LogLevel::DEBUG) << "绑定成功";

int m = listen(ListenSocketfd_, gblocklist);
if (m < 0)
{
Log(LogLevel::ERROR) << "监听失败";
exit(LISTEN_ERR);
}
Log(LogLevel::DEBUG) << "监听成功";
}

// 新增:设置 Command 对象(所有权转移)
void SetCommand(std::unique_ptr<Command> cmd)
{
cmd_ = std::move(cmd);
}

// 新增:获取原始指针供 lambda 使用
Command* GetCommand() const
{
return cmd_.get();
}

void Register(handler_t handler)
{
hanlder_ = handler;
}

void ServerIO(int fd, InetAddr address)
{
Log(LogLevel::DEBUG) << "客户端信息" << address.ToString();
while (1)
{
// 新增:发送命令提示符
std::string prompt = cmd_->GetCommandString();
ssize_t sent = write(fd, prompt.c_str(), prompt.size());
if (sent <= 0)
{
Log(LogLevel::WARNING) << "发送提示符失败,关闭连接";
break;
}

char inbuffer[1024] = {0};
ssize_t n = read(fd, inbuffer, sizeof(inbuffer) – 1);
if (n > 0)
{
inbuffer[n] = 0;
std::string result;
if (hanlder_ != nullptr)
{
result = hanlder_(inbuffer);
}
else
{
Log(LogLevel::WARNING) << "服务没有准备好";
break;
}
write(fd, result.c_str(), result.size());
}
else if (n == 0)
{
Log(LogLevel::INFO) << "客户端退出" << address.ToString();
break;
}
else
{
Log(LogLevel::WARNING) << "客户端出错" << address.ToString();
break;
}
}
}

class ThreadData
{
public:
ThreadData(TCPServer *ts, int sockfd, InetAddr addr)
: _this(ts), _sockfd(sockfd), _addr(addr)
{}
~ThreadData() {}
public:
TCPServer *_this;
int _sockfd;
InetAddr _addr;
};

void Start()
{
while (1)
{
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int socketfd = accept(ListenSocketfd_, (struct sockaddr *)&clientaddr, &len);
if (socketfd < 0)
{
Log(LogLevel::WARNING) << "监听失败";
continue;
}
Log(LogLevel::DEBUG) << "监听成功";

InetAddr clientaddress(clientaddr);
threadpool<task_t>::Instance()->Enqueue([this, socketfd, clientaddress]() {
this->ServerIO(socketfd, clientaddress);
});
}
}

~TCPServer()
{
close(ListenSocketfd_);
}
};

        服务器运行

#include "TCPServer.hpp"
#include "Command.hpp"
#include <memory>
#include <cstdlib>

inline static void Usage(const std::string &proc)
{
std::cerr << "输入错误\\n\\t";
std::cerr << proc << "你应该告诉我你的端口号" << std::endl;
}

int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
ENABLE_LOG_CONSOLE();

uint16_t port = std::atoi(argv[1]);

// 创建 Command 对象
auto cmd = std::make_unique<Command>();

// 创建 TCPServer 对象
auto tsvr = std::make_unique<TCPServer>(port);

// 将 Command 所有权转移给 TCPServer
tsvr->SetCommand(std::move(cmd));

// 注册命令处理函数(使用 TCPServer 内部的 Command 对象)
tsvr->Register([cmd_ptr = tsvr->GetCommand()](std::string command) {
return cmd_ptr->Excute(command);
});

tsvr->Init();
tsvr->Start();

return 0;
}

        客户端运行

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "InetAddr.h"

static void Usage(const std::string &name)
{
std::cerr << "Usage:\\n\\t";
std::cerr << name << " server_ip server_port" << std::endl;
}

// ./client_tcp server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
// 1. 创建tcpsocket套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
exit(2);
}

// 2. 要不要进行bind?需要
// 要不要显示的bind本地IP和端口?不需要自己手动显示的进行bind,OS会自动bind的!
// OS 帮助我们进行随机端口,防止端口号冲突
// 2. 应该干什么?bind端口号ip,发起建立连接的工作
// {client ip, client port} <-> {server ip, server port}
InetAddr serveraddress(server_port, server_ip);
int n = connect(sockfd, (struct sockaddr *)serveraddress.GetNetAddress(), serveraddress.len());
if (n < 0)
{
std::cerr << "connect to " << serveraddress.ToString() << " failed!"<< std::endl;
exit(3);
}
std::cerr << "connect to " << serveraddress.ToString() << " success!" << std::endl;

// 3. 通信
while(true)
{
char commandline[1024];
std::string commandlinestr;
ssize_t n = read(sockfd, commandline, sizeof(commandline)-1);
if(n > 0)
{
commandline[n] = 0;
commandlinestr = commandline;
}
else{
commandlinestr = "[unknow@unknown None]# ";
}

std::cout << commandlinestr;

// 处理一个命令
std::string command;
std::getline(std::cin, command);
write(sockfd, command.c_str(), command.size());

char inbuffer[1024];
n = read(sockfd, inbuffer, sizeof(inbuffer));
if(n > 0)
{
inbuffer[n] = 0;
std::cout << inbuffer << std::endl;
}
else if(n == 0)
{
std::cout << "read enf of file!" << std::endl;
break;
}
else
{
std::cerr << "read error!" << std::endl;
break;
}
}

return 0;
}

        测试

        跨网络连接,结果为:

        关于TCP网络的内容,到这里就结束了,喜欢请点个赞,谢谢

封面图自取:

赞(0)
未经允许不得转载:网硕互联帮助中心 » C++项目:基于Linux系统和TCP协议的远程命令行终端
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!