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

📅 发布时间:2026/7/8 19:37:46 👁️ 浏览次数:
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 // C17 #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_uniqueConsoleStrategy(); } void UseFileStrategy() { _strategy std::make_uniqueFileLogStrategy(); } void UseLevelFileStrategy(const std::string log_dir ./logs) { _strategy std::make_uniqueLevelFileLogStrategy(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(); _pidgetpid(); // 构建日志左半部分信息 std::stringstream oss; oss [_cur_time] [LogLevelToString(_level)] [_pid] [ filename : line ]; _LogInfo oss.str(); } templatetypename T LogMessage operator(const Tinfo) { 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_ptrLogStrategy _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::functionvoid(); 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_castThread *(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); } } templatetypename 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 threadpoolT* Instance() { if (_instance nullptr) { LockModule::LockGuard lockguard(_lock); if (_instance nullptr) { // 使用 reset(new ...) 替代 std::make_unique因为构造函数私有 _instance.reset(new threadpoolT()); _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::vectorThread _Slaves; std::queueT _tasks; // 临界资源 LockModule::Mutex _mutex; CondModule::Cond _cond; size_t _Slaves_Sleep_Count; // 休眠的线程数量 // 单例模式静态成员 static std::unique_ptrthreadpoolT _instance; static LockModule::Mutex _lock; // 类型改为 LockModule::Mutex }; // 静态成员定义必须放在命名空间内 templatetypename T std::unique_ptrthreadpoolT threadpoolT::_instance nullptr; templatetypename T LockModule::Mutex threadpoolT::_lock; } // namespace ThreadPool网络客户端#pragma once #includeiostream #includestring #includecstring #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 ip0.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 InetAddraddrs) { 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() { // whbiv-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::vectorstd::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::functionvoid(); using handler_t std::functionstd::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_ptrCommand 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_ptrCommand 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); threadpooltask_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_uniqueCommand(); // 创建 TCPServer 对象 auto tsvr std::make_uniqueTCPServer(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和端口不需要自己手动显示的进行bindOS会自动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 [unknowunknown 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网络的内容到这里就结束了喜欢请点个赞谢谢封面图自取