⭐小白苦学IT的博客主页
⭐初学者必看:Linux操作系统入门
⭐代码仓库:Linux代码仓库
❤关注我一起讨论和学习Linux系统
TCP单例模式的多线程版本的英汉互译服务器
我们先来认识一下与udp服务器实现的不同的接口:
TCP服务器端
socket():创建一个新的套接字,指定使用的协议族(如IPv4)、套接字类型(如SOCK_STREAM表示TCP)和协议(通常为0,表示使用默认协议)。
bind():将套接字绑定到一个特定的地址和端口号上,这样客户端就可以通过这个地址和端口号连接到服务器。
listen():使套接字进入监听状态,等待客户端的连接请求。可以指定最大连接队列长度。
accept():接受一个客户端的连接请求,并返回一个新的套接字,用于与这个客户端进行通信。原始的套接字继续用于监听其他客户端的连接请求。
read() :从已连接的客户端套接字读取数据。TCP是字节流协议,因此你需要按照某种协议或方式来分割和解析接收到的数据。
write():向已连接的客户端套接字发送数据。
TCP客户端
socket():同样创建一个新的套接字。
connect():发起一个到服务器地址和端口号的连接请求。
read() :从服务器套接字读取数据。
write():向服务器套接字发送数据。
封装TcpSocket
TcpServer.hpp
#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include <cstring>
#include<pthread.h>
#include"ThreadPool.hpp"
#include "task.hpp"const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 5;enum
{UsageError = 1,SocketError,BindError,ListenError,
};class TcpServer;class ThreadData1
{
public:ThreadData1(int fd,const std::string & ip,const uint16_t & port,TcpServer* t):sockfd(fd),clientip(ip),clientport(port),tsvr(t){}public:int sockfd;std::string clientip;uint16_t clientport;TcpServer* tsvr;
};class TcpServer
{public:TcpServer(const uint16_t port, const std::string &ip = defaultip): _listensock(-1), _port(port), _ip(ip){}void InitServer(){_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){log.LogMessage(FATAL, "create socket error , errno:%d, strerror: %s", errno, strerror(errno));exit(SocketError);}log.LogMessage(INFO, "create socket success ,_listensock:%d", _listensock);struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);inet_aton(_ip.c_str(), &(local.sin_addr));if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0){log.LogMessage(FATAL, "bind error , errno:%d, strerror: %s", errno, strerror(errno));exit(BindError);}log.LogMessage(INFO, "bind socket success ,_listensock:%d", _listensock);// Tcp是面向连接的,所以服务器一般是比较“被动”的,服务器一种处于一种if (listen(_listensock, backlog) < 0){log.LogMessage(FATAL, "listen error , errno:%d, strerror: %s", errno, strerror(errno));exit(ListenError);}log.LogMessage(INFO, "listen success ,_listensock:%d", _listensock);}void Start(){ThreadPool<Task>::GetInstance()->Start();log.LogMessage(INFO, "tcpServer is running ...");for (;;){// 1.获取新链接struct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = accept(_listensock, (struct sockaddr *)&client, &len);if (sockfd < 0){log.LogMessage(WARNING, "accept error , errno:%d, strerror: %s", errno, strerror(errno));continue;}uint16_t clientport = ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));// 2.根据新连接来进行通信log.LogMessage(INFO, "get a new link ... client ip : %s, client port : %d , sockfd:%d", clientip, clientport, sockfd);//version4 线程池版本Task t(sockfd,clientport,clientip);ThreadPool<Task>::GetInstance()->Push(t);}}~TcpServer() {}private:int _listensock;uint16_t _port;std::string _ip;
};
ThreadPool.hpp
#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>struct ThreadInfo
{pthread_t tid;std::string name;
};static const int defalutnum = 10;template <class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}bool IsQueueEmpty(){return tasks_.empty();}std::string GetThreadName(pthread_t tid){for (const auto &ti : threads_){if (ti.tid == tid)return ti.name;}return "None";}public:static void *HandlerTask(void *args){ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while (true){tp->Lock();while (tp->IsQueueEmpty()){tp->ThreadSleep();}T t = tp->Pop();tp->Unlock();t();}}void Start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name = "thread-" + std::to_string(i + 1);pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);}}T Pop(){T t = tasks_.front();tasks_.pop();return t;}void Push(const T &t){Lock();tasks_.push(t);Wakeup();Unlock();}static ThreadPool<T> *GetInstance(){if (nullptr == tp_) // ???{pthread_mutex_lock(&lock_);if (nullptr == tp_){std::cout << "log: singleton create done first!" << std::endl;tp_ = new ThreadPool<T>();}pthread_mutex_unlock(&lock_);}return tp_;}private:ThreadPool(int num = defalutnum) : threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}ThreadPool(const ThreadPool<T> &) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:std::vector<ThreadInfo> threads_;std::queue<T> tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;static ThreadPool<T> *tp_;static pthread_mutex_t lock_;
};template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;
Main.cc
#include"TcpServer.hpp"
#include<memory>
#include<iostream>void Usage(std::string proc)
{std::cout<<"\n\rUsage: "<<proc<<" port[1024+]"<<std::endl;
}int main(int argc,char* argv[])
{if(argc!=2){Usage(argv[0]);exit(UsageError);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tcp_svr(new TcpServer(port));tcp_svr->InitServer();tcp_svr->Start();return 0;
}
TcpClient.cc
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include<unistd.h>void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " serverip serverport[1024+]" << std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;return 1;}struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));server.sin_port = htons(serverport);// tcp要不要bind? 要bind 要不要显示的bind? 不用显示的bind 系统进行bind,随机端口// 客户端发起connect的时候,进行自动随机bind.int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){std::cerr << "connect error" << std::endl;return 2;}std::string message;std::cout<<"please Enter# ";std::getline(std::cin,message);write(sockfd,message.c_str(),message.size());char inbuffer[4096];int r = read(sockfd,inbuffer,sizeof(inbuffer));if(r>0){inbuffer[r] = 0;std::cout<<inbuffer<<std::endl;}close(sockfd);std::cout<<"Connection closed by foreign host"<<std::endl;return 0;
}
Init.hpp
#pragma once#include<iostream>
#include<string>
#include<fstream>
#include<unordered_map>
#include "Log.hpp"const std::string dictname = "./translation.txt";
const std::string sep = ":";static bool Split(std::string & s,std::string *part1,std::string *part2)
{auto pos = s.find(sep);if(pos==std::string::npos){return false;}*part1 = s.substr(0,pos);*part2 = s.substr(pos+1);return true;
}class Init
{
public:Init(){std::ifstream in(dictname);if(!in.is_open()){log.LogMessage(FATAL,"ifstream open %s error",dictname.c_str());exit(1);}std::string line;while(std::getline(in,line)){std::string part1,part2;Split(line,&part1,&part2);dict.insert({part1,part2});}in.close();}std::string translation(const std::string &key){auto iter = dict.find(key);if(iter == dict.end()){return "UnKnow";}else return iter->second;}
private:std::unordered_map<std::string,std::string> dict;};
Log.hpp
#pragma once#include <iostream>
#include <cstdarg>
#include <ctime>
#include <string>
#include <unistd.h>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>enum
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};enum
{Screen = 10,Onefile,Classfile
};std::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 "Unknown";}
}const int defaultstyle = Screen;
const std::string default_filename = "log.";
const std::string logdir="log";class Log
{
public:Log():style(defaultstyle),filename(default_filename){mkdir(logdir.c_str(),0775);}void Enable(int sty){style = sty;}std::string TimestampToLocalTime(){time_t curr = time(nullptr);struct tm *currtime = localtime(&curr);char time_buffer[128];snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",currtime->tm_year + 1900, currtime->tm_mon, currtime->tm_mday, currtime->tm_hour,currtime->tm_min, currtime->tm_sec);return time_buffer;}void WriteLog(const std::string &levelstr, const std::string &message){switch (style){case Screen:std::cout << message<<std::endl;break;case Onefile:WriteLogToOnefile("all", message);break;case Classfile:WriteLogToClassfile(levelstr, message);break;default:break;}}void WriteLogToOnefile(const std::string &logname, const std::string &message){umask(0);int fd = open(logname.c_str(),O_CREAT | O_WRONLY | O_APPEND,0666);if(fd<0)return;write(fd,message.c_str(),message.size());close(fd);// std::ofstream out(logname);// if (!out.is_open())// return;// out.write(message.c_str(), message.size());// out.close();}void WriteLogToClassfile(const std::string &levelstr, const std::string &message){std::string logname = logdir;logname+="/";logname+=filename;logname += levelstr;WriteLogToOnefile(logname, message);}void LogMessage(int level, const char *format, ...) // 类c的日志接口{char rightbuffer[1024];va_list args;va_start(args, format);vsnprintf(rightbuffer, sizeof(rightbuffer), format, args);va_end(args);char leftbuffer[1024];std::string curtime = TimestampToLocalTime();std::string levelstr = LevelToString(level);std::string idstr = std::to_string(getpid());snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%s][%s]",levelstr.c_str(), curtime.c_str(), idstr.c_str());std::string logInfo = leftbuffer;logInfo += rightbuffer;WriteLog(levelstr, logInfo);}~Log() {}private:int style;std::string filename;
};Log log;class Conf
{
public:Conf(){log.Enable(Screen);}~Conf(){}
};Conf conf;
task.hpp
#pragma once
#include<string>
#include<iostream>
#include"Log.hpp"
#include"Init.hpp"Init init;class Task
{
public:Task(int sockfd, const uint16_t &clientport, const std::string &clientip):clientip_(clientip),clientport_(clientport),sockfd_(sockfd){}void Run(){char buffer[4096];// 测试代码ssize_t n = read(sockfd_, buffer, sizeof(buffer));if (n > 0){buffer[n] = 0;std::cout << "client key# " << buffer << std::endl;std::string echo_string = init.translation(buffer);write(sockfd_, echo_string.c_str(), echo_string.size());}else if (n == 0){log.LogMessage(INFO, "%s:%d quit,server close sockfd:%d", clientip_.c_str(), clientport_, sockfd_);}else{log.LogMessage(WARNING, "read error,sockfd:%d,clientip:%s ,clientport:%d ", sockfd_, clientip_.c_str(), clientport_);}close(sockfd_);}void operator()()//运算符重载实现仿函数{Run();}~Task() {}
private:int sockfd_;std::string clientip_;uint16_t clientport_;
};
translation.txt
# 英汉互译键值对
# English-Chinese Key-Value Pairs hello: 你好
world: 世界
goodbye: 再见
thank you: 谢谢
please: 请
welcome: 欢迎
happy: 快乐的
sad: 悲伤的
angry: 生气的
excited: 兴奋的 apple: 苹果
banana: 香蕉
orange: 橙子
grape: 葡萄
peach: 桃子
watermelon: 西瓜
strawberry: 草莓
cherry: 樱桃 morning: 早上
afternoon: 下午
evening: 晚上
night: 夜晚
weekday: 工作日
weekend: 周末
January: 一月
February: 二月
March: 三月
April: 四月
只是写入了部分单词,比较简单,想要实现的更完整可以自行加入一些单词与中文意思及其解释等内容可以让该词典内容更丰富。
运行结果:
服务器一直在运行,客户端访问一次就退出了。