> 技术文档 > 【Linux | 网络】socket编程 - 使用TCP实现服务端向客户端提供简单的服务

【Linux | 网络】socket编程 - 使用TCP实现服务端向客户端提供简单的服务

【Linux | 网络】socket编程 - 使用TCP实现服务端向客户端提供简单的服务

目录

  • 一、Comm.hpp(公共数据)
  • 二、Log.hpp(日志)
  • 三、InetAddr.hpp(管理sockaddr_in相关信息)
  • 四、NoCopy.hpp(防拷贝)
  • 五、Lockguard.hpp(自动管理锁)
  • 六、Thread.hpp(封装线程
  • 七、ThreadPool.hpp(线程池)
  • 八、dict.txt(配置文件、简单字典)
  • 九、Translate.hpp(提供翻译服务)
  • 十、Daemon.hpp(使进程变为守护进程)
  • 十一、TcpServer.hpp(V1~V4版服务端封装)
  • 十二、TcpServer.hpp(最终版,V5版服务端封装)
  • 十三、Main.cpp(服务端)
  • 十四、TcpClient.cpp(客户端)
  • 结尾

一、Comm.hpp(公共数据)

#pragma onceenum { Socket_err = 1, Bind_err, Accept_err, Recvfrom_err};

二、Log.hpp(日志)

#pragma once#include #include #include #include #include #include #include #include #include #include #include using namespace std;// 日志等级enum{ Debug = 0, // 调试 Info, // 正常 Warning, // 警告 Error, // 错误,但程序并未直接退出 Fatal // 程序直接挂掉};enum{ Screen = 10, // 打印到显示器上 OneFile, // 打印到一个文件中 ClassFile // 按照日志等级打印到不同的文件中};string LevelToString(int level){ switch (level) { case Debug: return \"Debug\"; case Info: return \"Info\"; case Warning: return \"Warning\"; case Error: return \"Error\"; case Fatal: return \"Fatal\"; default: return \"Unknow\"; }}const char* default_filename = \"log.\";const int default_style = Screen;const char* defaultdir = \"log\";class Log{public: Log() : style(default_style), filename(default_filename) { // mkdir(defaultdir,0775); pthread_mutex_init(&_log_mutex, nullptr); } void SwitchStyle(int sty) { style = sty; } void WriteLogToOneFile(const string& logname, const string& logmessage) { int fd = open(logname.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666); if (fd == -1) return; pthread_mutex_lock(&_log_mutex); write(fd, logmessage.c_str(), logmessage.size()); pthread_mutex_unlock(&_log_mutex); close(fd); } void WriteLogToClassFile(const string& levelstr, const string& logmessage) { mkdir(defaultdir, 0775); string name = defaultdir; name += \"/\"; name += filename; name += levelstr; WriteLogToOneFile(name, logmessage); } void WriteLog(int level, const string& logmessage) { switch (style) { case Screen: { pthread_mutex_lock(&_log_mutex); cout << logmessage; pthread_mutex_unlock(&_log_mutex); } break; case OneFile: WriteLogToClassFile(\"All\", logmessage); break; case ClassFile: WriteLogToClassFile(LevelToString(level), logmessage); break; default: break; } } string GetTime() { time_t CurrentTime = time(nullptr); struct tm* curtime = localtime(&CurrentTime); char time[128]; // localtime 的年是从1900开始的,所以要加1900, 月是从0开始的所以加1 snprintf(time, sizeof(time), \"%d-%d-%d %d:%d:%d\", curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday, curtime->tm_hour, curtime->tm_min, curtime->tm_sec); return time; return \"\"; } void LogMessage(int level, const char* format, ...) { char left[1024]; string Levelstr = LevelToString(level).c_str(); string Timestr = GetTime().c_str(); string Idstr = to_string(getpid()); snprintf(left, sizeof(left), \"[%s][%s][%s] \", Levelstr.c_str(), Timestr.c_str(), Idstr.c_str()); va_list args; va_start(args, format); char right[1024]; vsnprintf(right, sizeof(right), format, args); string logmessage = left; logmessage += right; WriteLog(level, logmessage); va_end(args); } ~Log() { pthread_mutex_destroy(&_log_mutex); };private: int style; string filename; pthread_mutex_t _log_mutex;};Log lg;class Conf{public: Conf() { lg.SwitchStyle(Screen); } ~Conf() { }};Conf conf;

三、InetAddr.hpp(管理sockaddr_in相关信息)

#pragma once#include #include #include  /* See NOTES */#include #include class InetAddr{public: InetAddr(struct sockaddr_in sock) :_sock(sock) {} std::string Ip() { return inet_ntoa(_sock.sin_addr); } uint16_t Port() { return ntohs(_sock.sin_port); } std::string PrintDebug() { std::string info = \"[\"; info += Ip(); info += \":\"; info += std::to_string(Port()); info += \"]\"; info += \"# \"; return info; } ~InetAddr() {}private: struct sockaddr_in _sock;};

四、NoCopy.hpp(防拷贝)

#pragma onceclass Nocopy{public: Nocopy() {} ~Nocopy() {} Nocopy(const Nocopy&) = delete; const Nocopy& operator=(const Nocopy&) = delete;};

五、Lockguard.hpp(自动管理锁)

#pragma once#include class Mutex{public: Mutex(pthread_mutex_t* lock) :pmutex(lock) {} void Lock() { pthread_mutex_lock(pmutex); } void Unlock() { pthread_mutex_unlock(pmutex); } ~Mutex() {}public: pthread_mutex_t* pmutex;};class LockGuard{public: LockGuard(pthread_mutex_t* lock) :mutex(lock) { mutex.Lock(); } ~LockGuard() { mutex.Unlock(); }public: Mutex mutex;};

六、Thread.hpp(封装线程)

#pragma once#include #include #include #include using namespace std;// typedef function func_t;template<class T>using func_t = function<void(T&)>;template<class T>class Thread{public: Thread(const string &threadname, func_t<T> func ,const T& data) : _pid(0), _threadname(threadname), _func(func), isrunning(false) , _data(data) { } // 线程需要执行的函数 static void *ThreadRoutine(void *arg) { Thread *pt = (Thread *)arg; pt->_func(pt->_data); return nullptr; } // 线程开创建并执行 bool Start() { int n = pthread_create(&_pid, nullptr, ThreadRoutine, this); if (n == 0) { isrunning = true; // cout << \"is strat , is running : \" << isrunning << endl; return true; } else { return false; } } // 线程等待 bool Join() { if(!isrunning) return false; return pthread_join(_pid, nullptr); } bool IsRunning() { return isrunning; } string ThreadName() { return _threadname; } ~Thread() { }private: pthread_t _pid; string _threadname; bool isrunning; func_t<T> _func; T _data;};

七、ThreadPool.hpp(线程池)

#pragma once#include #include #include #include #include #include \"Thread.hpp\"#include \"Log.hpp\"#include \"LockGuard.hpp\"using namespace std;const int default_threadnum = 3;class ThreadDate{public: ThreadDate(const string &name) : threadname(name) { } ~ThreadDate() { }public: string threadname;};template <class T>class ThreadPool{public: static ThreadPool *GetInstance() { if (_psl == nullptr) { LockGuard lock(&_sig_lock); if (_psl == nullptr) { lg.LogMessage(Info, \"create singleton is success\\n\"); _psl = new ThreadPool<T>(); } } return _psl; } static void DelInstance(){if (_psl){LockGuard lock(&_sig_lock);if (_psl){delete _psl;_psl = nullptr; lg.LogMessage(Info, \"destroy singleton is success\\n\");}}} // 启动所有线程 bool Start() { for (auto &t : _threads) { t.Start(); lg.LogMessage(Info, \"%s , is running...\\n\", t.ThreadName().c_str()); } return true; } // 等待所有线程终止 void Join() { for (auto &t : _threads) { t.Join(); } } // 线程等待当前条件变量 void Thread_Wait(const ThreadDate &td) { pthread_cond_wait(&_cond, &_mutex); lg.LogMessage(Debug, \"no task , %s is sleeping...\\n\", td.threadname.c_str()); } // 唤醒当前条件变量下的某个线程 void Thread_Wakeup() { pthread_cond_signal(&_cond); } // 添加任务 bool Push(T &in) { LockGuard lockguard(&_mutex); _q.push(in); Thread_Wakeup(); return true; } // 线程需要执行的回调函数 void ThreadRun(const ThreadDate &td) { while (1) { T t; // 取任务 { LockGuard lockguard(&_mutex); while (_q.empty()) {  Thread_Wait(td);  lg.LogMessage(Debug, \"haven task , %s is wakeup\\n\", td.threadname.c_str()); } t = _q.front(); _q.pop(); } // 执行任务 t(); // lg.LogMessage(Debug, \"%s handler task %s done , result is %s\\n\", //  td.threadname.c_str(), t.PrintTask().c_str(), t.PrintResult().c_str()); } }private: ThreadPool(int num = default_threadnum) : _threadnum(num) { // 初始化锁和条件变量 pthread_mutex_init(&_mutex, nullptr); pthread_cond_init(&_cond, nullptr); // 创建线程 for (int i = 0; i < _threadnum; i++) { string threadname = \"thread-\" + to_string(i + 1); ThreadDate td(threadname); // 由于Thread执行的是线程池的类内函数,而Thread调用的函数中并没有this指针 // 所以这里就使用bind函数,调整一下Thread调用函数的参数,使函数可以多接收一个参数 _threads.push_back(Thread<ThreadDate>(threadname, bind(&ThreadPool<T>::ThreadRun, this, placeholders::_1), td)); lg.LogMessage(Info, \"%s is create\\n\", threadname.c_str()); } } // 销毁锁和条件变量 ~ThreadPool() { pthread_mutex_destroy(&_mutex); pthread_cond_destroy(&_cond); } ThreadPool(const ThreadPool&) = delete; const ThreadPool& operator=(const ThreadPool&) = delete;private: queue<T> _q; vector<Thread<ThreadDate>> _threads; int _threadnum; static ThreadPool<T> *_psl; static pthread_mutex_t _sig_lock; pthread_mutex_t _mutex; pthread_cond_t _cond;};template <class T>ThreadPool<T> *ThreadPool<T>::_psl = nullptr;template <class T>pthread_mutex_t ThreadPool<T>::_sig_lock = PTHREAD_MUTEX_INITIALIZER;

八、dict.txt(配置文件、简单字典)

apple 苹果book 书cat 猫dog 狗eat 吃fly 飞happy 快乐jump 跳kiss 吻love 爱moon 月亮night 夜pen 笔red 红色run 跑sad 悲伤sing 唱歌star 星星tree 树water 水big 大的boy 男孩car 汽车dance 跳舞fast 快的girl 女孩

九、Translate.hpp(提供翻译服务)

#pragma once#include #include #include #include #include #include #include #include \"Log.hpp\"const char* defalutpath = \"resourse/dict.txt\";const static std::string Sep = \" \";const static std::string unknown = \"unknown\";class Translate{public: Translate() :_dict_path(defalutpath) { Loaddict(); Parse(); } // 加载字典 void Loaddict() { std::ifstream in(_dict_path.c_str(),std::ifstream::in); if(!in) lg.LogMessage(Fatal,\"ifstream error , errno : %d , error string : %s\\n\",errno,strerror(errno)); std::string line; while(getline(in,line)) { _lines.push_back(line); } } // 执行翻译 std::string Execute(const std::string& word) { if(_dict.find(word) != _dict.end()) { return _dict[word]; } return \"unknown\"; } // 将读取到的一行中英文单词互译拆分为:英文和中文 void Parse() { for(auto& line : _lines) { size_t pos = line.find(Sep); if(pos == std::string::npos) { continue; } std::string word = line.substr(0,pos); // 跳过空格 while(line[pos] == \' \') { pos++; } std::string chinese = line.substr(pos); _dict.insert(std::make_pair(word,chinese)); } } // 用于测试 void Debug() { for(auto& line:_lines) { std::cout << line << std::endl; } for(auto& tmp:_dict) { std::cout << tmp.first << \" : \" << tmp.second << std::endl; } } ~Translate() {}private: std::string _dict_path; std::vector<std::string> _lines; std::unordered_map<std::string,std::string> _dict;};

十、Daemon.hpp(使进程变为守护进程)

#pragma once#include #include #include  #include #include const char* root = \"/\";const char* dev_null = \"/dev/null\";bool Daemon(bool nochdir, bool noclose){ // 1、忽略可能引起程序异常退出的信号 SIGCHLD SIGPIPE signal(SIGCHLD,SIG_IGN); signal(SIGPIPE,SIG_IGN); // 2、创建子进程,让父进程退出,使得子进程不成为组长 pid_t pid = fork(); if(pid > 0) exit(0); // 3、设置自己成为一个新的会画,setsid setsid(); // 4、每一个进程都有自己的CWD(当前工作路径),是否将当前进程的CWD改为根目录 // ​改为根目录以后,进程可以以绝对路径的方式找到操作系统中的文件 if(nochdir) chdir(root); // 5、变成守护进程以后,就不需要与用户的输入、输出和错误进行关联了 // ​可以将它们全部关闭,但难免服务器中会有输入、输出和错误 // ​向关闭的文件描述符中写入可能会导致进程退出 // ​所以这里将它们关闭不是最优解,而是将它们重定向到/dev/null中 // ​因为写入到/dev/null的数据会被直接丢弃,而从/dev/null读取信息,会默认读取到文件结尾 if(noclose) { int fd = open(dev_null,O_RDWR); if(fd > 0) { dup2(fd,0); dup2(fd,1); dup2(fd,2); close(fd); } } else // 不推荐 { close(0); close(1); close(2); } return true;}

十一、TcpServer.hpp(V1~V4版服务端封装)

#pragma once#include #include #include #include #include #include #include #include #include #include #include #include #include #include \"Comm.hpp\"#include \"Log.hpp\"#include \"Nocopy.hpp\"#include \"LockGuard.hpp\"#include \"InetAddr.hpp\"#include \"Thread.hpp\"#include \"ThreadPool.hpp\"const static int default_backlog = 5;using task_t = function<void(int,InetAddr&)>;class TcpServer;class ThreadDate{public: ThreadDate(TcpServer* pts,int sockfd,const InetAddr& addr) :_sockfd(sockfd),_pts(pts),_addr(addr) {} int Sockfd() { return _sockfd; } InetAddr& GetAddr() { return _addr; } TcpServer* GetPTcpserver() { return _pts; } ~ThreadDate() {}private: int _sockfd; TcpServer* _pts; InetAddr _addr;};class TcpServer : public Nocopy{ public: TcpServer(uint16_t port) :_port(port),_isrunning(false) {} void Init() { // 创建套接字 _listensock = socket(AF_INET,SOCK_STREAM,0); if(_listensock < 0) { lg.LogMessage(Fatal,\"create socket fail , errno : %d , error string : %s\\n\",errno,strerror(errno)); exit(Socket_err); } lg.LogMessage(Info , \"create socket success , sockfd : %d\\n\",_listensock); int opt = 1; setsockopt(_listensock,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt)); // 填充网络信息,并绑定 struct sockaddr_in ServerSockaddr; ServerSockaddr.sin_family = AF_INET; ServerSockaddr.sin_port = htons(_port); ServerSockaddr.sin_addr.s_addr = INADDR_ANY; socklen_t ServerLen = sizeof(ServerSockaddr); int n = bind(_listensock , (struct sockaddr*)&ServerSockaddr,ServerLen); if(n < 0) { lg.LogMessage(Fatal,\"bind socket fail , errno : %d , error string : %s\\n\",errno,strerror(errno)); exit(Bind_err); } lg.LogMessage(Info , \"bind socket success , sockfd : %d\\n\",_listensock); // 将listensock调节为监听模式 int m = listen(_listensock,default_backlog); if(m < 0) { lg.LogMessage(Fatal,\"listen socket fail , errno : %d , error string : %s\\n\",errno,strerror(errno)); exit(Listen_err); } lg.LogMessage(Info , \"listen socket success , sockfd : %d\\n\",_listensock); } // v1、v2、v3版本Service // void Service(int sockfd) // { // char buffer[1024]; // while(true) // { // int n = read(sockfd,buffer,sizeof(buffer) - 1); // if(n > 0) // { // buffer[n] = 0; // std::cout << \"Client Say# \" << buffer << std::endl; // std::string response = buffer; // int m = write(sockfd,response.c_str(),response.size());  // } // else if(n == 0) // { // lg.LogMessage(Warning,\"client quit\\n\"); // break; // } // else // { // lg.LogMessage(Fatal,\"read socket error , errno : %d , error string : %s\\n\",errno,strerror(errno)); // break; // } // } // } // v4版本Service void Service(int sockfd,InetAddr& inetAddr) { char buffer[1024]; while(true) { int n = read(sockfd,buffer,sizeof(buffer) - 1); if(n > 0) { buffer[n] = 0; std::cout << inetAddr.PrintDebug() << buffer << std::endl; std::string response = buffer; int m = write(sockfd,response.c_str(),response.size()); } else if(n == 0) { lg.LogMessage(Warning,\"client quit\\n\"); break; } else { lg.LogMessage(Fatal,\"read socket error , errno : %d , error string : %s\\n\",errno,strerror(errno)); break; } } } static void* HandlerRoutine(void* arg) { // 使主线程与新线程分离,使得主线程不需要等待新线程终止 pthread_detach(pthread_self()); ThreadDate* td = (ThreadDate*)arg; td->GetPTcpserver()->Service(td->Sockfd(),td->GetAddr()); // 关闭文件描述符,delete ThreadDate close(td->Sockfd()); delete td; return nullptr; } void Start() { _isrunning = true; while(_isrunning) { // 获取连接 struct sockaddr_in ClientSockaddr; socklen_t ClientLen = sizeof(ClientSockaddr); int sockfd = accept(_listensock,CONV(&ClientSockaddr),&ClientLen); if(sockfd < 0) { lg.LogMessage(Warning,\"accept socket fail , errno : %d , error string : %s\\n\",errno,strerror(errno)); break; } lg.LogMessage(Info , \"accept socket success , get a new sockfd : %d\\n\",sockfd); // v3版本服务,忽略子进程退出时给父进程发送的信号 signal(SIGCHLD,SIG_IGN); // 提供服务 // v1版本,单进程版本 // Service(sockfd); // close(sockfd); // v2版本,多进程版本 ,父进程负责接收连接,子进程(孙子进程)负责处理连接 // 这里父进程和子进程需要关闭自己不需要的文件描述符 // 防止多个客户端访问服务器时,导致父进程的文件描述符不够用的情况 // int fd = fork(); // if(fd < 0) // { // lg.LogMessage(Fatal,\"fork child process fail , errno : %d , error string : %s\\n\",errno,strerror(errno)); // break; // } // else if(fd == 0) // 子进程 // { // // 这里我们不想让父进程等待子进程退出 // // 我们创建一个孙子进程,子进程退出 // // 这样孙子进程变成了孤儿进程,被操作系统领养 // // 退出时,操作系统会回收孙子进程 // if(fork())  // exit(0); // Service(sockfd); // close(sockfd); // close(_listensock); // } // else // 父进程 // { // close(sockfd); // } // v3版本,多进程版本,忽略信号版本 // 忽略子进程退出时给父进程发送的信号,父进程就不需要等待子进程退出了 // int fd = fork(); // if(fd < 0) // { // lg.LogMessage(Fatal,\"fork child process fail , errno : %d , error string : %s\\n\",errno,strerror(errno)); // break; // } // else if(fd == 0) // 子进程 // { // Service(sockfd); // close(sockfd); // close(_listensock); // } // else // 父进程 // { // close(sockfd); // } // v4版本,多线程版本 // pthread_t pth; // ThreadDate* ptd = new ThreadDate(this,sockfd,InetAddr(ClientSockaddr)); // pthread_create(&pth,nullptr,HandlerRoutine,ptd); } } ~TcpServer() {}private: uint16_t _port; int _listensock; bool _isrunning;};

十二、TcpServer.hpp(最终版,V5版服务端封装)

#pragma once#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include \"Comm.hpp\"#include \"Log.hpp\"#include \"Nocopy.hpp\"#include \"LockGuard.hpp\"#include \"InetAddr.hpp\"#include \"Thread.hpp\"#include \"ThreadPool.hpp\"#include \"Translate.hpp\"const static int default_backlog = 5;using callback_t = function<void(int,InetAddr&)>; // 回调函数using task_t = function<void(void)>; class TcpServer : public Nocopy{ public: TcpServer(uint16_t port) :_port(port),_isrunning(false) {} void Init() { // 创建套接字 _listensock = socket(AF_INET,SOCK_STREAM,0); if(_listensock < 0) { lg.LogMessage(Fatal,\"create socket fail , errno : %d , error string : %s\\n\",errno,strerror(errno)); exit(Socket_err); } lg.LogMessage(Info , \"create socket success , sockfd : %d\\n\",_listensock); int opt = 1; setsockopt(_listensock,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt)); // 填充网络信息,并绑定 struct sockaddr_in ServerSockaddr; ServerSockaddr.sin_family = AF_INET; ServerSockaddr.sin_port = htons(_port); ServerSockaddr.sin_addr.s_addr = INADDR_ANY; socklen_t ServerLen = sizeof(ServerSockaddr); int n = bind(_listensock , (struct sockaddr*)&ServerSockaddr,ServerLen); if(n < 0) { lg.LogMessage(Fatal,\"bind socket fail , errno : %d , error string : %s\\n\",errno,strerror(errno)); exit(Bind_err); } lg.LogMessage(Info , \"bind socket success , sockfd : %d\\n\",_listensock); // 将listensock调节为监听模式 int m = listen(_listensock,default_backlog); if(m < 0) { lg.LogMessage(Fatal,\"listen socket fail , errno : %d , error string : %s\\n\",errno,strerror(errno)); exit(Listen_err); } lg.LogMessage(Info , \"listen socket success , sockfd : %d\\n\",_listensock); // 启动线程池 ThreadNS::ThreadPool<task_t>::GetInstance()->Start(); // 添加默认任务 RegisterFunc(\"defalutserver\",bind(&TcpServer::DefalutServer,this,placeholders::_1,placeholders::_2)); } // v4版本Service void Service(int sockfd,InetAddr& inetAddr) { char buffer[1024]; while(true) { int n = read(sockfd,buffer,sizeof(buffer) - 1); if(n > 0) { buffer[n] = 0; std::cout << inetAddr.PrintDebug() << buffer << std::endl; std::string response = buffer; int m = write(sockfd,response.c_str(),response.size()); } else if(n == 0) { lg.LogMessage(Warning,\"client quit\\n\"); break; } else { lg.LogMessage(Fatal,\"read socket error , errno : %d , error string : %s\\n\",errno,strerror(errno)); break; } } } // 效果:| ping | translate | transform | // 获取任务列表 void DefalutServer(int sockfd,InetAddr& inetaddr) { (void)inetaddr; // 这里未使用inetaddr,强转防止警告 string task_list = \"| \"; for(auto task : _task_list) { if(task.first != \"defalutserver\") { task_list += task.first; task_list += \" | \"; } } write(sockfd,task_list.c_str(),task_list.size()); } // 读取客户端所需服务 string Read(int sockfd,InetAddr& inetaddr) { char buffer[1024]; int n = read(sockfd,buffer,sizeof(buffer) - 1); if(n > 0) { buffer[n] = 0; } else if(n == 0) { lg.LogMessage(Warning,\"client quit\\n\"); } else { lg.LogMessage(Fatal,\"read socket error , errno : %d , error string : %s\\n\",errno,strerror(errno)); } return buffer; } // 任务转接 void Routine(int sockfd,InetAddr& inetaddr) { _task_list[\"defalutserver\"](sockfd,inetaddr); string type = Read(sockfd,inetaddr); lg.LogMessage(Info,\"%s select %s\\n\",inetaddr.PrintDebug().c_str(),type.c_str()); if(type == \"ping\") { _task_list[type](sockfd,inetaddr); } else if(type == \"translate\") { _task_list[type](sockfd,inetaddr); } else if(type == \"transform\") { _task_list[type](sockfd,inetaddr); } else {} close(sockfd); } // 注册任务 void RegisterFunc(const string& type , callback_t cb) { _task_list[type] = cb; } void Start() { _isrunning = true; while(_isrunning) { // 获取连接 struct sockaddr_in ClientSockaddr; socklen_t ClientLen = sizeof(ClientSockaddr); int sockfd = accept(_listensock,CONV(&ClientSockaddr),&ClientLen); if(sockfd < 0) { lg.LogMessage(Warning,\"accept socket fail , errno : %d , error string : %s\\n\",errno,strerror(errno)); break; } lg.LogMessage(Info , \"accept socket success , get a new sockfd : %d\\n\",sockfd); // v5版本,线程池版本 // 其他四个版本在TcpServer copy.hpp中 // task_t task = bind(&TcpServer::Service,this,sockfd,InetAddr(ClientSockaddr)); // 将任务添加到线程池中 task_t task = bind(&TcpServer::Routine,this,sockfd,InetAddr(ClientSockaddr)); ThreadNS::ThreadPool<task_t>::GetInstance()->Push(task); } } ~TcpServer() {}private: uint16_t _port; int _listensock; bool _isrunning; // 任务列表 unordered_map<string,callback_t> _task_list;};

十三、Main.cpp(服务端)

#include #include #include #include #include #include \"TcpServer.hpp\"#include \"InetAddr.hpp\"#include \"Log.hpp\"#include \"Translate.hpp\"#include \"Daemon.hpp\"using namespace std;Translate trans;void Usage(const string& proc){ cout << proc << \" localport\\n\" << endl;}// 未来我们不知道我们的服务器在任意一个时刻是否是健康的// 所以我们可以让客户端定期的向服务器发送最小请求,若服务器有回应,则说明服务正常// 这种机制我们称之为心跳机制 // ping服务,客户端发如何消息,服务端回复pong,表示服务器健康void Ping(int sockfd,InetAddr& inetaddr){ lg.LogMessage(Info,\"%s select ping\\n\",inetaddr.PrintDebug().c_str()); char buffer[1024]; int n = read(sockfd,buffer,sizeof(buffer) - 1); if(n > 0) { string response = \"Pong\"; write(sockfd,response.c_str(),response.size()); } else if(n == 0) { lg.LogMessage(Warning,\"client quit\\n\"); } else { lg.LogMessage(Fatal,\"read socket error , errno : %d , error string : %s\\n\",errno,strerror(errno)); }}// 提供翻译服务,客户端发送一个单词,服务端返回单词的翻译void Translate(int sockfd,InetAddr& inetaddr){ lg.LogMessage(Info,\"%s select translate\\n\",inetaddr.PrintDebug().c_str()); char buffer[1024]; int n = read(sockfd,buffer,sizeof(buffer) - 1); if(n > 0) { buffer[n] = 0; string word = buffer; string response = trans.Execute(word); write(sockfd,response.c_str(),response.size()); } else if(n == 0) { lg.LogMessage(Warning,\"client quit\\n\"); } else { lg.LogMessage(Fatal,\"read socket error , errno : %d , error string : %s\\n\",errno,strerror(errno)); }}// 提供转大写服务,服务端将客户端发送的信息,全部转换为大写再发送给客户端void TransForm(int sockfd,InetAddr& inetaddr){ lg.LogMessage(Info,\"%s select transform\\n\",inetaddr.PrintDebug().c_str()); char buffer[1024]; int n = read(sockfd,buffer,sizeof(buffer) - 1); if(n > 0) { buffer[n] = 0; string messages = buffer; string response(messages.size(),\' \'); std::transform(messages.begin(),messages.end(),response.begin(),[](unsigned char c){return toupper(c);}); write(sockfd,response.c_str(),response.size()); } else if(n == 0) { lg.LogMessage(Warning,\"client quit\\n\"); } else { lg.LogMessage(Fatal,\"read socket error , errno : %d , error string : %s\\n\",errno,strerror(errno)); }}int main(int argc , char* argv[]){ if (argc != 2) { Usage(argv[0]); exit(1); } uint16_t ServerPort = stoi(argv[1]); Daemon(true,true); lg.SwitchStyle(OneFile); unique_ptr<TcpServer> uq = make_unique<TcpServer>(ServerPort); uq->RegisterFunc(\"ping\",Ping); uq->RegisterFunc(\"translate\",Translate); uq->RegisterFunc(\"transform\",TransForm); uq->Init(); uq->Start(); return 0;}

十四、TcpClient.cpp(客户端)

#include #include #include #include #include #include #include #include #include \"Comm.hpp\"using namespace std;const static int Retry_Count = 5;void Usage(const string &proc){ cout << proc << \" serverip serverport\\n\" << endl;}// // v1~v4版本的客户端// // v1~v4版本的服务器都是长服务,客户端发送什么消息,服务器添加部分消息后就直接转发服务// bool VisitServer(const string &serverip, uint16_t serverport,int* count)// {// int n = 0 , m = 0;// string buffer;// char inbuffer[1024];// bool ret = true;// // 填充服务端信息// struct sockaddr_in server;// server.sin_family = AF_INET;// server.sin_port = htons(serverport);// inet_pton(AF_INET, serverip.c_str(), &server.sin_addr.s_addr);// // 创建套接字// int sockfd = socket(AF_INET, SOCK_STREAM, 0);// if (sockfd < 0)// {// cout << \"create socket fail\" << endl;// ret = false;// goto END;// }// // 建立连接// if (connect(sockfd, CONV(&server), sizeof(server)) != 0)// {// cout << \"connect fail\" << endl;// ret = false;// goto END;// }// *count = 1;// while(1)// {// // 向服务端发送信息// cout << \"Please Enter# \";// getline(cin, buffer);// n = write(sockfd, buffer.c_str(), buffer.size());// if (n > 0)// {// m = read(sockfd, inbuffer, sizeof(inbuffer) - 1);// if (m > 0)// {//  inbuffer[m] = 0;//  cout << \"get a message : \" << inbuffer << endl;// }// else // {//  ret = false;//  goto END;// }// }// else// {// ret = false;// goto END;// }// }// END:// close(sockfd);// return ret;// }// v5版本客户端// 短服务,服务一次后就退出bool VisitServer(const string &serverip, uint16_t serverport,int* count){ int n = 0 , m = 0; string buffer; char inbuffer[1024]; bool ret = true; // 填充服务端信息 struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(serverport); inet_pton(AF_INET, serverip.c_str(), &server.sin_addr.s_addr); // 创建套接字 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { cout << \"create socket fail\" << endl; ret = false; goto END; } // 建立连接 if (connect(sockfd, CONV(&server), sizeof(server)) != 0) { cout << \"connect fail\" << endl; ret = false; goto END; } *count = 1; // 获取服务列表 m = read(sockfd, inbuffer, sizeof(inbuffer) - 1); if (m > 0) { inbuffer[m] = 0; cout << \"server list : \" << inbuffer << endl; } else { ret = false; goto END; } // 选择服务 cout << \"Please Select Server# \"; getline(cin, buffer); if(buffer == \"quit\") { ret = true; goto END; } n = write(sockfd, buffer.c_str(), buffer.size()); // 向服务端发送信息 cout << \"Enter> \"; getline(cin, buffer); n = write(sockfd, buffer.c_str(), buffer.size()); if (n > 0) { m = read(sockfd, inbuffer, sizeof(inbuffer) - 1); if (m > 0) { inbuffer[m] = 0; cout << inbuffer << endl; } else { ret = false; goto END; } } else { ret = false; goto END; }END: close(sockfd); return ret;}int main(int argc, char *argv[]){ if (argc != 3) { Usage(argv[0]); exit(1); } string serverip = argv[1]; uint16_t serverport = stoi(argv[2]); // 断线重连 int count = 1; while(count <= Retry_Count) { if(VisitServer(serverip,serverport,&count)) { break; } else { sleep(1); cout << \"server offline , retrying... , count : \" << count << endl; count++; } } if(count >= Retry_Count) cout << \"server offline , retrying... , count : \" << count << endl; return 0;}

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

【Linux | 网络】socket编程 - 使用TCP实现服务端向客户端提供简单的服务