【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 <pthread.h>
#include <iostream>
#include <string>
#include <stdarg.h>
#include <stdio.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>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开始的所以加1snprintf(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 <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string>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 <iostream>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 <iostream>
#include <string>
#include <functional>
#include <pthread.h>using namespace std;// typedef function<void()> 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 <string>
#include <queue>
#include <vector>
#include <pthread.h>
#include <functional>
#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 <unordered_map>
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <string.h>
#include <error.h>#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 <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>const char* root = "/";
const char* dev_null = "/dev/null";bool Daemon(bool nochdir, bool noclose)
{// 1、忽略可能引起程序异常退出的信号 SIGCHLD SIGPIPEsignal(SIGCHLD,SIG_IGN);signal(SIGPIPE,SIG_IGN);// 2、创建子进程,让父进程退出,使得子进程不成为组长pid_t pid = fork();if(pid > 0) exit(0);// 3、设置自己成为一个新的会画,setsidsetsid();// 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 <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <functional>#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版本Servicevoid 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 ThreadDateclose(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 <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <functional>
#include <unordered_map>#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版本Servicevoid 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 <iostream>
#include <string>
#include <memory>
#include <algorithm>
#include <cctype>
#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 <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#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;
}
结尾
如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹