V1版本 - Echo server
UdpServer.hpp
cpp
复制代码
#pragma once
#include <functional>
#include <iostream>
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
using namespace LogModule;
using func_t = std::function<std::string(const std::string&)>;
const int defaultfd = -1;
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _port(port), _sockfd(defaultfd), _is_running(false), _func(func)
{
}
void Init()
{
// 创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "create socket failed";
return;
}
LOG(LogLevel::INFO) << "create socket success, sockfd: " << _sockfd;
// 2. 绑定socket信息,ip和端口
// 2.1 填充socdaddr_in结构体
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
// 2.1.1 转换ip地址为网络字节序
// local.sin_addr.s_addr = inet_addr(_ip.c_str());
// 2.1.1 绑定到任何一个本地地址
local.sin_addr.s_addr = INADDR_ANY;
// 2.1.2 转换端口为网络字节序
local.sin_port = htons(_port);
// 2.2 绑定socket
// 为什么服务器端需要显示的bind呢?IP和端口必须是众所周知且不能轻易改变的。
int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
if(n < 0)
{
LOG(LogLevel::FATAL) << "bind socket failed";
exit(2);
}
LOG(LogLevel::INFO) << "bind socket success" << " port: " << _port;
}
void Start()
{
_is_running = true;
// 大部分的软件都是死循环
while(_is_running)
{
char buffer[1024] = {0};
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// 1. 接收数据
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
if(s > 0)
{
int peer_port = ntohs(peer.sin_port); // 从网络中拿到的
std::string peer_ip = inet_ntoa(peer.sin_addr); // 4字节网络风格 -> 点分十进制的字符串风格的IP
buffer[s] = 0;
std::string result = _func(buffer);
LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port << "]# " << buffer; // 1. 消息内容 2. 谁发的?
// std::string echo_string = "server echo@ ";
// echo_string += buffer;
// 2. 发送数据
sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
}
}
}
~UdpServer()
{
}
private:
int _sockfd;
uint16_t _port;
// std::string _ip;
bool _is_running;
func_t _func; // 服务器的回调函数,对数据进行处理
};
cpp
复制代码
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
std::string defaulthandler(const std::string &message)
{
std::string hello = "hello, ";
hello += message;
return hello;
}
// 翻译系统,字符串当成英文单词
// ./udpserver ip port
int main(int argc, char* argv[])
{
if(argc != 2)
{
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
return 1;
}
// std::string ip = argv[1];
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, defaulthandler);
usvr->Init();
usvr->Start();
return 0;
}
cpp
复制代码
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <cstring>
// client 要访问目标服务器,需要知道什么?
// 目标服务器的IP地址和端口号
// 但是我怎么知道服务器的ip和端口?
// 客服端和服务器是一家公司写的,客户端内置了服务器的IP地址和端口号,所以客户端可以直接访问服务器。
// ./udpserver ip port
int main(int argc, char* argv[])
{
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
// 1. 创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
std::cerr << "Create socket failed." << std::endl;
return 2;
}
// 2. 本地的ip和端口是什么?要不要和上面的"文件"关联?
// 问题:client要不要bind?需要bind
// client要不要显示的bind?首次发送消息,os会自动给client进行bind,os知道ip,端口号采用随机端口号的方式
// 为什么?一个端口号只能被一个进程bind,为了避免client端口冲突,os会自动给client分配一个随机端口号
// client端的端口号是几,不重要,只要是唯一的就行
// 填写服务端信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
while(true)
{
std::string input;
std::cout << "Please Enter# ";
std::getline(std::cin, input);
int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&server, sizeof(server));
if(n < 0)
{
std::cerr << "Send failed." << std::endl;
return 3;
}
char buffer[1024] = {0};
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
if(m < 0)
{
std::cerr << "Recv failed." << std::endl;
return 4;
}
buffer[m] = '\0';
std::cout << buffer << std::endl;
}
return 0;
}
V2 版本 - DictServer
dictionary.txt
cpp
复制代码
book: 书
pen: 钢笔
apple: 苹果
cat: 猫
dog: 狗
happy: 快乐的
sad: 悲伤的
love: 爱
friend: 朋友
family: 家庭
english: 英语
chinese: 中文
Dict.hpp
cpp
复制代码
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>
#include "Log.hpp"
#include "InerAddr.hpp"
using namespace LogModule;
const std::string defaultdict = "./dictionary.txt";
const std::string sep = ": ";
class Dict
{
public:
Dict(const std::string &path = defaultdict) :_dict_path(path)
{}
bool LoadDict()
{
std::ifstream in(_dict_path);
if (!in.is_open())
{
LOG(LogLevel::ERROR) << "字典文件打开失败 " << _dict_path;
return false;
}
std::string line;
while (std::getline(in, line))
{
auto pos = line.find(sep);
if(pos == std::string::npos)
{
LOG(LogLevel::WARNING) << "解析: " << line << "失败";
continue;
}
std::string english = line.substr(0, pos);
std::string chinese = line.substr(pos + sep.size());
_dict[english] = chinese;
}
return true;
}
std::string Translate(const std::string& word, const InerAddr& client)
{
auto it = _dict.find(word);
if(it == _dict.end())
{
return "None";
}
LOG(LogLevel::DEBUG) << "进入了翻译模块, " << client.Ip() << " : " << client.Port() << " : " << word << " -> " << it->second;
return it->second;
}
~Dict()
{
}
private:
std::string _dict_path;
std::unordered_map<std::string, std::string> _dict;
};
InetAddr.hpp
cpp
复制代码
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class InerAddr
{
public:
InerAddr(struct sockaddr_in &addr) : _addr(addr)
{
_port = ntohs(addr.sin_port); // 从网络中拿到的
_ip = inet_ntoa(addr.sin_addr); // 4字节网络风格 -> 点分十进制的字符串风格的IP
}
std::string Ip() const { return _ip; }
uint16_t Port() const { return _port; }
~InerAddr()
{
}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
UdpServer.hpp
cpp
复制代码
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InerAddr.hpp"
using func_t = std::function<std::string(const std::string&, const InerAddr&)>;
const int defaultfd = -1;
@@ -72,13 +72,12 @@ public:
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
if(s > 0)
{
InerAddr client(peer);
buffer[s] = 0;
std::string result = _func(buffer, client);
// LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port << "]# " << buffer; // 1. 消息内容 2. 谁发的?
// std::string echo_string = "server echo@ ";
// echo_string += buffer;
// 2. 发送数据
sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
}
}
}
~UdpServer()
{
}
private:
int _sockfd;
uint16_t _port;
// std::string _ip;
bool _is_running;
func_t _func; // 服务器的回调函数,对数据进行处理
};
cpp
复制代码
#include <iostream>
#include <memory>
#include "Dict.hpp"
#include "UdpServer.hpp"
std::string defaulthandler(const std::string &message)
{
std::string hello = "hello, ";
hello += message;
return hello;
}
// 翻译系统,字符串当成英文单词
// ./udpserver ip port
int main(int argc, char* argv[])
{
if(argc != 2)
{
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
return 1;
}
// std::string ip = argv[1];
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
// 1. 字典对象提供翻译功能
Dict dict;
dict.LoadDict();
// 2. 网络服务器对象提供通信功能
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string &word, const InerAddr& cli) -> std::string {
return dict.Translate(word, cli);
});
usvr->Init();
usvr->Start();
return 0;
}
V3版本 - 简单聊天室
Route.hpp
cpp
复制代码
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "InerAddr.hpp"
#include "Log.hpp"
#include "Mutex.hpp"
using namespace LogModule;
using namespace MutexModule;
class Route
{
private:
bool IsExist(const InerAddr &peer)
{
for (auto &user : _online_user)
{
if (user == peer)
return true;
}
return false;
}
void AddUser(const InerAddr &peer)
{
LOG(LogLevel::INFO) << "新增一个在线用户:" << peer.StringAddr();
_online_user.push_back(peer);
}
void DeleteUser(InerAddr &peer)
{
for (auto iter = _online_user.begin(); iter != _online_user.end(); ++iter)
{
if (*iter == peer)
{
_online_user.erase(iter);
break;
}
}
}
public:
Route()
{
}
void MessageRoute(int sockfd, const std::string &message, InerAddr &peer)
{
LockGuard lock(_mutex);
if (!IsExist(peer))
{
AddUser(peer);
}
std::string send_message = peer.StringAddr() + "# " + message;
for (auto &user : _online_user)
{
sendto(sockfd, send_message.c_str(), send_message.size(), 0, (const struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr()));
}
// 这个用户已经在线
if (message == "QUIT")
{
LOG(LogLevel::INFO) << "用户" << peer.StringAddr() << "下线";
DeleteUser(peer);
}
}
~Route()
{
}
private:
// 首次给我发消息,等同于登录
std::vector<InerAddr> _online_user;
Mutex _mutex;
};
UdpServer.hpp
cpp
复制代码
#pragma once
#include <functional>
#include <iostream>
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InerAddr.hpp"
#include "ThreadPool.hpp"
using namespace ThreadPoolModule;
using func_t = std::function<void(int sockfd, const std::string&, InerAddr&)>;
const int defaultfd = -1;
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _port(port), _sockfd(defaultfd), _is_running(false), _func(func)
{
}
void Init()
{
// 创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "create socket failed";
return;
}
LOG(LogLevel::INFO) << "create socket success, sockfd: " << _sockfd;
// 2. 绑定socket信息,ip和端口
// 2.1 填充socdaddr_in结构体
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
// 2.1.1 转换ip地址为网络字节序
// local.sin_addr.s_addr = inet_addr(_ip.c_str());
// 2.1.1 绑定到任何一个本地地址
local.sin_addr.s_addr = INADDR_ANY;
// 2.1.2 转换端口为网络字节序
local.sin_port = htons(_port);
// InerAddr addr("0", _port);
// addr.NetAddr();
// 2.2 绑定socket
// 为什么服务器端需要显示的bind呢?IP和端口必须是众所周知且不能轻易改变的。
int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
if(n < 0)
{
LOG(LogLevel::FATAL) << "bind socket failed";
exit(2);
}
LOG(LogLevel::INFO) << "bind socket success" << " port: " << _port;
}
void Start()
{
_is_running = true;
// 大部分的软件都是死循环
while(_is_running)
{
char buffer[1024] = {0};
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// 1. 接收数据
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
if(s > 0)
{
InerAddr client(peer);
buffer[s] = 0;
_func(_sockfd, buffer, client);
// LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port << "]# " << buffer; // 1. 消息内容 2. 谁发的?
// std::string echo_string = "server echo@ ";
// echo_string += buffer;
// 2. 发送数据
// sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
}
}
}
~UdpServer()
{
}
private:
int _sockfd;
uint16_t _port;
// std::string _ip;
bool _is_running;
func_t _func; // 服务器的回调函数,对数据进行处理
};
InetAddr.hpp
cpp
复制代码
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class InerAddr
{
public:
InerAddr(struct sockaddr_in &addr) : _addr(addr)
{
_port = ntohs(addr.sin_port); // 从网络中拿到的
// _ip = inet_ntoa(addr.sin_addr); // 4字节网络风格 -> 点分十进制的字符串风格的IP
char buffer[64];
inet_ntop(AF_INET, &addr.sin_addr, buffer, sizeof(buffer));
_ip = buffer;
}
InerAddr(const std::string &ip, uint16_t port): _ip(ip), _port(port)
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
_addr.sin_port = htons(_port);
}
std::string Ip() const { return _ip; }
uint16_t Port() const { return _port; }
const struct sockaddr_in &NetAddr() { return _addr; }
bool operator==(const InerAddr& addr) const
{
return addr._ip == _ip && addr._port == _port;
}
std::string StringAddr() const
{
return _ip + ":" + std::to_string(_port);
}
~InerAddr()
{
}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
cpp
复制代码
#include <iostream>
#include <memory>
#include "Dict.hpp"
#include "UdpServer.hpp"
#include "Route.hpp"
std::string defaulthandler(const std::string &message)
{
std::string hello = "hello, ";
hello += message;
return hello;
}
// 翻译系统,字符串当成英文单词
using task_t = std::function<void()>;
// ./udpserver ip port
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
return 1;
}
// std::string ip = argv[1];
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
// 1. 路由服务
Route r;
// 2. 线程池
auto tp = ThreadPool<task_t>::GetIntance();
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r, &tp](int sockfd, const std::string &message, InerAddr& peer) {
auto t = std::bind(&Route::MessageRoute, &r, sockfd, message, peer);
tp->Enqueue(t);
});
// 3. 网络服务器对象提供通信功能
// std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r](int sockfd, const std::string &message, InerAddr& peer){
// r.MessageRoute(sockfd, message, peer);
// });
usvr->Init();
usvr->Start();
return 0;
}
cpp
复制代码
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <cstring>
#include "Thread.hpp"
// client 要访问目标服务器,需要知道什么?
// 目标服务器的IP地址和端口号
// 但是我怎么知道服务器的ip和端口?
// 客服端和服务器是一家公司写的,客户端内置了服务器的IP地址和端口号,所以客户端可以直接访问服务器。
int sockfd = 0;
std::string server_ip;
uint16_t server_port = 0;
pthread_t id = 0;
using namespace ThreadModlue;
void Recv()
{
while (true)
{
char buffer[1024] = {0};
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (m < 0)
{
std::cerr << "Recv failed." << std::endl;
break;
}
buffer[m] = '\0';
std::cerr << buffer << std::endl;
}
}
void Send()
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
const std::string online = "inline";
sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));
while (true)
{
std::string input;
std::cout << "Please Enter# ";
std::getline(std::cin, input);
int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
if (n < 0)
{
std::cerr << "Send failed." << std::endl;
break;
}
if (input == "QUIT")
{
pthread_cancel(id);
break;
}
}
}
// ./udpserver ip port
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
server_ip = argv[1];
server_port = std::stoi(argv[2]);
// 1. 创建套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "Create socket failed." << std::endl;
return 2;
}
// 创建线程
Thread recver(Recv);
Thread sender(Send);
recver.Start();
sender.Start();
id = recver.Id();
recver.Join();
sender.Join();
// 2. 本地的ip和端口是什么?要不要和上面的"文件"关联?
// 问题:client要不要bind?需要bind
// client要不要显示的bind?首次发送消息,os会自动给client进行bind,os知道ip,端口号采用随机端口号的方式
// 为什么?一个端口号只能被一个进程bind,为了避免client端口冲突,os会自动给client分配一个随机端口号
// client端的端口号是几,不重要,只要是唯一的就行
// 填写服务端信息
return 0;
}