Socket编程UDP
目录
- 一、V1 版本-echo server
- 1、服务端的实现
- (1)创建套接字 socket
- (2)绑定 bind
- (i)sockaddr_in结构体
- (ii)结构体初始化--bzero
- (iii)结构体数据填充-协议家族
- (iv)结构体数据填充--大小端转化问题
- (v)设置进内核
- (3)启动服务器
- (4)IP的绑定
- (i)ifconfig
- (ii)netstat
- (5)读取数据 recvfrom
- (i)inet_ntoa函数
- (6)服务端整体代码
- 2、客户端实现
- (1)创建套接字
- (2)绑定问题
- (3)发送信息sendto
- 3、源码简易版
- (1)UdpServer.hpp
- (2)Server.cc
- (3)Client.cc
- 4、源码封装版
- (1)UdpServer.hpp
- (2)UdpServerMain.cc
- (3)UdpClient.hpp
- (4)UdpClientMain.cc
- (5)Mutex.hpp
- (6)Makefile
- (7)Log.hpp
- (8)InetAddr.hpp
- (9)Common.hpp
- 5、加餐--网络命令
- (1)Ping命令
- (2)pidof
- 二、V2 版本-DictServer
- 1、UdpServer.hpp
- 2、UdpServerMain.cc
- 3、UdpClient.hpp
- 4、UdpClientMain.cc
- 5、Mutex.hpp
- 6、Makefile
- 7、Log.hpp
- 8、InetAddr.hpp
- 9、Dictionary.hpp
- 10、Common.hpp
- 11、dict.txt
- 三、V3 版本-ChatServer
- 1、UdpServer.hpp
- 2、UdpServerMain.cc
- 3、UdpClient.hpp
- 4、UdpClientMain.cc
- 5、Mutex.hpp
- 6、ThreadPool.hpp
- 7、Thread.hpp
- 8、User.hpp
- 9、Makefile
- 10、Log.hpp
- 11、InetAddr.hpp
- 12、Cond.hpp
- 13、Common.hpp
一、V1 版本-echo server
1、服务端的实现
(1)创建套接字 socket
在通信之前要先把网卡文件打开,函数作用:打开一个文件,把文件和网卡关联起来。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数说明:
- domain:域,标识了这个套接字的通信类型(网络通信/本地通信),这个就是sockaddr结构体的前16个bit位。如果是本地通信就是AF_UNIX,如果是网络通信就是AF_INET。
- type:套接字提供的服务类型,常见的就是SOCK_STREAM和SOCK_DGRAM,如果我们是基于TCP协议的通信,就使用SOCK_STREAM,表示的是流式服务。如果我们是基于UDP协议通信的,就使用SOCK_DGRAM,表示的是数据包服务。
- protocol:创建套接字的类型(TCP/UDP),但是这个参数可以由前两个参数决定。所以通常设置为0
返回值:
成功会返回一个文件描述符,失败返回-1,错误码被设置
在系统的文件操作中,也会返回文件描述符,与socket返回的文件描述符不同的是,普通文件的文件缓冲区对应的是磁盘,而socket返回的文件描述符的文件缓冲区对应的是网卡。用户将数据写到缓冲区,由操作系统自动将缓冲区中的数据刷新到网卡中,网卡会负责将这个数据发送到对端主机上。
class UdpServer
{
public:
UdpServer()
{
// 创建套接字
_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (_socket < 0)
{
std::cerr << "create socket error" << std::endl;
exit(1);
}
std::cout << "create socket success, socket: " << _socket << std::endl;
}
~UdpServer()
{
close(_socket);
}
private:
int _socket;
};
调用socket函数,就能创建一个套接字了,第一个参数我们填AF_INET,表示的是我们是网络通信。第二个参数我们填SOCK_DGRAM,表示我们是UDP服务(数据报)。由于一个进程启动时,默认会打开标准输入,标准输出,标准错误这三个文件描述符,而文件描述符的创建规则就是从0开始,向上找到第一个没有使用的,所以我们可以猜测以下_sock的值为3.
(2)绑定 bind
我们创建完套接字以后,也只是打开了一个文件。还没有将这个文件和网络关联起来,所以我们需要使用bind函数,将IP+port和文件绑定
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数介绍:
- sockfd:socket函数的返回值。
- addr:通用结构体,包括协议家族,IP地址,端口号port
- addrlen:addr的长度
绑定前我们要先定义一个 sockaddr_in 结构体填充所需绑定的数据,再传递进去。
(i)sockaddr_in结构体
struct sockaddr_in
{
short int sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
- sin_family:协议家族,表示通信类型(AF_INET网络通信,PF_INET本地通信)这个family被藏在SOCKADDR_COMMON 里面
- sin_port:端口号(网络字节序)
- sin_addr:IP地址。
- sin_zero:填充字段,让sizeof(sockaddr_in) = 16
(ii)结构体初始化–bzero
创建结构体后要先清空数据(初始化),我们可以用 memset,系统也提供了接口:
struct sockaddr_in local;
bzero(&local, sizeof(local));
(iii)结构体数据填充-协议家族
变量sin_family协议家族位置:
我们需要填充的数据中有一个变量是sin_family,但是源码中并没直接见到,实际在__SOCKADDR_COMMON (sin_);中
这是一个冷门的用法:##,就是把双##两边的符号合并成一个新的符号
所以调用宏定义,把参数sin_传进去,sin_替换sa_prefix,sa_family_t sa_prefix##family就变成sa_family sin_family
数据填充: sin_family需要和socket套接字创建时的domain相同。
(iv)结构体数据填充–大小端转化问题
填充端口号:
填充端口号的时候要注意端口号是两个字节的数据,涉及到大小端问题。
换句话说就是由于端口号和IP将来是要发送到网络的,而网络数据流统一采用大端的形式,所以为了代码的可移植性,我们不管自己是大端还是小端,统一调用函数htons转化成大端。也就是把端口号从主机序列转化成网络序列(大端的形式)。
大小端转化接口:
#include <arpa/inet.h>
// 主机序列转网络序列
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
// 网络序列转主机序列
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
填充 IP 地址:
对于 IP,首先我们要先转成整数,再解决大小端问题。
换句话说就是对于IP来说,
第一步:如果直接发送的是点分十进制形式如(192.168.12.80)的形式,则需要占用过多的字节数,对网络传输无疑是一种很大的消耗,所以我们采用一个32位的整数来表示IP,也就是四个字节,在网络传输中,要将点分十进制的IP转化成整数,
第二步:把这四字节地址从主机序列转化成网络序列,进行传输。
系统给了直接能解决这两个问题的接口:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp); //点分十进制转整数
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in); //整数转点分十进制
struct in_addr inet_makeaddr(int net, int host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);
这里重点学习两个,inet_addr(点分十进制转整数),现在使用的函数和inet_ntoa(整数转点分十进制),后续(4)使用的函数。
但是这里使用有个小问题:
c语言里结构体可以被直接整体初始化,但不能被直接整体赋值,也就是说要一个个赋值,因此我们右键查看定义,找到其中变量:
此时不会报错。
(v)设置进内核
文件+网络结合,才得到了一个绑定信息,到这里才使用bind
int n = bind(_socket, (sockaddr *)&addr, sizeof(addr));
(3)启动服务器
作为一款网络服务器,是永远不退出的。
服务器启动-> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了
首先要知道服务器要死循环,永远不退出,除非用户删除。站在操作系统的角度,服务器是常驻内存中的进程,而我们启动服务器的时候要传递进去 IP 和端口号。
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
void Usage()
{
std::cout << "Please enter: ./UdpServer [ip] port" << std::endl;
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
Usage();
return 3;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[argc - 1]);
std::unique_ptr<UdpServer> p(new UdpServer(port, ip));
p->Start();
return 0;
}
我们可以使用命令行参数的形式,在启动这个程序时就把IP和端口号传递过去。如上
(4)IP的绑定
(i)ifconfig
我们先来了解一个命令,ifconfig,操作系统中用于配置和显示网络接口参数的命令行工具。它主要用于查看和设置网络接口(如以太网和 Wi-Fi)的详细信息,包括 IP 地址、子网掩码、广播地址、MAC 地址等。
这里的 127.0.0.1 叫做 本地环回。client 和 server 发送数据只在本地协议栈中进行数据流动,不会将我们的数据发送到网络中。
作用:用来做本地网络服务器代码测试的,意思就是如果我们绑定的 IP 是 127.0.0.1 的话,在应用层发送的消息不会进入物理层,也就不会发送出去。
(ii)netstat
当我们运行起来后想要查看网络情况就可以用指令 netstat,后边也可以附带参数:
-a:显示所有连线中的 Socket。
-e:显示网络其他相关信息。
-i:显示网络界面信息表单。
-l:显示监控中的服务器的 Socket。
-n:直接使用 ip 地址(数字),而不通过域名服务器。
-p:显示正在使用 Socket 的程序识别码和程序名称。
-t:显示 TCP 传输协议的连线状况。
-u:显示 UDP 传输协议的连线状况。
我们可以看到确实能看到我们刚刚运行的程序成功bind了一个IP和端口号。
但是在实际情况下,服务器时不建议绑定一个固定IP的。
- 安全性:攻击者可能会针对这个固定的IP地址进行攻击,比如DDoS攻击或者其他类型的网络攻击。相比之下,使用动态IP或者负载均衡等技术可以更好地分散攻击,提高系统的安全性。
- 可用性:在某些情况下,固定IP可能并不总是可用的。例如,在云服务环境中,IP地址可能会因为服务器的迁移或重新部署而发生变化。如果服务端绑定到这样的固定IP,当IP地址发生变化时,服务端可能无法正常工作,导致服务中断。
- 一个服务器可能会有多个IP(多张网卡),当客户端发送数据时,每张网卡都会收到该数据,如果我们想访问指定的某一个端口8080,并且如果指定了IP,那么只能由那一个指定的IP接受数据,但是如果我们绑定的是任意IP,那么只要是发送给8080端口的,任意一个网卡接受到了都会向上交付给服务器。
所以我们修改一下,如果创建服务端的时候,传了IP,那就用传的IP,如果没用,那就用我们INADDR_ANY其实就是(0.0.0.0),
INADDR_ANY 如上所示,任何地址都接受。
绑定之后,只要是发送到这台主机上,端口号为XXX(我这里是8080)的,就将数据全部交给这个进程。
addr.sin_addr.s_addr = str.empty() ? INADDR_ANY : inet_addr(str.c_str());
(5)读取数据 recvfrom
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
参数介绍:
- sockfd:从哪个套接字读。
- buf:数据放入的缓冲区。
- len:缓冲区长度。
- flags:读取方式。 0 代表阻塞式读取。
- src_addr 和 addrlen:输出型参数,返回对应的消息内容是从哪一个客户端发出的。第一个是自己定义的结构体,第二个是结构体长度。
void Start()
{
while (true)
{
char temp[1024];
sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
int n = recvfrom(_socket, temp, sizeof(temp) - 1, 0, (sockaddr *)&addr, &addrlen);
std::string ip = inet_ntoa(addr.sin_addr);
uint16_t port = ntohs(addr.sin_port);
if (n > 0)
{
printf("[%s:%d]# %s", ip.c_str(), port, temp);
}
}
}
(i)inet_ntoa函数
recvfrom里的参数 src_addr用来保存发送端的信息,我们在前面使用bind函数的时候,也介绍过sockaddr结构,里面包含了发送端的IP和端口port,但是因为是网络序列,我们需要将他转化回主机序列,ip是32位整数,我们想转化成点分十进制形式方便观看,于是可以使用inet_ntoa函数。
char *inet_ntoa(struct in_addr in);
inet_ntoa 这个函数返回了一个 char*,很显然是这个函数自己在内部为我们申请了一块内存来保存 ip 的结果。那么是否需要调用者手动释放呢?
man 手册上说,inet_ntoa 函数是把这个返回结果放到了静态存储区。这个时候不需要我们手动进行释放。
那如果我们调用多次这个函数,会有什么样的效果呢?
因为 inet_ntoa 把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果。
如果有多个线程调用 inet_ntoa,是否会出现异常情况呢?
- 在 APUE 中,明确提出 inet_ntoa 不是线程安全的函数。
- 但是在 centos7 上测试并没有出现问题,可能内部的实现加了互斥锁。
- 在多线程环境下,推荐使用 inet_ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题。
(6)服务端整体代码
#pragma once
#include <iostream>
#include <string>
#include <unordered_set>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class UdpServer
{
public:
UdpServer(const uint16_t &port, const std::string &str = "")
{
// 创建套接字
_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (_socket < 0)
{
std::cerr << "create socket error" << std::endl;
exit(1);
}
std::cout << "create socket success, socket: " << _socket << std::endl;
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = str.empty() ? INADDR_ANY : inet_addr(str.c_str());
int n = bind(_socket, (sockaddr *)&addr, sizeof(addr));
if (n < 0)
{
std::cout << "bind error" << std::endl;
exit(2);
}
std::cout << "bind success " << std::endl;
}
void Start()
{
while (true)
{
char temp[1024];
sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
int n = recvfrom(_socket, temp, sizeof(temp) - 1, 0, (sockaddr *)&addr, &addrlen);
std::string ip = inet_ntoa(addr.sin_addr);
uint16_t port = ntohs(addr.sin_port);
if (n > 0)
{
printf("[%s:%d]# %s", ip.c_str(), port, temp);
}
}
}
~UdpServer()
{
close(_socket);
}
private:
int _socket;
};
2、客户端实现
服务端的创建和客户端类似。我们也需要创建套接字
(1)创建套接字
void Usage()
{
std::cout << "Please enter: ./Client [ip] port" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage();
return 3;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[argc - 1]);
// 创建套接字
int clientsocket = socket(AF_INET, SOCK_DGRAM, 0);
if (clientsocket < 0)
{
std::cerr << "create socket error" << std::endl;
exit(1);
}
std::cout << "create socket success, socket: " << clientsocket << std::endl;
return 0;
}
(2)绑定问题
客户端必须绑定IP和端口,但是不用显示的bind。
这句话是什么意思呢,就是我们需要将网卡文件和IP+端口进行绑定,但是不需要我们自己手动绑定,操作系统会自动帮我们绑定一个端口号。
为什么不能显示绑定呢,首先我们要知道,一个端口只能对应一个进程,现在有两家公司,各推出了一款客户端,如果他们想让端口不冲突,那就不能让自己家的客户端绑定的端口和对方的一样,如果一样就会起冲突,但是互联网上有很多家公司,难道都要协商一下,哪个端口谁来用吗。所以我们采用了让操作系统帮我们自动分配一个没有使用的端口号。
当我们调用了类似与sendto(发送信息)的函数的时候,操作系统会帮我们自动分配一个端口号,也就是说,客户端每次启动时的端口号可能都不相同。
服务端的端口号为什么是固定的?
服务端的端口号是众所周知的,如果每次都不一样的话,客户端就找不到服务器了
(3)发送信息sendto
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数介绍:
- sockfd:从哪个套接字中读取
- buf:将缓冲区中的数据发给对端
- len:要发送多少个字节
- flags:写入方式,0表示阻塞写入
- src_addr:对端主机的信息(包括协议家族,IP,port)
- addrlen:表示src_addr的长度
参数和recvfrom类似
// 直接给服务器send,系统会自动帮我们bind
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
while (true)
{
std::string buffer;
std::cout << "Please enter# ";
std::getline(std::cin, buffer);
sendto(clientsocket, buffer.c_str(), buffer.size(), 0, (sockaddr *)&addr, sizeof(addr));
}
当我们将客户端发送消息的逻辑 完成之后就可以正常开始通信了。
3、源码简易版
(1)UdpServer.hpp
//Server.hpp
#pragma once
#include <iostream>
#include <string>
#include <unordered_set>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class UdpServer
{
public:
UdpServer(const uint16_t &port, const std::string &str = "")
{
// 创建套接字
_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (_socket < 0)
{
std::cerr << "create socket error" << std::endl;
exit(1);
}
std::cout << "create socket success, socket: " << _socket << std::endl;
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = str.empty() ? INADDR_ANY : inet_addr(str.c_str());
int n = bind(_socket, (sockaddr *)&addr, sizeof(addr));
if (n < 0)
{
std::cout << "bind error" << std::endl;
exit(2);
}
std::cout << "bind success " << std::endl;
}
void Start()
{
while (true)
{
char temp[1024];
sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
int n = recvfrom(_socket, temp, sizeof(temp) - 1, 0, (sockaddr *)&addr, &addrlen);
std::string ip = inet_ntoa(addr.sin_addr);
uint16_t port = ntohs(addr.sin_port);
if (n > 0)
{
temp[n] = 0;
printf("[%s:%d]# %s\n", ip.c_str(), port, temp);
}
}
}
~UdpServer()
{
close(_socket);
}
private:
int _socket;
};
(2)Server.cc
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
void Usage()
{
std::cout << "Please enter: ./UdpServer [ip] port" << std::endl;
}
int main(int argc, char* argv[])
{
if (argc != 3 && argc != 2)
{
Usage();
return 3;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[argc - 1]);
if (argc == 2)
ip = "";
std::unique_ptr<UdpServer> p(new UdpServer(port, ip));
p->Start();
return 0;
}
(3)Client.cc
#include <iostream>
#include <memory>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void Usage()
{
std::cout << "Please enter: ./Client [ip] port" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage();
return 3;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[argc - 1]);
// 创建套接字
int clientsocket = socket(AF_INET, SOCK_DGRAM, 0);
if (clientsocket < 0)
{
std::cerr << "create socket error" << std::endl;
exit(1);
}
std::cout << "create socket success, socket: " << clientsocket << std::endl;
// 直接给服务器send,系统会自动帮我们bind
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
while (true)
{
std::string buffer;
std::cout << "Please enter# ";
std::getline(std::cin, buffer);
sendto(clientsocket, buffer.c_str(), buffer.size(), 0, (sockaddr *)&addr, sizeof(addr));
}
return 0;
}
4、源码封装版
对相关代码封装,增加了日志
(1)UdpServer.hpp
#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__
#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"
using namespace LogModule;
const static int gsockfd = -1;
// const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机
const static uint16_t gdefaultport = 8080;
class UdpServer
{
public:
UdpServer(uint16_t port = gdefaultport)
: _sockfd(gsockfd),
_addr(port),
_isrunning(false)
{
}
// 都是套路
void InitServer()
{
// 1. 创建socket
_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地?
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
Die(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;
// 2. 填充网络信息,并bind绑定
// 2.1 有没有把socket信息,设置进入内核中??没有,只是填充了结构体!
// struct sockaddr_in local;
// bzero(&local, sizeof(local));
// local.sin_family = AF_INET;
// local.sin_port = ::htons(_port); // 要被发送给对方的,即要发到网络中!
// //local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1. string ip->4bytes 2. network order //TODO
//local.sin_addr.s_addr = INADDR_ANY;
// 2.2 bind : 设置进入内核中
int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind: " << strerror(errno);
Die(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success";
}
void Start()
{
_isrunning = true;
while (true)
{
char inbuffer[1024]; // string
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 必须设定
ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &len);
if (n > 0)
{
// 1. 消息内容 && 2. 谁发给我的
// uint16_t clientport = ::ntohs(peer.sin_port);
// std::string clientip = ::inet_ntoa(peer.sin_addr);
InetAddr cli(peer);
inbuffer[n] = 0;
std::string clientinfo = cli.Ip() + ":" + std::to_string(cli.Port()) + " # " + inbuffer;
LOG(LogLevel::DEBUG) << clientinfo;
std::string echo_string = "echo# ";
echo_string += inbuffer;
::sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, CONV(&peer), sizeof(peer));
}
}
_isrunning = false;
}
~UdpServer()
{
if(_sockfd > gsockfd)
::close(_sockfd);
}
private:
int _sockfd;
InetAddr _addr;
// uint16_t _port; // 服务器未来的端口号
// // std::string _ip; // 服务器所对应的IP
bool _isrunning; // 服务器运行状态
};
#endif
(2)UdpServerMain.cc
#include "UdpServer.hpp"
// ./server_udp localport
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " localport" << std::endl;
Die(USAGE_ERR);
}
// std::string ip = argv[1];
uint16_t port = std::stoi(argv[1]);
ENABLE_CONSOLE_LOG();
std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port);
svr_uptr->InitServer();
svr_uptr->Start();
return 0;
}
(3)UdpClient.hpp
#pragma once
(4)UdpClientMain.cc
#include "UdpClient.hpp"
#include "Common.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// CS
// ./client_udp serverip serverport
int main(int argc, char *argv[])
{
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl;
Die(USAGE_ERR);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 1. 创建socket
int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
std::cerr << "socket error" << std::endl;
Die(SOCKET_ERR);
}
// 1.1 填充server信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = ::htons(serverport);
server.sin_addr.s_addr = ::inet_addr(serverip.c_str());
// 2. clientdone
while(true)
{
std::cout << "Please Enter# ";
std::string message;
std::getline(std::cin, message);
// client 不需要bind吗?socket <-> socket
// client必须也要有自己的ip和端口!但是客户端,不需要自己显示的调用bind!!
// 而是,客户端首次sendto消息的时候,由OS自动进行bind
// 1. 如何理解client自动随机bind端口号? 一个端口号,只能被一个进程bind
// 2. 如何理解server要显示的bind?服务器的端口号,必须稳定!!必须是众所周知且不能改变轻易改变的!
int n = ::sendto(sockfd, message.c_str(),message.size(), 0, CONV(&server), sizeof(server));
(void)n;
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
char buffer[1024];
n = ::recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, CONV(&temp), &len);
if(n > 0)
{
buffer[n] = 0;
std::cout << buffer << std::endl;
}
}
return 0;
}
(5)Mutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>
namespace LockModule
{
class Mutex
{
public:
Mutex(const Mutex&) = delete;
const Mutex& operator = (const Mutex&) = delete;
Mutex()
{
int n = ::pthread_mutex_init(&_lock, nullptr);
(void)n;
}
~Mutex()
{
int n = ::pthread_mutex_destroy(&_lock);
(void)n;
}
void Lock()
{
int n = ::pthread_mutex_lock(&_lock);
(void)n;
}
pthread_mutex_t *LockPtr()
{
return &_lock;
}
void Unlock()
{
int n = ::pthread_mutex_unlock(&_lock);
(void)n;
}
private:
pthread_mutex_t _lock;
};
class LockGuard
{
public:
LockGuard(Mutex &mtx):_mtx(mtx)
{
_mtx.Lock();
}
~LockGuard()
{
_mtx.Unlock();
}
private:
Mutex &_mtx;
};
}
(6)Makefile
.PHONY:all
all:server_udp client_udp
server_udp:UdpServerMain.cc
g++ -o $@ $^ -std=c++17
client_udp:UdpClientMain.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -f server_udp client_udp
(7)Log.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem> //C++17
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"
namespace LogModule
{
using namespace LockModule;
// 获取一下当前系统的时间
std::string CurrentTime()
{
time_t time_stamp = ::time(nullptr);
struct tm curr;
localtime_r(&time_stamp, &curr); // 时间戳,获取可读性较强的时间信息5
char buffer[1024];
// bug
snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",
curr.tm_year + 1900,
curr.tm_mon + 1,
curr.tm_mday,
curr.tm_hour,
curr.tm_min,
curr.tm_sec);
return buffer;
}
// 构成: 1. 构建日志字符串 2. 刷新落盘(screen, file)
// 1. 日志文件的默认路径和文件名
const std::string defaultlogpath = "./log/";
const std::string defaultlogname = "log.txt";
// 2. 日志等级
enum class LogLevel
{
DEBUG = 1,
INFO,
WARNING,
ERROR,
FATAL
};
std::string Level2String(LogLevel level)
{
switch (level)
{
case LogLevel::DEBUG:
return "DEBUG";
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
default:
return "None";
}
}
// 3. 刷新策略.
class LogStrategy
{
public:
virtual ~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 3.1 控制台策略
class ConsoleLogStrategy : public LogStrategy
{
public:
ConsoleLogStrategy()
{
}
~ConsoleLogStrategy()
{
}
void SyncLog(const std::string &message)
{
LockGuard lockguard(_lock);
std::cout << message << std::endl;
}
private:
Mutex _lock;
};
// 3.2 文件级(磁盘)策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname)
: _logpath(logpath),
_logname(logname)
{
// 确认_logpath是存在的.
LockGuard lockguard(_lock);
if (std::filesystem::exists(_logpath))
{
return;
}
try
{
std::filesystem::create_directories(_logpath);
}
catch (std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << "\n";
}
}
~FileLogStrategy()
{
}
void SyncLog(const std::string &message)
{
LockGuard lockguard(_lock);
std::string log = _logpath + _logname; // ./log/log.txt
std::ofstream out(log, std::ios::app); // 日志写入,一定是追加
if (!out.is_open())
{
return;
}
out << message << "\n";
out.close();
}
private:
std::string _logpath;
std::string _logname;
// 锁
Mutex _lock;
};
// 日志类: 构建日志字符串, 根据策略,进行刷新
class Logger
{
public:
Logger()
{
// 默认采用ConsoleLogStrategy策略
_strategy = std::make_shared<ConsoleLogStrategy>();
}
void EnableConsoleLog()
{
_strategy = std::make_shared<ConsoleLogStrategy>();
}
void EnableFileLog()
{
_strategy = std::make_shared<FileLogStrategy>();
}
~Logger() {}
// 一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)
class LogMessage
{
public:
LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger)
: _currtime(CurrentTime()),
_level(level),
_pid(::getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
std::stringstream ssbuffer;
ssbuffer << "[" << _currtime << "] "
<< "[" << Level2String(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "] - ";
_loginfo = ssbuffer.str();
}
template <typename T>
LogMessage &operator<<(const T &info)
{
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if (_logger._strategy)
{
_logger._strategy->SyncLog(_loginfo);
}
}
private:
std::string _currtime; // 当前日志的时间
LogLevel _level; // 日志等级
pid_t _pid; // 进程pid
std::string _filename; // 源文件名称
int _line; // 日志所在的行号
Logger &_logger; // 负责根据不同的策略进行刷新
std::string _loginfo; // 一条完整的日志记录
};
// 就是要拷贝,故意的拷贝
LogMessage operator()(LogLevel level, const std::string &filename, int line)
{
return LogMessage(level, filename, line, *this);
}
private:
std::shared_ptr<LogStrategy> _strategy; // 日志刷新的策略方案
};
Logger logger;
#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}
(8)InetAddr.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"
class InetAddr
{
private:
void PortNet2Host()
{
_port = ::ntohs(_net_addr.sin_port);
}
void IpNet2Host()
{
char ipbuffer[64];
const char *ip = ::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
(void)ip;
}
public:
InetAddr()
{
}
InetAddr(const struct sockaddr_in &addr) : _net_addr(addr)
{
PortNet2Host();
IpNet2Host();
}
InetAddr(uint16_t port) : _port(port), _ip("")
{
_net_addr.sin_family = AF_INET;
_net_addr.sin_port = htons(_port);
_net_addr.sin_addr.s_addr = INADDR_ANY;
}
struct sockaddr *NetAddr() { return CONV(&_net_addr); }
socklen_t NetAddrLen() { return sizeof(_net_addr); }
std::string Ip() { return _ip; }
uint16_t Port() { return _port; }
~InetAddr()
{
}
private:
struct sockaddr_in _net_addr;
std::string _ip;
uint16_t _port;
};
(9)Common.hpp
#pragma once
#include <iostream>
#define Die(code) \
do \
{ \
exit(code); \
} while (0)
#define CONV(v) (struct sockaddr *)(v)
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR
};
5、加餐–网络命令
(1)Ping命令
用来检测两个主机能不能通信。用ping检测是否是网络没联通,要是能ping通,就证明是我们本身服务器写的有问题。
ping -c x 就ping x 次
(2)pidof
在查看服务器的进程id 时非常方便
语法:pidof[进程名]
功能:通过进程名,查看进程 id
也可以搭配管道组合使用
二、V2 版本-DictServer
1、UdpServer.hpp
#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__
#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <cstring>
#include <cerrno>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"
using namespace LogModule;
const static int gsockfd = -1;
// const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机
const static uint16_t gdefaultport = 8080;
using func_t = std::function<std::string(const std::string&)>;
class UdpServer
{
public:
UdpServer(func_t func, uint16_t port = gdefaultport)
: _sockfd(gsockfd),
_addr(port),
_isrunning(false),
_func(func)
{
}
// 都是套路
void InitServer()
{
// 1. 创建socket
_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地?
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
Die(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;
// 2. 填充网络信息,并bind绑定
// 2.1 有没有把socket信息,设置进入内核中??没有,只是填充了结构体!
// struct sockaddr_in local;
// bzero(&local, sizeof(local));
// local.sin_family = AF_INET;
// local.sin_port = ::htons(_port); // 要被发送给对方的,即要发到网络中!
// //local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1. string ip->4bytes 2. network order //TODO
// local.sin_addr.s_addr = INADDR_ANY;
// 2.2 bind : 设置进入内核中
int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind: " << strerror(errno);
Die(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success";
}
void Start()
{
_isrunning = true;
while (true)
{
char inbuffer[1024]; // string
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 必须设定
ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &len);
if (n > 0)
{
// 1. 消息内容 && 2. 谁发给我的
// uint16_t clientport = ::ntohs(peer.sin_port);
// std::string clientip = ::inet_ntoa(peer.sin_addr);
// InetAddr cli(peer);
// inbuffer[n] = 0;
// std::string clientinfo = cli.Ip() + ":" + std::to_string(cli.Port()) + " # " + inbuffer;
// LOG(LogLevel::DEBUG) << clientinfo;
// std::string echo_string = "echo# ";
// echo_string += inbuffer;
// 把英文单词转化成为中文
inbuffer[n] = 0;
std::string result = _func(inbuffer); // 这个是回调,出去,还会回来!!!!
::sendto(_sockfd, result.c_str(), result.size(), 0, CONV(&peer), sizeof(peer));
}
}
_isrunning = false;
}
~UdpServer()
{
if(_sockfd > gsockfd)
::close(_sockfd);
}
private:
int _sockfd;
InetAddr _addr;
// uint16_t _port; // 服务器未来的端口号
// // std::string _ip; // 服务器所对应的IP
bool _isrunning; // 服务器运行状态
// 业务,回调
func_t _func;
};
#endif
2、UdpServerMain.cc
#include "UdpServer.hpp"
#include "Dictionary.hpp"
// ./server_udp localport
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " localport" << std::endl;
Die(USAGE_ERR);
}
// std::string ip = argv[1];
uint16_t port = std::stoi(argv[1]);
ENABLE_CONSOLE_LOG();
std::shared_ptr<Dictionary> dict_sptr = std::make_shared<Dictionary>();
std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>([&dict_sptr](const std::string &word){
std::cout << "|" << word << "|" << std::endl;
return dict_sptr->Translate(word);
}, port);
// std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(std::bind(&Dictionary::Translate,\
// dict_sptr.get(), std::placeholders::_1), port);
svr_uptr->InitServer();
svr_uptr->Start();
return 0;
}
3、UdpClient.hpp
#pragma once
4、UdpClientMain.cc
#include "UdpClient.hpp"
#include "Common.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// CS
// ./client_udp serverip serverport
int main(int argc, char *argv[])
{
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl;
Die(USAGE_ERR);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 1. 创建socket
int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
std::cerr << "socket error" << std::endl;
Die(SOCKET_ERR);
}
// 1.1 填充server信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = ::htons(serverport);
server.sin_addr.s_addr = ::inet_addr(serverip.c_str());
// 2. clientdone
while(true)
{
std::cout << "Please Enter# ";
std::string message;
std::getline(std::cin, message);
// client 不需要bind吗?socket <-> socket
// client必须也要有自己的ip和端口!但是客户端,不需要自己显示的调用bind!!
// 而是,客户端首次sendto消息的时候,由OS自动进行bind
// 1. 如何理解client自动随机bind端口号? 一个端口号,只能被一个进程bind
// 2. 如何理解server要显示的bind?服务器的端口号,必须稳定!!必须是众所周知且不能改变轻易改变的!
int n = ::sendto(sockfd, message.c_str(),message.size(), 0, CONV(&server), sizeof(server));
(void)n;
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
char buffer[1024];
n = ::recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, CONV(&temp), &len);
if(n > 0)
{
buffer[n] = 0;
std::cout << buffer << std::endl;
}
}
return 0;
}
5、Mutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>
namespace LockModule
{
class Mutex
{
public:
Mutex(const Mutex&) = delete;
const Mutex& operator = (const Mutex&) = delete;
Mutex()
{
int n = ::pthread_mutex_init(&_lock, nullptr);
(void)n;
}
~Mutex()
{
int n = ::pthread_mutex_destroy(&_lock);
(void)n;
}
void Lock()
{
int n = ::pthread_mutex_lock(&_lock);
(void)n;
}
pthread_mutex_t *LockPtr()
{
return &_lock;
}
void Unlock()
{
int n = ::pthread_mutex_unlock(&_lock);
(void)n;
}
private:
pthread_mutex_t _lock;
};
class LockGuard
{
public:
LockGuard(Mutex &mtx):_mtx(mtx)
{
_mtx.Lock();
}
~LockGuard()
{
_mtx.Unlock();
}
private:
Mutex &_mtx;
};
}
6、Makefile
.PHONY:all
all:server_udp client_udp
server_udp:UdpServerMain.cc
g++ -o $@ $^ -std=c++17
client_udp:UdpClientMain.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -f server_udp client_udp
7、Log.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem> //C++17
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"
namespace LogModule
{
using namespace LockModule;
// 获取一下当前系统的时间
std::string CurrentTime()
{
time_t time_stamp = ::time(nullptr);
struct tm curr;
localtime_r(&time_stamp, &curr); // 时间戳,获取可读性较强的时间信息5
char buffer[1024];
// bug
snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",
curr.tm_year + 1900,
curr.tm_mon + 1,
curr.tm_mday,
curr.tm_hour,
curr.tm_min,
curr.tm_sec);
return buffer;
}
// 构成: 1. 构建日志字符串 2. 刷新落盘(screen, file)
// 1. 日志文件的默认路径和文件名
const std::string defaultlogpath = "./log/";
const std::string defaultlogname = "log.txt";
// 2. 日志等级
enum class LogLevel
{
DEBUG = 1,
INFO,
WARNING,
ERROR,
FATAL
};
std::string Level2String(LogLevel level)
{
switch (level)
{
case LogLevel::DEBUG:
return "DEBUG";
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
default:
return "None";
}
}
// 3. 刷新策略.
class LogStrategy
{
public:
virtual ~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 3.1 控制台策略
class ConsoleLogStrategy : public LogStrategy
{
public:
ConsoleLogStrategy()
{
}
~ConsoleLogStrategy()
{
}
void SyncLog(const std::string &message)
{
LockGuard lockguard(_lock);
std::cout << message << std::endl;
}
private:
Mutex _lock;
};
// 3.2 文件级(磁盘)策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname)
: _logpath(logpath),
_logname(logname)
{
// 确认_logpath是存在的.
LockGuard lockguard(_lock);
if (std::filesystem::exists(_logpath))
{
return;
}
try
{
std::filesystem::create_directories(_logpath);
}
catch (std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << "\n";
}
}
~FileLogStrategy()
{
}
void SyncLog(const std::string &message)
{
LockGuard lockguard(_lock);
std::string log = _logpath + _logname; // ./log/log.txt
std::ofstream out(log, std::ios::app); // 日志写入,一定是追加
if (!out.is_open())
{
return;
}
out << message << "\n";
out.close();
}
private:
std::string _logpath;
std::string _logname;
// 锁
Mutex _lock;
};
// 日志类: 构建日志字符串, 根据策略,进行刷新
class Logger
{
public:
Logger()
{
// 默认采用ConsoleLogStrategy策略
_strategy = std::make_shared<ConsoleLogStrategy>();
}
void EnableConsoleLog()
{
_strategy = std::make_shared<ConsoleLogStrategy>();
}
void EnableFileLog()
{
_strategy = std::make_shared<FileLogStrategy>();
}
~Logger() {}
// 一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)
class LogMessage
{
public:
LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger)
: _currtime(CurrentTime()),
_level(level),
_pid(::getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
std::stringstream ssbuffer;
ssbuffer << "[" << _currtime << "] "
<< "[" << Level2String(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "] - ";
_loginfo = ssbuffer.str();
}
template <typename T>
LogMessage &operator<<(const T &info)
{
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if (_logger._strategy)
{
_logger._strategy->SyncLog(_loginfo);
}
}
private:
std::string _currtime; // 当前日志的时间
LogLevel _level; // 日志等级
pid_t _pid; // 进程pid
std::string _filename; // 源文件名称
int _line; // 日志所在的行号
Logger &_logger; // 负责根据不同的策略进行刷新
std::string _loginfo; // 一条完整的日志记录
};
// 就是要拷贝,故意的拷贝
LogMessage operator()(LogLevel level, const std::string &filename, int line)
{
return LogMessage(level, filename, line, *this);
}
private:
std::shared_ptr<LogStrategy> _strategy; // 日志刷新的策略方案
};
Logger logger;
#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}
8、InetAddr.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"
class InetAddr
{
private:
void PortNet2Host()
{
_port = ::ntohs(_net_addr.sin_port);
}
void IpNet2Host()
{
char ipbuffer[64];
const char *ip = ::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
(void)ip;
}
public:
InetAddr()
{
}
InetAddr(const struct sockaddr_in &addr) : _net_addr(addr)
{
PortNet2Host();
IpNet2Host();
}
InetAddr(uint16_t port) : _port(port), _ip("")
{
_net_addr.sin_family = AF_INET;
_net_addr.sin_port = htons(_port);
_net_addr.sin_addr.s_addr = INADDR_ANY;
}
struct sockaddr *NetAddr() { return CONV(&_net_addr); }
socklen_t NetAddrLen() { return sizeof(_net_addr); }
std::string Ip() { return _ip; }
uint16_t Port() { return _port; }
~InetAddr()
{
}
private:
struct sockaddr_in _net_addr;
std::string _ip;
uint16_t _port;
};
9、Dictionary.hpp
#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"
#include "Common.hpp"
const std::string gpath = "./";
const std::string gdictname = "dict.txt";
const std::string gsep = ": ";
using namespace LogModule;
class Dictionary
{
private:
bool LoadDictionary()
{
std::string file = _path + _filename;
std::ifstream in(file.c_str());
if (!in.is_open())
{
LOG(LogLevel::ERROR) << "open file " << file << " error";
return false;
}
std::string line;
while (std::getline(in, line))
{
// happy: 快乐的
std::string key;
std::string value;
if (SplitString(line, &key, &value, gsep))
{ // line -> key, value
_dictionary.insert(std::make_pair(key, value));
}
}
in.close();
return true;
}
public:
Dictionary(const std::string &path = gpath, const std::string &filename = gdictname)
: _path(path),
_filename(filename)
{
LoadDictionary();
Print();
}
std::string Translate(const std::string &word)
{
auto iter = _dictionary.find(word);
if(iter == _dictionary.end()) return "None";
return iter->second;
}
void Print()
{
for(auto &item : _dictionary)
{
std::cout << item.first << ":" << item.second<< std::endl;
}
}
~Dictionary()
{
}
private:
std::unordered_map<std::string, std::string> _dictionary;
std::string _path;
std::string _filename;
};
10、Common.hpp
#pragma once
#include <iostream>
#include <string>
#define Die(code) \
do \
{ \
exit(code); \
} while (0)
#define CONV(v) (struct sockaddr *)(v)
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR
};
// happy: 快乐的
bool SplitString(const std::string &line, std::string *key, std::string *value, const std::string &sep)
{
auto pos = line.find(sep);
if(pos == std::string::npos) return false;
*key = line.substr(0, pos);
*value = line.substr(pos+sep.size());
if(key->empty() || value->empty()) return false;
return true;
}
11、dict.txt
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
三、V3 版本-ChatServer
1、UdpServer.hpp
#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__
#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"
#include "ThreadPool.hpp"
using namespace LogModule;
using namespace ThreadPoolModule;
const static int gsockfd = -1;
// const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机
const static uint16_t gdefaultport = 8080;
using adduser_t = std::function<void(InetAddr &id)>;
using remove_t = std::function<void(InetAddr &id)>;
using task_t = std::function<void()>;
using route_t = std::function<void(int sockfd, const std::string &message)>;
class nocopy
{
public:
nocopy(){}
nocopy(const nocopy &) = delete;
const nocopy& operator = (const nocopy &) = delete;
~nocopy(){}
};
class UdpServer : public nocopy
{
public:
UdpServer(uint16_t port = gdefaultport)
: _sockfd(gsockfd),
_addr(port),
_isrunning(false)
{
}
// 都是套路
void InitServer()
{
// 1. 创建socket
_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地?
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
Die(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;
// 2. 填充网络信息,并bind绑定
// 2.1 有没有把socket信息,设置进入内核中??没有,只是填充了结构体!
// struct sockaddr_in local;
// bzero(&local, sizeof(local));
// local.sin_family = AF_INET;
// local.sin_port = ::htons(_port); // 要被发送给对方的,即要发到网络中!
// //local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1. string ip->4bytes 2. network order //TODO
// local.sin_addr.s_addr = INADDR_ANY;
// 2.2 bind : 设置进入内核中
int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind: " << strerror(errno);
Die(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success";
}
void RegisterService(adduser_t adduser, route_t route, remove_t removeuser)
{
_adduser = adduser;
_route = route;
_removeuser = removeuser;
}
void Start()
{
_isrunning = true;
while (true)
{
char inbuffer[1024]; // string
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 必须设定
ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &len);
if (n > 0)
{
// 1. 消息内容 && 2. 谁发给我的
// uint16_t clientport = ::ntohs(peer.sin_port);
// std::string clientip = ::inet_ntoa(peer.sin_addr);
InetAddr cli(peer);
inbuffer[n] = 0;
std::string message;
if (strcmp(inbuffer, "QUIT") == 0)
{
// 移除观察者
_removeuser(cli);
message = cli.Addr() + "# " + "我走了,你们聊!";
}
else
{
// 2. 新增用户
_adduser(cli);
message = cli.Addr() + "# " + inbuffer;
}
// 3. 构建转发任务,推送给线程池,让线程池进行转发
task_t task = std::bind(UdpServer::_route, _sockfd, message);
ThreadPool<task_t>::getInstance()->Equeue(task);
// std::string clientinfo = cli.Ip() + ":" + std::to_string(cli.Port()) + " # " + inbuffer;
// LOG(LogLevel::DEBUG) << clientinfo;
// std::string echo_string = "echo# ";
// echo_string += inbuffer;
// ::sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, CONV(&peer), sizeof(peer));
}
}
_isrunning = false;
}
~UdpServer()
{
if (_sockfd > gsockfd)
::close(_sockfd);
}
private:
int _sockfd;
InetAddr _addr;
bool _isrunning; // 服务器运行状态
// 新增用户
adduser_t _adduser;
// 移除用户
remove_t _removeuser;
// 数据转发
route_t _route;
};
#endif
2、UdpServerMain.cc
#include "UdpServer.hpp"
#include "User.hpp"
// ./server_udp localport
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " localport" << std::endl;
Die(USAGE_ERR);
}
// std::string ip = argv[1];
uint16_t port = std::stoi(argv[1]);
ENABLE_CONSOLE_LOG();
// 用户管理模块
std::shared_ptr<UserManager> um = std::make_shared<UserManager>();
// 网络模块
std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port);
svr_uptr->RegisterService([&um](InetAddr &id){ um->AddUser(id); },
[&um](int sockfd, const std::string &message){ um->Router(sockfd, message);},
[&um](InetAddr &id){ um->DelUser(id);}
);
svr_uptr->InitServer();
svr_uptr->Start();
return 0;
}
3、UdpClient.hpp
#pragma once
4、UdpClientMain.cc
#include "UdpClient.hpp"
#include "Common.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
int sockfd = -1;
struct sockaddr_in server;
void ClientQuit(int signo)
{
(void)signo;
const std::string quit = "QUIT";
int n = ::sendto(sockfd, quit.c_str(), quit.size(), 0, CONV(&server), sizeof(server));
exit(0);
}
void *Recver(void *args)
{
while (true)
{
(void)args;
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
char buffer[1024];
int n = ::recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&temp), &len);
if (n > 0)
{
buffer[n] = 0;
std::cerr << buffer << std::endl; // 代码没问题,重定向也没问题,管道读写同时打开,才会继续向后运行
// fprintf(stderr, "%s\n", buffer);
// fflush(stderr);
}
}
}
// CS
// ./client_udp serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl;
Die(USAGE_ERR);
}
signal(2, ClientQuit);
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 1. 创建socket
sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
Die(SOCKET_ERR);
}
// 1.1 填充server信息
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = ::htons(serverport);
server.sin_addr.s_addr = ::inet_addr(serverip.c_str());
pthread_t tid;
pthread_create(&tid, nullptr, Recver, nullptr);
// 1.2 启动的时候,给服务器推送消息即可
const std::string online = " ... 来了哈!";
int n = ::sendto(sockfd, online.c_str(), online.size(), 0, CONV(&server), sizeof(server));
// 2. clientdone
while (true)
{
std::cout << "Please Enter# ";
std::string message;
std::getline(std::cin, message);
// client 不需要bind吗?socket <-> socket
// client必须也要有自己的ip和端口!但是客户端,不需要自己显示的调用bind!!
// 而是,客户端首次sendto消息的时候,由OS自动进行bind
// 1. 如何理解client自动随机bind端口号? 一个端口号,只能被一个进程bind
// 2. 如何理解server要显示的bind?服务器的端口号,必须稳定!!必须是众所周知且不能改变轻易改变的!
int n = ::sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));
(void)n;
}
return 0;
}
5、Mutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>
namespace LockModule
{
class Mutex
{
public:
Mutex(const Mutex&) = delete;
const Mutex& operator = (const Mutex&) = delete;
Mutex()
{
int n = ::pthread_mutex_init(&_lock, nullptr);
(void)n;
}
~Mutex()
{
int n = ::pthread_mutex_destroy(&_lock);
(void)n;
}
void Lock()
{
int n = ::pthread_mutex_lock(&_lock);
(void)n;
}
pthread_mutex_t *LockPtr()
{
return &_lock;
}
void Unlock()
{
int n = ::pthread_mutex_unlock(&_lock);
(void)n;
}
private:
pthread_mutex_t _lock;
};
class LockGuard
{
public:
LockGuard(Mutex &mtx):_mtx(mtx)
{
_mtx.Lock();
}
~LockGuard()
{
_mtx.Unlock();
}
private:
Mutex &_mtx;
};
}
6、ThreadPool.hpp
#pragma once
#include <iostream>
#include <string>
#include <queue>
#include <vector>
#include <memory>
#include "Log.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include "Thread.hpp"
namespace ThreadPoolModule
{
using namespace LogModule;
using namespace ThreadModule;
using namespace LockModule;
using namespace CondModule;
// 用来做测试的线程方法
void DefaultTest()
{
while (true)
{
LOG(LogLevel::DEBUG) << "我是一个测试方法";
sleep(1);
}
}
using thread_t = std::shared_ptr<Thread>;
const static int defaultnum = 5;
template <typename T>
class ThreadPool
{
private:
bool IsEmpty() { return _taskq.empty(); }
void HandlerTask(std::string name)
{
LOG(LogLevel::INFO) << "线程: " << name << ", 进入HandlerTask的逻辑";
while (true)
{
// 1. 拿任务
T t;
{
LockGuard lockguard(_lock);
while (IsEmpty() && _isrunning)
{
_wait_num++;
_cond.Wait(_lock);
_wait_num--;
}
// 2. 任务队列为空 && 线程池退出了
if (IsEmpty() && !_isrunning)
break;
t = _taskq.front();
_taskq.pop();
}
// 2. 处理任务
t(); // 规定,未来所有的任务处理,全部都是必须提供()方法!
}
LOG(LogLevel::INFO) << "线程: " << name << " 退出";
}
ThreadPool(const ThreadPool<T> &) = delete;
ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
ThreadPool(int num = defaultnum) : _num(num), _wait_num(0), _isrunning(false)
{
for (int i = 0; i < _num; i++)
{
_threads.push_back(std::make_shared<Thread>(std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1)));
LOG(LogLevel::INFO) << "构建线程" << _threads.back()->Name() << "对象 ... 成功";
}
}
public:
static ThreadPool<T> *getInstance()
{
if (instance == NULL)
{
LockGuard lockguard(mutex);
if (instance == NULL)
{
LOG(LogLevel::INFO) << "单例首次被执行,需要加载对象...";
instance = new ThreadPool<T>();
instance->Start();
}
}
return instance;
}
void Equeue(T &in)
{
LockGuard lockguard(_lock);
if (!_isrunning)
return;
// _taskq.push(std::move(in));
_taskq.push(in);
if (_wait_num > 0)
_cond.Notify();
}
void Start()
{
if (_isrunning)
return;
_isrunning = true; // bug fix??
for (auto &thread_ptr : _threads)
{
LOG(LogLevel::INFO) << "启动线程" << thread_ptr->Name() << " ... 成功";
thread_ptr->Start();
}
}
void Wait()
{
for (auto &thread_ptr : _threads)
{
thread_ptr->Join();
LOG(LogLevel::INFO) << "回收线程" << thread_ptr->Name() << " ... 成功";
}
}
void Stop()
{
LockGuard lockguard(_lock);
if (_isrunning)
{
// 3. 不能在入任务了
_isrunning = false; // 不工作
// 1. 让线程自己退出(要唤醒) && // 2. 历史的任务被处理完了
if (_wait_num > 0)
_cond.NotifyAll();
}
}
~ThreadPool()
{
}
private:
std::vector<thread_t> _threads;
int _num;
int _wait_num;
std::queue<T> _taskq; // 临界资源
Mutex _lock;
Cond _cond;
bool _isrunning;
static ThreadPool<T> *instance;
static Mutex mutex; // 只用来保护单例
};
template <typename T>
ThreadPool<T> *ThreadPool<T>::instance = NULL;
template <typename T>
Mutex ThreadPool<T>::mutex; // 只用来保护单例
}
7、Thread.hpp
#ifndef _THREAD_HPP__
#define _THREAD_HPP__
#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>
// v1
namespace ThreadModule
{
using func_t = std::function<void(std::string name)>;
static int number = 1;
enum class TSTATUS
{
NEW,
RUNNING,
STOP
};
class Thread
{
private:
// 成员方法!
static void *Routine(void *args)
{
Thread *t = static_cast<Thread *>(args);
t->_status = TSTATUS::RUNNING;
t->_func(t->Name());
return nullptr;
}
void EnableDetach() { _joinable = false; }
public:
Thread(func_t func) : _func(func), _status(TSTATUS::NEW), _joinable(true)
{
_name = "Thread-" + std::to_string(number++);
_pid = getpid();
}
bool Start()
{
if (_status != TSTATUS::RUNNING)
{
int n = ::pthread_create(&_tid, nullptr, Routine, this); // TODO
if (n != 0)
return false;
return true;
}
return false;
}
bool Stop()
{
if (_status == TSTATUS::RUNNING)
{
int n = ::pthread_cancel(_tid);
if (n != 0)
return false;
_status = TSTATUS::STOP;
return true;
}
return false;
}
bool Join()
{
if (_joinable)
{
int n = ::pthread_join(_tid, nullptr);
if (n != 0)
return false;
_status = TSTATUS::STOP;
return true;
}
return false;
}
void Detach()
{
EnableDetach();
pthread_detach(_tid);
}
bool IsJoinable() { return _joinable; }
std::string Name() {return _name;}
~Thread()
{
}
private:
std::string _name;
pthread_t _tid;
pid_t _pid;
bool _joinable; // 是否是分离的,默认不是
func_t _func;
TSTATUS _status;
};
}
// v2
// namespace ThreadModule
// {
// static int number = 1;
// enum class TSTATUS
// {
// NEW,
// RUNNING,
// STOP
// };
// template <typename T>
// class Thread
// {
// using func_t = std::function<void(T)>;
// private:
// // 成员方法!
// static void *Routine(void *args)
// {
// Thread<T> *t = static_cast<Thread<T> *>(args);
// t->_status = TSTATUS::RUNNING;
// t->_func(t->_data);
// return nullptr;
// }
// void EnableDetach() { _joinable = false; }
// public:
// Thread(func_t func, T data) : _func(func), _data(data), _status(TSTATUS::NEW), _joinable(true)
// {
// _name = "Thread-" + std::to_string(number++);
// _pid = getpid();
// }
// bool Start()
// {
// if (_status != TSTATUS::RUNNING)
// {
// int n = ::pthread_create(&_tid, nullptr, Routine, this); // TODO
// if (n != 0)
// return false;
// return true;
// }
// return false;
// }
// bool Stop()
// {
// if (_status == TSTATUS::RUNNING)
// {
// int n = ::pthread_cancel(_tid);
// if (n != 0)
// return false;
// _status = TSTATUS::STOP;
// return true;
// }
// return false;
// }
// bool Join()
// {
// if (_joinable)
// {
// int n = ::pthread_join(_tid, nullptr);
// if (n != 0)
// return false;
// _status = TSTATUS::STOP;
// return true;
// }
// return false;
// }
// void Detach()
// {
// EnableDetach();
// pthread_detach(_tid);
// }
// bool IsJoinable() { return _joinable; }
// std::string Name() { return _name; }
// ~Thread()
// {
// }
// private:
// std::string _name;
// pthread_t _tid;
// pid_t _pid;
// bool _joinable; // 是否是分离的,默认不是
// func_t _func;
// TSTATUS _status;
// T _data;
// };
// }
#endif
8、User.hpp
#pragma once
#include <iostream>
#include <string>
#include <list>
#include <memory>
#include <algorithm>
#include <sys/types.h>
#include <sys/socket.h>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Mutex.hpp"
using namespace LogModule;
using namespace LockModule;
class UserInterface
{
public:
virtual ~UserInterface() = default;
virtual void SendTo(int sockfd, const std::string &message) = 0;
virtual bool operator==(const InetAddr &u) = 0;
virtual std::string Id() = 0;
};
class User : public UserInterface
{
public:
User(const InetAddr &id) : _id(id)
{
}
void SendTo(int sockfd, const std::string &message) override
{
LOG(LogLevel::DEBUG) << "send message to " << _id.Addr() << " info: " << message;
int n = ::sendto(sockfd, message.c_str(), message.size(), 0, _id.NetAddr(), _id.NetAddrLen());
(void)n;
}
bool operator==(const InetAddr &u) override
{
return _id == u;
}
std::string Id() override
{
return _id.Addr();
}
~User()
{
}
private:
InetAddr _id;
};
// 对用户消息进行路由
// UserManager 把所有的用户先管理起来!
// 观察者模式!observer
class UserManager
{
public:
UserManager()
{
}
void AddUser(InetAddr &id)
{
LockGuard lockguard(_mutex);
for (auto &user : _online_user)
{
if (*user == id)
{
LOG(LogLevel::INFO) << id.Addr() << "用户已经存在";
return;
}
}
LOG(LogLevel::INFO) << " 新增该用户: " << id.Addr();
_online_user.push_back(std::make_shared<User>(id));
PrintUser();
}
void DelUser(InetAddr &id)
{
// v1
auto pos = std::remove_if(_online_user.begin(), _online_user.end(), [&id](std::shared_ptr<UserInterface> &user){
return *user == id;
});
_online_user.erase(pos, _online_user.end());
PrintUser();
//v2
// for(auto user : _online_user)
// {
// if(*user == id)
// {
// _online_user.erase(user);
// break; // 迭代器失效的问题
// }
// }
}
void Router(int sockfd, const std::string &message)
{
LockGuard lockguard(_mutex);
for (auto &user : _online_user)
{
user->SendTo(sockfd, message);
}
}
void PrintUser()
{
for(auto user : _online_user)
{
LOG(LogLevel::DEBUG) <<"在线用户-> "<< user->Id();
}
}
~UserManager()
{
}
private:
std::list<std::shared_ptr<UserInterface>> _online_user;
Mutex _mutex;
};
9、Makefile
.PHONY:all
all:server_udp client_udp
server_udp:UdpServerMain.cc
g++ -o $@ $^ -std=c++17 -lpthread
client_udp:UdpClientMain.cc
g++ -o $@ $^ -std=c++17 -lpthread
.PHONY:clean
clean:
rm -f server_udp client_udp
10、Log.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem> //C++17
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"
namespace LogModule
{
using namespace LockModule;
// 获取一下当前系统的时间
std::string CurrentTime()
{
time_t time_stamp = ::time(nullptr);
struct tm curr;
localtime_r(&time_stamp, &curr); // 时间戳,获取可读性较强的时间信息5
char buffer[1024];
// bug
snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",
curr.tm_year + 1900,
curr.tm_mon + 1,
curr.tm_mday,
curr.tm_hour,
curr.tm_min,
curr.tm_sec);
return buffer;
}
// 构成: 1. 构建日志字符串 2. 刷新落盘(screen, file)
// 1. 日志文件的默认路径和文件名
const std::string defaultlogpath = "./log/";
const std::string defaultlogname = "log.txt";
// 2. 日志等级
enum class LogLevel
{
DEBUG = 1,
INFO,
WARNING,
ERROR,
FATAL
};
std::string Level2String(LogLevel level)
{
switch (level)
{
case LogLevel::DEBUG:
return "DEBUG";
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
default:
return "None";
}
}
// 3. 刷新策略.
class LogStrategy
{
public:
virtual ~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 3.1 控制台策略
class ConsoleLogStrategy : public LogStrategy
{
public:
ConsoleLogStrategy()
{
}
~ConsoleLogStrategy()
{
}
void SyncLog(const std::string &message)
{
LockGuard lockguard(_lock);
std::cout << message << std::endl;
}
private:
Mutex _lock;
};
// 3.2 文件级(磁盘)策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname)
: _logpath(logpath),
_logname(logname)
{
// 确认_logpath是存在的.
LockGuard lockguard(_lock);
if (std::filesystem::exists(_logpath))
{
return;
}
try
{
std::filesystem::create_directories(_logpath);
}
catch (std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << "\n";
}
}
~FileLogStrategy()
{
}
void SyncLog(const std::string &message)
{
LockGuard lockguard(_lock);
std::string log = _logpath + _logname; // ./log/log.txt
std::ofstream out(log, std::ios::app); // 日志写入,一定是追加
if (!out.is_open())
{
return;
}
out << message << "\n";
out.close();
}
private:
std::string _logpath;
std::string _logname;
// 锁
Mutex _lock;
};
// 日志类: 构建日志字符串, 根据策略,进行刷新
class Logger
{
public:
Logger()
{
// 默认采用ConsoleLogStrategy策略
_strategy = std::make_shared<ConsoleLogStrategy>();
}
void EnableConsoleLog()
{
_strategy = std::make_shared<ConsoleLogStrategy>();
}
void EnableFileLog()
{
_strategy = std::make_shared<FileLogStrategy>();
}
~Logger() {}
// 一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)
class LogMessage
{
public:
LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger)
: _currtime(CurrentTime()),
_level(level),
_pid(::getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
std::stringstream ssbuffer;
ssbuffer << "[" << _currtime << "] "
<< "[" << Level2String(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "] - ";
_loginfo = ssbuffer.str();
}
template <typename T>
LogMessage &operator<<(const T &info)
{
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if (_logger._strategy)
{
_logger._strategy->SyncLog(_loginfo);
}
}
private:
std::string _currtime; // 当前日志的时间
LogLevel _level; // 日志等级
pid_t _pid; // 进程pid
std::string _filename; // 源文件名称
int _line; // 日志所在的行号
Logger &_logger; // 负责根据不同的策略进行刷新
std::string _loginfo; // 一条完整的日志记录
};
// 就是要拷贝,故意的拷贝
LogMessage operator()(LogLevel level, const std::string &filename, int line)
{
return LogMessage(level, filename, line, *this);
}
private:
std::shared_ptr<LogStrategy> _strategy; // 日志刷新的策略方案
};
Logger logger;
#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}
11、InetAddr.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"
class InetAddr
{
private:
void PortNet2Host()
{
_port = ::ntohs(_net_addr.sin_port);
}
void IpNet2Host()
{
char ipbuffer[64];
const char *ip = ::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
(void)ip;
_ip = ipbuffer;
}
public:
InetAddr()
{
}
InetAddr(const struct sockaddr_in &addr) : _net_addr(addr)
{
PortNet2Host();
IpNet2Host();
}
bool operator == (const InetAddr &addr)
{
return _ip == addr._ip && _port == addr._port; //debug
//return _ip == addr._ip; //debug
}
InetAddr(uint16_t port) : _port(port), _ip("")
{
_net_addr.sin_family = AF_INET;
_net_addr.sin_port = htons(_port);
_net_addr.sin_addr.s_addr = INADDR_ANY;
}
struct sockaddr *NetAddr() { return CONV(&_net_addr); }
socklen_t NetAddrLen() { return sizeof(_net_addr); }
std::string Ip() { return _ip; }
uint16_t Port() { return _port; }
std::string Addr()
{
return Ip() + ":" + std::to_string(Port());
}
~InetAddr()
{
}
private:
struct sockaddr_in _net_addr;
std::string _ip;
uint16_t _port;
};
12、Cond.hpp
#pragma once
#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"
namespace CondModule
{
using namespace LockModule;
class Cond
{
public:
Cond()
{
int n = ::pthread_cond_init(&_cond, nullptr);
(void)n;
}
void Wait(Mutex &lock) // 让我们的线程释放曾经持有的锁!
{
int n = ::pthread_cond_wait(&_cond, lock.LockPtr());
}
void Notify()
{
int n = ::pthread_cond_signal(&_cond);
(void)n;
}
void NotifyAll()
{
int n = ::pthread_cond_broadcast(&_cond);
(void)n;
}
~Cond()
{
int n = ::pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond;
};
}
13、Common.hpp
#pragma once
#include <iostream>
#define Die(code) \
do \
{ \
exit(code); \
} while (0)
#define CONV(v) (struct sockaddr *)(v)
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR
};