🔥个人主页: Milestone-里程碑
❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>
🌟心向往之行必能至
上一篇文章,我们实现了基础的TCP的封装实现,本篇文章,我们将与前面的UDP一样,实现英文查中文的操作
一.引入Dict.hpp
bash
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>
#include "Log.hpp"
#include"InetAddr.hpp"
using namespace LogModule;
const std::string gap = ":";
const std::string defaultdictpath = "./word.txt";
class Dict
{
public:
Dict() : _dict_path(defaultdictpath)
{
}
bool LoadDict()
{
std::ifstream in(_dict_path);
if (!in.is_open())
{
LOG(LogLevel::DEBUG) << "打开字典: " << _dict_path << " 错误";
return false;
}
std::string line;
while (std::getline(in, line))
{
auto pos = line.find(gap);
if (pos == std::string ::npos)
{
LOG(LogLevel::WARNING) << "解析: " << line << " 失败";
continue;
}
// 左闭右开
std::string word = line.substr(0, pos);
std::string chinaese = line.substr(pos + gap.size());
if (word.empty() || chinaese.empty())
{
LOG(LogLevel::WARNING) << "没有有效数据 " << line;
continue;
}
_dict.insert(std::make_pair(word, chinaese));
LOG(LogLevel::DEBUG) << "加载: " << line;
}
in.close();
return true;
}
std::string Translate(const std::string &word, InetAddr & client)
{
auto iter = _dict.find(word);
if (iter == _dict.end())
{
// LOG(LogLevel::DEBUG) << "word ";
LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.IP() << " : " << client.PORT() << "]# " << word << "->None";
return "None";
}
return iter->second;
}
~Dict() {}
private:
std::string _dict_path; // 默认路径
std::unordered_map<std::string, std::string> _dict;
};
二 TcpClient.cc
bash
#include <iostream>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
int main(int args, char *argv[])
{
if (args != 3)
{
LOG(LogLevel::ERROR) << "USAG" << argv[0] << " ip port";
exit(usgv_err);
}
std::string ip = argv[1];
uint16_t port = std::stoi(argv[2]);
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
LOG(LogLevel::ERROR) << "sockfd error";
exit(socket_err);
}
LOG(LogLevel::DEBUG) << "sockfd success";
// 无需显示绑定,系统随随机绑定端口号
// 2. 我应该做什么呢?listen?accept?都不需要!!
// 2. 直接向目标服务器发起建立连接的请求
InetAddr serveraddr(ip, port);
int n = connect(sockfd, serveraddr.NetAddrPtr(), serveraddr.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::ERROR) << "connect fail";
exit(connect_err);
}
LOG(LogLevel::DEBUG) << "connect success";
while (true)
{
// 1.写P
LOG(LogLevel::DEBUG) << "Please input#";
std::string line;
std::getline(std::cin, line);
ssize_t n = write(sockfd, line.c_str(), line.size());
// 2.读
char buf[1024];
n = read(sockfd, buf, sizeof(buf) - 1);
if (n > 0)
{
buf[n] = 0;
LOG(LogLevel::INFO) << "server echo#" << buf;
}
}
return 0;
}
三.TcpServer.cc
bash
#include"TcpServer.hpp"
#include<iostream>
int main(int args,char*argv[])
{
if(args!=2)
{
std::cout<<"USGR: "<<argv[0]<<"port";
exit(usgv_err);
}
uint16_t port=std::stoi(argv[1]);
std::unique_ptr<TcpServer> tcp=std::make_unique<TcpServer>(port);
tcp->Init();
tcp->Start();
return 0;
}
四.TcpServer.hpp
bash
#pragma once
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <pthread.h>
#include<functional>
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
#include"ThreadPool.hpp"
using namespace LogModule;
using namespace ThreadPoolModule;
const int defaultsocket = -1;
using task_t=std::function<void()>;
class TcpServer : public NoCopy // 服务器不可拷贝
{
public:
TcpServer(uint16_t &port)
: _listensocket(defaultsocket), _port(port), _isrunning(false)
{
}
void Init()
{
// signal(SIGCHLD, SIG_IGN); // 忽略SIG_IGN信号
// 1.创建套接字
_listensocket = socket(AF_INET, SOCK_STREAM, 0);
if (_listensocket < 0)
{
LOG(LogLevel::ERROR) << "socket fail";
exit(socket_err);
}
LOG(LogLevel::INFO) << "socket success" << _listensocket;
// 2. bind
InetAddr local(_port);
int n = bind(_listensocket, local.NetAddrPtr(), local.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::ERROR) << "bind fail" << _listensocket;
exit(bind_err);
}
LOG(LogLevel::INFO) << "bind success" << _listensocket;
// 进行监听
int backlog = 5;
n = listen(_listensocket, backlog);
if (n < 0)
{
LOG(LogLevel::ERROR) << "listen fail" << _listensocket;
exit(listen_err);
}
LOG(LogLevel::INFO) << "listen success" << _listensocket;
}
class ThreadData
{
public:
ThreadData(int fd, InetAddr &ar, TcpServer *s) : sockfd(fd), addr(ar), tsvr(s) {}
public:
int sockfd;
InetAddr addr;
TcpServer *tsvr;
};
static void *Rotinue(void *argv)
{
ThreadData *td = static_cast<ThreadData *>(argv);
// 脱离主线程
pthread_detach(pthread_self());
td->tsvr->Service(td->sockfd, td->addr);
delete td;
return nullptr;
}
// 短服务
// 长服务: 多进程多线程比较合适
void Service(int sockfd, InetAddr &peer)
{
// 1. 先读取数据
// a. n>0: 读取成功
// b. n<0: 读取失败
// c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
char buf[1024];
while (true)
{
ssize_t n = read(sockfd, buf, sizeof(buf) - 1);
if (n < 0)
{
LOG(LogLevel::ERROR) << "read fail";
close(sockfd);
exit(read_err);
}
else if (n == 0)
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << "exit ";
close(sockfd);
break;
}
else
{
buf[n] = 0;
LOG(LogLevel::INFO) << peer.StringAddr() << " #" << buf;
std::string echo_string = "echo# ";
write(sockfd, echo_string.c_str(), echo_string.size());
}
}
}
void Start()
{
_isrunning = true;
while (_isrunning)
{
// a. 获取链接
struct sockaddr_in peer;
socklen_t len = sizeof(sockaddr_in);
// 如果没有连接,accept就会阻塞
int sockfd = accept(_listensocket, CONV(peer), &len);
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
InetAddr addr(peer);
LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();
// version0 -- text version 单线程版本
// Service(sockfd, addr);
// version1 -- 多线程版本
// pid_t id = fork();
// if (id < 0)
// {
// LOG(LogLevel::ERROR) << "fork fail";
// exit(fork_err);
// }
// // 父与子都关闭不需要的端口
// else if (id == 0)
// {
// close(_listensocket);
// if(fork()>0)
// exit(OK);
// //孙子进程
// Service(sockfd, addr);
// exit(OK);
// }
// else
// {
// close(sockfd);
// pid_t rid = waitpid(id, nullptr, 0);
// (void)rid;
// //父进程要等待子进程,要不然僵尸,但等待与之前的一样,等于没处理
// //解决方法:
// //1. 设置为非阻塞状态,不推荐
// //2. 自定义信号(不够优雅)
// //3. 利用子进程创建一个孙进程,让子进程退出,孙进程就成了孤儿进程,由系统领养
// }
// version 2 ---多线程版本
// ThreadData *td = new ThreadData(sockfd, addr, this);
// pthread_t tid;
// pthread_create(&tid, nullptr, Rotinue, td);
//version 3 --线程池版本,一般适合处理短服务
// 将新链接和客户端构建一个新任务,push线程池中
ThreadPool<task_t>::GetInstance()->Enqueue([this,sockfd,&addr]
(){
this->Service(sockfd,addr);
});
}
_isrunning = false;
}
~TcpServer() {}
private:
int _listensocket; // 监听socket
uint16_t _port;
bool _isrunning;
};
五.总结
有前面造的轮子,此处增加这一个功能,基本上都是CV
CV的快乐