浦口国家建设部网站,大庆城市建设投资网站,node做网站后台,北京网站设计 培训本篇文章将带大家了解网络通信是如何进行的#xff08;如包括网络字节序#xff0c;端口号#xff0c;协议等#xff09; #xff1b;再对socket套接字进行介绍#xff1b;以及一些udp-socket相关网络通信接口的介绍及使用#xff1b;最后进行对基于udp的网络通信#…本篇文章将带大家了解网络通信是如何进行的如包括网络字节序端口号协议等 再对socket套接字进行介绍以及一些udp-socket相关网络通信接口的介绍及使用最后进行对基于udp的网络通信服务端与客户端之间进行测试和外加模拟实现的基于udp通信的简单的英译汉词典与多人聊天室欢迎阅读 欢迎拜访羑悻的小杀马特.-CSDN博客 本篇主题速通基于UDP的简单网络通信 制作日期2025.07.08 隶属专栏Linux之旅 目录
一·网络通信相关概念
1.1认识ip及端口号
认识ip
认识端口号
端口号划分
端口号与进程ID的区别
理解源端口号与目标端口号
1.2浅解Socket概念
浅浅认识TCP/UDP协议 TCP协议
UDP协议
认识网络字节序
认识Socket底层结构
认识sockaddr_in 结构
二·网络通信之Socket-UDP
2.1Socket-UDP相关网络通信接口函数认识
2.1.1创建套接字之socket接口
2.1.2绑定套接字之bind接口
2.1.3 接收信息之recvfrom接口
2.1.4发送信息之sendto接口
2.2UDP通信特点及注意事项
UDP通信特性 UDP通信时注意事项 ①客户端是不需要手动绑定:
②一般服务端是不能直接绑定特定ip的
2.3实现基于UDP的server-client简单通信 测试效果
代码实现
主要代码文件部分
udpclient.cc:
udpserver.cc:
udpserver.hpp:
间接包含文件
log.hpp:
Makefile:
mutex.hpp: 2.4基于UDP实现的server-client简单通信改造的英译汉翻译功能: 测试效果
代码实现 主要代码部分
dict.hpp:
dict.txt:
addr.hpp:
udpclient.cc:
udpserver.cc:
udpserver.hpp:
间接包含部分
2.5基于UDP实现的server-clients通信改造多人聊天室服务端多线程版本
效果展示
代码实现
主要代码部分
udpclient.cc:
udpserver.cc:
udpserver.hpp:
addr.hpp:
route.hpp:
间接包含部分
cond.hpp:
thread.hpp:
threadpoll.hpp:
优化关于网络序列和本机序列之间的转化
之前对port的网络本机序列转化
之前对ip的网络本机序列转化
三·常用的网络指令
3.1ifconfig
3.2netstat
3.3ping:
3.4pidof:
四·本篇小结: 一·网络通信相关概念
1.1认识ip及端口号
首先何为网络通信
数据传输到主机不是目的而是手段。到达主机内部在交给主机内的进程才是目的
因此上网要么从某处读取数据要么向某处发送数据本质就是数据的交互因此我们可以理解成 故通信/数据通过网络传输其实就是两个指定进程之间通过网络传输 认识ip IP地址是在IP协议中,用来标识网络中不同主机的地址;对于IPv4来说,IP地址是一个4字节,32位的整数;我们通常也使用点分十进制的字符串表示IP地址,例如192.168.0.1;用点分割的每一个数字表示一个字节,范围是0-255这里我们就认为是区域内标定唯一主机的 认识端口号 端口号是一个 2 字节 16 位的整数; 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理因此可以总结成一个端口号只能对应一个进程但是一个进程可以有多个端口号 端口号目的就是从主机中确定一个具体的进程! 端口号划分 0-1023: 知名端口号, HTTP,FTP,SSH 等这些广为使用的应用层协议,他们的端口号都是固定的。 1024-65535:操作系统动态分配的端口号.客户端程序的端口号,就是由操作系统从这个范围分配的。也就是说我们在后面udptcp通信等使用的端口号都是1024-65535这个范围内的 端口号与进程ID的区别 端口号是进程唯一性的标识也就是说通过指定端口号只能找到一个进程另外,一个进程可以绑定多个端口号;但是一个端口号不能被多个进程绑定
形象理解下
可以把进程想象成不同的商店端口号就像是商店的门牌号。
一个进程可以绑定多个端口号就好比一家商店可以有多个门比如大型商场它可能有正门、侧门、后门等多个门(多个端口号)顾客数据可以从不同的门进入商场进程)每个门都可以用来接待不同类型的顾客或者提供不同的服务。
而一个端口号不能被多个进程绑定是因为如果一个门牌号对应了多家商店那么顾客就会不知道该进哪家店数据也会混乱不知道该把信息送到哪个进程。所以一个门牌号(端口号只能对应一家商店进程)这样才能保证数据准确无误地到达对应的进程。 进程 ID 属于系统概念技术上也具有唯一性确实可以用来标识唯一的一个进程但是这样做会让系统进程管理和网络强耦合实际设计的时候并没有选择这样做。 不是所有的进程都要进行网络通信因此端口号不是每个进程都有的 这里pid是一个系统概念端口号是网络概念当网络一变那么进程对应的端口号就会变化但是pid是不变的实现了解耦 理解源端口号与目标端口号 传输层协议(TCP和UDP)的数据段中有两个端口号分别叫做源端口号和目的端口号.就是在描述数据是谁发的,要发给谁。 得出结论IPPort全网内唯一的一个进程
因此就可以理解网络通信本质全网内唯二的两个进程在进行进程间通信其中ip确定好网络内一主机而端口号就确定的是该主机的某个进程要完成网络通信我们就需要源ip源port目的ip目的端口号也就是源socket和目标socket我们在下面会讲到
1.2浅解Socket概念
在上面我们也讲到了IP 地址用来标识互联网中唯一的一台主机port 用来标识该主机上唯一的.一个网络进程
通信的时候本质是两个互联网进程代表人来进行通信{srclpsrcPortdstlpdstPor}这样的4元组就能标识互联网中唯二的两个进程网络通信就是进程间通信 我们把币pport 叫做套接字 socket 这就是它的由来 socket和tcp/ip有关也就是和网络层传输层有关--属于内核--受OS控制--需要使用系统接口 因此当我们使用套接字的时候就需要使用系统提供的接口后面用的时候我们会讲解
浅浅认识TCP/UDP协议 TCP协议 TCP(Transmission Control Protocol 传输控制协议) 特点 做更多工作复杂占有资源多 传输层协议 有连接 可靠传输 面向字节流类似当初的文件流;耦合性不是特别强这里我们将TCP通信的时候会明显察觉 UDP协议 UDP(User Datagram Protoco 用户数据报协议) 特点 相对做的少;简单等 传输层协议 不可靠传输 面向数据报报比如发送10个信息就收到10个不多也不少马上就可以看到了 这里我们仍旧是需要保存他们俩;各有各的特点;谁更合适就选择谁
认识网络字节序
先认识下大小端 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。
因此就有了以下规定 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址其中TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节。 大小端机器 都会遵循这个原则如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可只不过应用了转化技术。 但是每次这样发送和接收的时候我们都进行转化相对比较麻烦因此系统就自己封装了一些函数自动帮助我们是被本端机器大小端完成网络和本地的转化工作 助记 h 表示 host,n 表示 network,表示 32 位长整数,s 表示 16 位短整数。 大小端转化问题 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。 说白了就是网络通信要用网络字节序;其他地方就是主行,可以调用以下库函数做网络字节序和主机字节序的转换。机字节序: 主机字节序--网络字节序--主机字节序
不仅有 1·本地序列和网络序列转化功能 2·还兼容大小端转化问题-完美
后面我们应用的时候再做演示说明
认识Socket底层结构
首先先来看一下常见API: 先认识下后面我们在UDPTCP通信的时候都会用到 IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址. IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6.这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容. socket API可以都用struct sockaddr*类型表示,在使用的时候需要强制转化成sockaddr_in;这样的好处是程序的通用性,可以接收IPv4,IPv6以及UNIX DomainSocket各种类型的sockaddr结构体指针做为参数; 总而言之我们接下来就只拿IPV4地址类型来谈利用它的类型定义及结构体
下面我们看张图理解下对应的IPV4下的本地与网络通信 这里socket为什么不强转void传进去:当时语言还不支持;而这样写更加能体现出继承多态那套逻辑。 但是上面我们说了无论是网络通信还是本地通信最后结构体都会被转出sockaddr形式那么它该如何区分是网络通信还是本地通信呢 判断首地址为哪个类型就强转成那个类型指针去访问 下面我们利用文字叙述下上面操作的过程 首先把un或者in的地址传给这个所谓的父类:然后它的address的值就被覆盖成对应传进来的结构体的首地址了﹔但是类型还是sockaddra因此当我们拿对应指针访问sockaddr的成员的时候就是相当于加上之前拿成员对应的移动多少地址的匹配对应关系)--故访问到的就是前16位;因此拿着个里面的数据去判断﹔如果是AFINET就转成对应的指针﹔此时首地址还是没有变;只是它的类型变成了对应的sockaddr_in类型就可以访问了﹔改变指针类型改变了这种访问成员的偏移量的匹配机制 因为网络通信和本地通信大差不差也就是理解了网络通信本地通信就差不多了那么下面我们就学习一下网络通信
认识sockaddr_in 结构 sockaddr_in:
struct sockaddr_in{__SOCKADDR_COMMON (sin_);in_port_t sin_port; /* Port number. */struct in_addr sin_addr; /* Internet address. *//* Pad to size of struct sockaddr. */unsigned char sin_zero[sizeof (struct sockaddr)- __SOCKADDR_COMMON_SIZE- sizeof (in_port_t)- sizeof (struct in_addr)];}; 这里虽然看起来很多参数大都是typedef出来的因此不必担心
下面我们看张图来更清晰认识它 这样我们就和之前的那张继承的结构体图片为什么那样写就完美结合底层结构认识了
这里我们就先简单认识下IPV4地址下的网络通信的底层结构即可后面我们在使用这些接口的时候会有更清晰认识的
二·网络通信之Socket-UDP
2.1Socket-UDP相关网络通信接口函数认识
下面我们将为之后基于UDP实现的网络通信先进行相关函数接口介绍
首先就是网络通信接口必备四大头文件
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
2.1.1创建套接字之socket接口 #include sys/types.h /* See NOTES */#include sys/socket.hint socket(int domain, int type, int protocol); 成功返回套接字的文件描述符失败就小于0 对于UDP我们这样用 这里其实返回的这个socketfd就是个文件描述符:因此可以理解成我们这样就创建了一个“网络通信的文件”。
2.1.2绑定套接字之bind接口 #include sys/types.h /* See NOTES */#include sys/socket.hint bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);这里的socklen_t就是这个结构体的长度字节 成功返回0失败就小于0 需要注意的是我们网络通信的sockaddr_in*需要强转成sockaddr*类型 在我们填写sockaddr_in的时候需要注意的是 注意本地序列与网络序列之间的转化 此时就用到我们上面讲述的htons了把这个16位整数按照1·大端的方式2·变成网络序列 #include arpa/inet.huint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort); 以及ip地址转化inet_addr系统提供的可以把string字符串类型转化成1·大端;2、·并且转化成32位数字的网络序列 in_addr_t inet_addr(const char *cp); 这里一般绑定是对于服务端而言而客户端无需绑定系统自己根据分配进行绑定然而一般服务端是允许它所可以识别的多个对应ip发起请求的因此不建议绑死固定ip后面会讲述 对于ip地址由本地的字符串样式转化成网络四字节序列模拟操作 上面过程只需要了解即可
这里我们提供了inet_addr及inet_pton/addr_ntop等接口大大减轻了我们的负担 2.1.3 接收信息之recvfrom接口 #include sys/types.h#include sys/socket.hssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); 一般使用的阻塞式用法成功就返回读到的个数失败就小于0下面我们结合使用来理解下各个参数 我们拿到发送方的ipport是网络序列可以转化成本地进行查看 2.1.4发送信息之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); 成功返回0失败就小于0和上面的那个recvfrom差不多但是上面传递的sockaddr_in是输出型参数这里是输入型参数 一些细节地方注意一下比如取地址等等一般只有需要修改的时候才会取地址如len但是sockaddr无论咋样都是传递地址 如; 上面这些接口对应我们的udp通信就够用了其他的就是相关其他转化接口了我们会在后面书写通信代码的时候提到如htons系列inet_addr等
2.2UDP通信特点及注意事项
UDP通信特性 udp sockfd既可以读又可以写。UDP通信其实是全双工的只要有srcdes的ipport就可以发送这里只有接收缓冲区无发送缓冲区对于udp和tcp不同后面讲的tcp会讲解如何处理只要发了多少就会一次性读完不会出现“粘报”等情况 UDP通信时注意事项 这里我们拿客户端然后用des的ip和port服务端绑定的就可以给服务端发信息客户端本身就绑定了对应服务器的des ip port等等服务端与客户端是一家公司写的 公网IP其实没有配置到你的IP上。公网IP无法被直接bind我们bind的要么是本地环回要么就是子网ip ①客户端是不需要手动绑定: os自己认识用户端机器的ip随机会生成端口号port自动绑定与解除;故无需bind/程序终止立刻解除故查不到状态 主机换了个区域的网络通常会获得一个对应新网络的子网IP;因此如果我们在某个区域进行网络通信;就会把这个区域分配的子网ipport绑定然后通信;如果我们还要在另一个不同的网络区域通信那么就又要解绑再次更换ipport了有点麻烦!!!----因此oS就支持了自动分配﹐(因为我们如果给服务器进行网络通信--因此客户端就会首先根据对应主机所在网络区域分配的ip然后进行自己的绑定--再拿到对应客户端内置的服务端的iptport进行发送即可)
下面我们举一个形象例子帮助理解下 你进入图书馆时不需要自己去指定要去哪个书架手动绑定IP地址)以及具体要坐在书架的什么位置看书(手动绑定端口)。因为图书馆有管理员他们会根据图书馆的空间使用情况和你的需求给你安排一个合适的书架和座位这就像网络中的 DHCP 服务器和操作系统它们会自动为客户端分配可用的IP地址和端口号。 ②一般服务端是不能直接绑定特定ip的
如果绑定了:给服务端发信息就只能通过这个绑定的ipport进行了! 但是一般这么用服务端所在的主机会为自己的客户端内置好自己的主机的一些ip;然后当客户端去给它发信息就可以拿着不同的ip(但是终归是服务端所在同一主机的ip)去给服务端发送信息;服务端识别到属于对应ip族;然后port也相同故可以接收.... 2.3实现基于UDP的server-client简单通信
下面我们就基于上面所介绍的接口函数以及注意事项来完成简单通信在我们书写代码前先看一下这个模型的形象图 下面我们就拿字节旗下的app如抖音等等基于这个模型解释下 比如用户在抖音 ...字节产品的app用户端进行访问自己的数据(是首先要和字节的服务器进行网络通信的;然后服务器发送过来);此时的网络通信就可以理解成上面所讲的;首先因为这些app是字节的肯定内置了字节对服务器的ip(可能不同;但是都是可以找到对应的服务器【由于服务器绑定的是INADDR ANY】);然后0S根据自己所在网络ip进行客户端绑定;然后进行通信;最后服务器再给返回来;然后可能有多个用户端进行访问也是可以同时访问成功服务器拿到数据的!!! 多个app同时进行也是一样的(套接字最终绑定的是进程) 测试效果 代码实现
详细说明见代码超详细注释
主要代码文件部分
udpclient.cc:
#include iostream
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include string.h
#include memory
#include log.hppusing namespace std;
int main(int argc, char *argv[])
{if (argc ! 3){cerr please use: argv[0] server_ip server_port std::endl;return 1;}// 创建套接字int sd socket(AF_INET, SOCK_DGRAM, 0); // 网络通信/用户数据报if (sd 0)use_log(loglevel::DEBUG) socket failure!;use_log(loglevel::DEBUG) socket success!;// os自己认识用户端机器的ip随机会生成端口号port自动绑定与解除;故无需bind/程序终止立刻解除故查不到状态string ip argv[1]; // string内置机制不会直接拷贝指针uint16_t port stoi(argv[2]); // 先构造string//初始化des套接字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());
//死循环式等待给服务端发送信息 while (1){string mess;getline(cin, mess);socklen_t len sizeof(local);//发送int so sendto(sd, mess.c_str(), mess.size(), 0, (sockaddr *)local, len); // 输入型参数if (so 0)use_log(loglevel::DEBUG) sendto failure!;//接收char buff[1024] {0};ssize_t rm recvfrom(sd, buff, sizeof(buff) - 1, 0, (sockaddr *)local, len);if (rm 0) use_log(loglevel::DEBUG) recvfrom failure!;cout$server say: buffendl;;}
}
udpserver.cc:
#includeudpserver.hppstring echo_repeat(string mess){string ret;retreturn ;retmess;return ret;}
int main(int argc ,char* argv[]){// if(argc ! 3)// {// std::cerr please use: argv[0] ip port std::endl;// return 1;// }if(argc ! 2){std::cerr please use: argv[0] port std::endl;return 1;}consolestrategy;//string ipargv[1];//string内置机制不会直接拷贝指针// uint16_t portstoi(argv[2]);//先构造stringuint16_t portstoi(argv[1]);// unique_ptrudpserver urmake_uniqueudpserver(ip,port);//unique_ptrudpserver urmake_uniqueudpserver(port);//回调unique_ptrudpserver urmake_uniqueudpserver(port,echo_repeat);ur-init();ur-start();}
udpserver.hpp:
#include iostream
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include string.h
#includememory
#includefunctional
#include log.hpp
using namespace std;
const int delfd -1;using func_tfunctionstring(string);
// consolestrategy; 不允许全局直接使用操作只允许定义声明等等class udpserver
{
public:// udpserver(string ip, uint16_t port) : _ip(ip), _port(port),// _isrunning(0), _socketfd(delfd) {}//这里服务器不能绑死否则只能接受指定的主机发来的信息了//udpserver( uint16_t port) : _port(port), _isrunning(0), _socketfd(delfd) {}udpserver( uint16_t port,func_t func) : _port(port), _isrunning(0), _socketfd(delfd),_func(func) {}void init(){// 1创建套接字_socketfd socket(AF_INET, SOCK_DGRAM, 0); // 网络通信/用户数据报if (_socketfd 0)use_log(loglevel::DEBUG) socket failure!;use_log(loglevel::DEBUG) socket success!;// 2socket绑定信息sockaddr_in local;// char sin_zero[8];// 填充字节使 sockaddr_in 和 sockaddr 长度相同bzero(local, sizeof(local));local.sin_family AF_INET;local.sin_port htons(_port); // 把对应的16位转化成网络序列// 内置的把string转化成大端类型的网络序列也可手动操作来回转化比较麻烦系统直接提供// ntohs()网络序列转回本地//. local.sin_addr.s_addr inet_addr(_ip.c_str());local.sin_addr.s_addr INADDR_ANY ;// 或者直接输入0可以理解成匹配同一主机的不同ip的任意des-port相同的不同进程发来的信息int n bind(_socketfd, (sockaddr *)local, sizeof(local));if (n 0)use_log(loglevel::DEBUG) bind failure!;use_log(loglevel::DEBUG) bind success!;}void start(){// 服务器用不挂掉-死循环_isrunning 1;while (_isrunning){char buff[1024] {0};sockaddr_in per;socklen_t len sizeof(per); // 套接字长度就是字节数ssize_t rm recvfrom(_socketfd, buff, sizeof(buff) - 1, 0, (sockaddr *)per, len); // 输出型参数故取地址if (rm 0) use_log(loglevel::DEBUG) recvfrom failure!;buff[rm]0;string per_addr inet_ntoa(per.sin_addr);uint16_t per_portntohs(per.sin_port);cout $client :[addr: per_addr port: per_port ] say: buffendl;//string resserver say:;// resbuff;//int sosendto(_socketfd,res.c_str(),res.size(),0,(sockaddr *)per,len);//这里接收两个全都对套接字用的是指针继承多态效果string ans _func(buff);int sosendto(_socketfd,ans.c_str(),ans.size(),0,(sockaddr *)per,len);//这里接收两个全都对套接字用的是指针继承多态效果//输入型参数if (so 0) use_log(loglevel::DEBUG) sendto failure!;}}~udpserver(){}private://string _ip;uint16_t _port;int _socketfd;bool _isrunning;func_t _func;
};
间接包含文件
log.hpp:
#ifndef __LOG__
#define __LOG__
#include sys/types.h
#include unistd.h
#include iostream
#include string
#include memory
#include fstream
#include filesystem
#include sstream
#includectime
#includecstdio
#include mutex.hpp
using namespace std;
#define gsep \r\n
// 基类
class Logstrategy
{
public:Logstrategy() {}virtual void synclog(const string message) 0;~Logstrategy() {}
};// 控制台打印日志class consolelogstrategy : public Logstrategy
{public:consolelogstrategy() {}void synclog(const string message) override{// 加锁完成多线程互斥{mutexguard md(_mutex);cout message gsep;}}~consolelogstrategy() {}private:mutex _mutex;
};// 自定义文件打印日志
const string P ./log;
const string F my.log;
class fileLogstrategy : public Logstrategy
{public:fileLogstrategy(const string path P, const string file F) : _path(path), _file(file){// 如果指定路径(目录)不存在进行创建否则构造直接返回{mutexguard md(_mutex);if (filesystem::exists(_path))return;try{filesystem::create_directories(_path);}catch (filesystem::filesystem_error e){cout e.what() gsep;}}}void synclog(const string message) override{// 得到指定文件名{mutexguard md(_mutex);string name _path (_path.back() / ? : /) _file;// 打开文件进行写入ofstream out(name, ios::app); // 对某文件进行操作的类对象if (!out.is_open())return; // 成功打开out message gsep;out.close();}}~fileLogstrategy() {}private:string _path;string _file;mutex _mutex;
};// 用户调用日志指定打印
// 日志等级
enum class loglevel
{DEBUG,INFO,WARNING,ERROR,FATAL
};
// 完成枚举值对应由数字到原值转化
string trans(loglevel lev)
{switch (lev){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 ERROR;}return;
}// 从时间戳提取出当前时间
string gettime()
{time_t curtimetime(nullptr);struct tm t;localtime_r(curtime,t);char buff[1024];sprintf(buff,%4d-%02d-%02d %02d:%02d:%02d,t.tm_year1900,//注意struct tm成员性质t.tm_mon1,t.tm_mday,t.tm_hour,t.tm_min,t.tm_sec);return buff;}
class Log
{
public:// Log刷新策略void console() { _fflush_strategy make_uniqueconsolelogstrategy(); }void file() { _fflush_strategy make_uniquefileLogstrategy(); }Log(){// 默认是控制台刷新console();}// 我们想让一个类重载了支持连续的输入并且希望每行结束就进行刷新因此这个meesage类// 析构就可以执行刷新--内部类天然就是外部类的友元类可以访问外部类所有成员变量及函数class Logmess{public:Logmess(loglevel lev, string filename, int line, Log log) : _lev(lev),_time(gettime()), _pid(getpid()), _filename(filename), _log(log), _linenum(line){stringstream ss;ss [ _time ] [ trans(_lev) ] [ _pid ] [ _filename ] [ _linenum ] -- ;_mergeinfoss.str();}templateclass TLogmess operator (const T data){stringstream ss;ssdata;_mergeinfo ss.str();return *this;}~Logmess(){_log._fflush_strategy-synclog(_mergeinfo);}private:loglevel _lev;string _time;pid_t _pid;string _filename;int _linenum;string _mergeinfo;Log _log;};Logmess operator()(loglevel l,string f,int le){ //返回的是匿名对象临时对象--也就是作用完当前行//执行完logmess的 后自动调用logmess的析构也就是直接策略打印 return Logmess(l,f,le,*this);}~Log() {}private:unique_ptrLogstrategy _fflush_strategy;
};Log l;#define use_log(x) l(x,__FILE__,__LINE__)//自动判断是哪行哪个文件#define filestrategy l.file()#define consolestrategy l.console()
#endif
Makefile:
.PHONY:all
all:udpclient udpserver
udpclient:udpclient.ccg -o $ $^ -stdc17
udpserver:udpserver.ccg -o $ $^ -stdc17
.PHONY:clean
clean:rm -f udpclient udpserver
mutex.hpp:
#pragma once
#includepthread.h
//封装锁
class mutex{public:mutex(){int n pthread_mutex_init(_mutex,nullptr);(void)n;}void Lock(){ pthread_mutex_lock(_mutex);}void Unlock(){ pthread_mutex_unlock(_mutex);}pthread_mutex_t*getmutex(){return _mutex;}~mutex(){int n pthread_mutex_destroy(_mutex);(void)n;}
private: pthread_mutex_t _mutex;
};
//自动上锁与解锁
class mutexguard{public://初始化为上锁;mutexguard(mutex mg):_mg(mg){ _mg.Lock() ; }//引用//析构为解锁~mutexguard(){_mg.Unlock() ; }private:mutex _mg;//注意引用确保不同线程上锁与解锁的时候拿到同一把锁不能是直接赋值
};2.4基于UDP实现的server-client简单通信改造的英译汉翻译功能:
这里实现翻译的功能只不过是给服务端接收到信息进行多了一个回调的函数来完成的所以改动不大相当于加个回调方法即可。 实现思路 首先服务端进行词典的加载也就是调用査询的函数内部把对应的英语和汉语的对应关系放入map里;然后用户端给dict. hpp:服务端发送对应的英文串;然后服务端拿着对应的英文串通过回调去査询函数寻找second;找到了就sendto回去对应second:否则sendto none!!! 下面我们增加了个翻译功能的类使得能从指定文件中加载对应映射关系以及处理功能等 测试效果
先loading词典到本地 下面等待用户端输入进行查询发出 代码实现 主要代码部分
dict.hpp:
#pragma once
#include iostream
#include fstream
#include string
#include unordered_map
#include log.hpp
#include addr.hpp
using namespace std;
const string sep : ;
string pathname ./dict.txt;
class dict
{
public:dict(const string p pathname) : _pathname(p) {}bool loaddict(){ifstream in(_pathname);if (!in.is_open()){use_log(loglevel::DEBUG) 打开字典: _pathname 错误;return false;}string line;while (getline(in, line)){int pos line.find(sep);if(posstring::npos) { use_log(loglevel::DEBUG) 当前加载错误继续向下加载;continue;} string word line.substr(0, pos);string chinese line.substr(pos 2,string::npos);if (word.size() 0 || chinese.size() 0){use_log(loglevel::DEBUG) 加载错误;continue;}_dict.insert({word, chinese});use_log(loglevel::DEBUG) 成功加载line;;}in.close();return true;}string translate(const string src, inetaddr client)//const类型对象只能调用非const修饰的成员函数等{if (!_dict.count(src)){use_log(loglevel::DEBUG) 有用户进入到了翻译模块, client: [ client.ip() : client.port() ]# 查询 src -None;return None;}auto iter _dict.find(src);use_log(loglevel::DEBUG) 有用户进入到了翻译模块, client: [ client.ip() : client.port() ]# 查询 src - iter-second;return iter-second;}~dict() {}private:string _pathname;unordered_mapstring, string _dict;
};
dict.txt:
robot: 机器人
flower: 花
tree: 树
table: 桌子
chair: 椅子
cup: 杯子
bowl: 碗
spoon: 勺子
knife: 刀
fork: 叉子
music: 音乐
movie: 电影
game: 游戏
park: 公园
zoo: 动物园
school: 学校
hospital: 医院
restaurant: 餐馆
supermarket: 超市
bank: 银行
post office: 邮局
library: 图书馆
street: 街道
road: 路
mountain: 山
river: 河流
lake: 湖泊
beach: 海滩
cloud: 云
rain: 雨下雨
snow: 雪下雪
wind: 风
sun: 太阳
moon: 月亮
star: 星星
sweet: 甜的
sour: 酸的
bitter: 苦的
spicy: 辣的
cold: 寒冷的冷的
hot: 炎热的热的
warm: 温暖的
cool: 凉爽的
big: 大的
small: 小的
long: 长的
short: 短的矮的
fat: 胖的肥的
thin: 瘦的薄的
tall: 高的
low: 低的
fast: 快的快速地
slow: 慢的缓慢地
easy: 容易的
difficult: 困难的
beautiful: 美丽的
ugly: 丑陋的
kind: 善良的
cruel: 残忍的
clever: 聪明的
stupid: 愚蠢的
strong: 强壮的
weak: 虚弱的
open: 打开开着的
close: 关闭关着的
clean: 干净的清洁
dirty: 脏的
new: 新的
old: 旧的老的
young: 年轻的
old-fashioned: 老式的过时的
modern: 现代的时髦的
expensive: 昂贵的
cheap: 便宜的
light: 轻的灯
heavy: 重的
empty: 空的
full: 满的
remember: 记得记住
forget: 忘记
begin: 开始
end: 结束结尾
start: 开始出发
stop: 停止阻止
give: 给
take: 拿取带走
buy: 买
sell: 卖
: 起飞
bug: borrow: 借入
lend: 借出
arrive: 到达
leave: 离开留下
find: 找到发现
lose: 丢失失去
dream: 梦想做梦
think: 思考认为
believe: 相信认为
doubt: 怀疑
hope: 希望
wish: 愿望希望祝愿
addr.hpp:
#pragma once
#include iostream
#include string
#include sys/socket.h
#include sys/types.h
#include arpa/inet.h
#include netinet/in.h
using namespace std;
class inetaddr
{public:inetaddr(const sockaddr_in addr) : _addr(addr){_port ntohs(_addr.sin_port);_ip inet_ntoa(_addr.sin_addr);}string ip() { return _ip; }uint16_t port() { return _port; }~inetaddr() {}private:sockaddr_in _addr;string _ip;uint16_t _port;
};
udpclient.cc:
#include iostream
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include string.h
#include memory
#include log.hppusing namespace std;
int main(int argc, char *argv[])
{if (argc ! 3){cerr please use: argv[0] server_ip server_port std::endl;return 1;}// 创建套接字int sd socket(AF_INET, SOCK_DGRAM, 0); // 网络通信/用户数据报if (sd 0)use_log(loglevel::DEBUG) socket failure!;use_log(loglevel::DEBUG) socket success!;// os自己认识用户端机器的ip随机会生成端口号port自动绑定与解除;故无需bind/程序终止立刻解除故查不到状态string ip argv[1]; // string内置机制不会直接拷贝指针uint16_t port stoi(argv[2]); // 先构造string//初始化des套接字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());
//死循环式等待给服务端发送信息 while (1){string mess;getline(cin, mess);socklen_t len sizeof(local);//发送int so sendto(sd, mess.c_str(), mess.size(), 0, (sockaddr *)local, len); // 输入型参数if (so 0)use_log(loglevel::DEBUG) sendto failure!;//接收char buff[1024] {0};ssize_t rm recvfrom(sd, buff, sizeof(buff) - 1, 0, (sockaddr *)local, len);if (rm 0) use_log(loglevel::DEBUG) recvfrom failure!;cout查询结果是 buffendl;;}
}
udpserver.cc:
#includeudpserver.hpp
#include addr.hpp
#includedict.hpp
// string echo_repeat(string mess,inetaddr addr){
// string ret;
// retreturn ;
// retmess;
// return ret;// }
int main(int argc ,char* argv[]){// if(argc ! 3)// {// std::cerr please use: argv[0] ip port std::endl;// return 1;// }if(argc ! 2){std::cerr please use: argv[0] port std::endl;return 1;}consolestrategy;//string ipargv[1];//string内置机制不会直接拷贝指针// uint16_t portstoi(argv[2]);//先构造stringuint16_t portstoi(argv[1]);// unique_ptrudpserver urmake_uniqueudpserver(ip,port);//unique_ptrudpserver urmake_uniqueudpserver(port);//回调//1加载翻译词典dict dt;dt.loaddict();//2服务端启动接受信息后进行查找功能unique_ptrudpserver urmake_uniqueudpserver(port,[dt](string mess,inetaddr ar)-string{return dt.translate(mess,ar);});ur-init();ur-start();}
udpserver.hpp:
#include iostream
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include string.h
#includememory
#includefunctional
#include log.hpp
#include addr.hpp
using namespace std;
const int delfd -1;using func_tfunctionstring(string,inetaddr);
// consolestrategy; 不允许全局直接使用操作只允许定义声明等等class udpserver
{
public:// udpserver(string ip, uint16_t port) : _ip(ip), _port(port),// _isrunning(0), _socketfd(delfd) {}//这里服务器不能绑死否则只能接受指定的主机发来的信息了//udpserver( uint16_t port) : _port(port), _isrunning(0), _socketfd(delfd) {}udpserver( uint16_t port,func_t func) : _port(port), _isrunning(0), _socketfd(delfd),_func(func) {}void init(){// 1创建套接字_socketfd socket(AF_INET, SOCK_DGRAM, 0); // 网络通信/用户数据报;返回文件描述符if (_socketfd 0)use_log(loglevel::DEBUG) socket failure!;use_log(loglevel::DEBUG) socket success!;// 2socket绑定信息sockaddr_in local;// char sin_zero[8];填充字节使 sockaddr_in 和 sockaddr 长度相同//套接字结构体初始化bzero(local, sizeof(local));local.sin_family AF_INET;//网络通信local.sin_port htons(_port); // 把对应的16位转化成网络序列// 内置的把string转化成大端类型的网络序列也可手动操作来回转化比较麻烦系统直接提供// ntohs()网络序列转回本地//. local.sin_addr.s_addr inet_addr(_ip.c_str());local.sin_addr.s_addr INADDR_ANY ;// 或者直接输入0可以理解成匹配同一主机的不同ip多个网卡的任意des-port相同的不同进程发来的信息int n bind(_socketfd, (sockaddr *)local, sizeof(local));//程序终止bind的网络信息自动解除if (n 0)use_log(loglevel::DEBUG) bind failure!;use_log(loglevel::DEBUG) bind success!;}void start(){// 服务器用不挂掉-死循环_isrunning 1;while (_isrunning){char buff[1024] {0};sockaddr_in per;socklen_t len sizeof(per); // 套接字长度就是字节数ssize_t rm recvfrom(_socketfd, buff, sizeof(buff) - 1, 0, (sockaddr *)per, len); // 输出型参数故取地址if (rm 0) use_log(loglevel::DEBUG) recvfrom failure!;buff[rm]0;string per_addr inet_ntoa(per.sin_addr);uint16_t per_portntohs(per.sin_port);cout $client :[addr: per_addr port: per_port ] say: buffendl;//string resserver say:;// resbuff;//int sosendto(_socketfd,res.c_str(),res.size(),0,(sockaddr *)per,len);//这里接收两个全都对套接字用的是指针继承多态效果//回调函数最后传给clientstring ans _func(buff,per);int sosendto(_socketfd,ans.c_str(),ans.size(),0,(sockaddr *)per,len);//这里接收两个全都对套接字用的是指针继承多态效果//输入型参数if (so 0) use_log(loglevel::DEBUG) sendto failure!;}}~udpserver(){}private://string _ip;uint16_t _port;int _socketfd;bool _isrunning;func_t _func;
};
间接包含部分
就是上面展示的log.hpp/mutex.hpp/Makefile等等这里就不展示了
2.5基于UDP实现的server-clients通信改造多人聊天室服务端多线程版本
实现思路
可能存在多个ip不同的用户端给服务端发信息;而服务端需要全部给这些连接服务端的用户端全部发送一遍服务端收到的信息)--〉客户端任务:给服务端发信息;服务端任务:全部转给客户端一遍。妳因为客户端如果先recv再sendto;那么这里会阻塞住;因此效果不太好
我们想要的是只有服务端收到信息就发给客户端--因此客户端的接受和发送两个线程同时进行。而服务端一个线程来回处理的时候只能处理完一个发送任务再去按收效率太慢了因此改成线程池;但是对应这个route;它底层需要插入连按客户的addr;因此我们这里用的vector;临界资源--线程不安全;加个锁就ok了(规定用户只要QUIT;然后服务端转发一遍;用用户端自动结束recv和sendto线程! ! ! 因此我们把服务端多线程化去执行客户端发来的信息然后去群发应用多线程但是储存客户ipport的数组是全局的线程不安全故还需加锁【引入多线程就要考虑是否加锁问题】 下面我们 采用线程池来模拟多线程但是用户上限有限制,群发调用回调方法进行发送:
效果展示 当多个客户端进行连接通信的时候类似这样 这里我们采用一个机器输入另一个窗口进行显示群发消息因此又对客户端标准错误进行了重定向到 另一台机器 客户端recvfrom处 echo 1/dev/pts/2//查询属于哪台机器 ./udpclient 127.0.0.1 8080 2/dev/pts/2//进行对应重定向 因此我们就可以采取上述方式进行执行客户端程序 效果 代码实现
主要代码部分
udpclient.cc:
#include iostream
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include string.h
#include memory
#include log.hpp
#includethread.hpp
using namespace std;
using namespace td;
//先设置成全局的方便函数内使用
string ip;
uint16_t port;
pthread_t id;//方便后序退出时候回收对应线程
int flag0;//标记用户QUIT后方便终止读与收两个线程
int sd;
void Recv(){while(1){char buff[1024] {0};sockaddr_in other;socklen_t len;ssize_t rm recvfrom(sd, buff, sizeof(buff) - 1, 0, (sockaddr *)other, len);if (rm 0) use_log(loglevel::DEBUG) recvfrom failure!;buff[rm]0;cerrbuffendl;/////////////////方便后序重定向}
}void Send(){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());socklen_t len sizeof(local);string tip我上线了 ;int so sendto(sd, tip.c_str(), tip.size(), 0, (sockaddr *)local, len); //coutsoendl;while (1){ //coutPlease Enter#endl;string mess;getline(cin, mess);//发送int so sendto(sd, mess.c_str(), mess.size(), 0, (sockaddr *)local, len); // 输入型参数if (so 0) use_log(loglevel::DEBUG) sendto failure!;if(messQUIT) {flag1;//终止读线程pthread_cancel(id);}}
}
int main(int argc, char *argv[])
{if (argc ! 3){cerr please use: argv[0] server_ip server_port std::endl;return 1;}// 创建套接字sd socket(AF_INET, SOCK_DGRAM, 0); // 网络通信/用户数据报if (sd 0)use_log(loglevel::DEBUG) socket failure!;use_log(loglevel::DEBUG) socket success!;// os自己认识用户端机器的ip随机会生成端口号port自动绑定与解除;故无需bind/程序终止立刻解除故查不到状态ip argv[1]; port stoi(argv[2]); //创建两个线程去执行接收和发送两个任务;Thread t1(Recv);Thread t2(Send);t1.start();t2.start();idt2.Id();t2.join();if(flag1) pthread_cancel(t1.Id());t1.join();}udpserver.cc:
#includeudpserver.hpp
#include addr.hpp
#includeroute.hpp
#includethreadpool.hpp
#includefunctional
// string echo_repeat(string mess,inetaddr addr){
// string ret;
// retreturn ;
// retmess;
// return ret;// }using fnfunctionvoid();
int main(int argc ,char* argv[]){// if(argc ! 3)// {// std::cerr please use: argv[0] ip port std::endl;// return 1;// }if(argc ! 2){std::cerr please use: argv[0] port std::endl;return 1;}consolestrategy;//string ipargv[1];//string内置机制不会直接拷贝指针// uint16_t portstoi(argv[2]);//先构造stringuint16_t portstoi(argv[1]);// unique_ptrudpserver urmake_uniqueudpserver(ip,port);//unique_ptrudpserver urmake_uniqueudpserver(port);//回调Route r;//2服务端启动接受信息后进行查找功能//单线程
// unique_ptrudpserver urmake_uniqueudpserver(port,[r](int fd,string mess,inetaddr ar){
// r.messroute(fd,mess,ar);
// });//线程池版本auto tpThreadpoolfn::getinstance();unique_ptrudpserver urmake_uniqueudpserver(port,[r,tp](int fd, string mess,inetaddr ar){//因此可以把messroute绑定成无参对象用bindfn tkstd::bind(Route::messroute,r,fd,mess,ar);//此时就不用给线程池任务传参了这里this指针必须给出值tp-equeue(tk);//只允许传递无参数无返回值的对象或者函数进行处理
});ur-init();ur-start();}
udpserver.hpp:
#pragma once
#include iostream
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include string.h
#include memory
#include functional
#include log.hpp
#include addr.hpp
using namespace std;
const int delfd -1;using func_t functionvoid(int, string, inetaddr);
// consolestrategy; 不允许全局直接使用操作只允许定义声明等等class udpserver
{
public:// udpserver(string ip, uint16_t port) : _ip(ip), _port(port),// _isrunning(0), _socketfd(delfd) {}// 这里服务器不能绑死否则只能接受指定的主机发来的信息了// udpserver( uint16_t port) : _port(port), _isrunning(0), _socketfd(delfd) {}udpserver(uint16_t port, func_t func) : _port(port), _isrunning(0), _socketfd(delfd), _func(func) {}void init(){// 1创建套接字_socketfd socket(AF_INET, SOCK_DGRAM, 0); // 网络通信/用户数据报;返回文件描述符if (_socketfd 0)use_log(loglevel::DEBUG) socket failure!;use_log(loglevel::DEBUG) socket success!;// 2socket绑定信息sockaddr_in local;// char sin_zero[8];填充字节使 sockaddr_in 和 sockaddr 长度相同// 套接字结构体初始化bzero(local, sizeof(local));local.sin_family AF_INET; // 网络通信local.sin_port htons(_port); // 把对应的16位转化成网络序列// 内置的把string转化成大端类型的网络序列也可手动操作来回转化比较麻烦系统直接提供// ntohs()网络序列转回本地//. local.sin_addr.s_addr inet_addr(_ip.c_str());local.sin_addr.s_addr INADDR_ANY;// 或者直接输入0可以理解成匹配同一主机的不同ip多个网卡的任意des-port相同的不同进程发来的信息int n bind(_socketfd, (sockaddr *)local, sizeof(local)); // 程序终止bind的网络信息自动解除if (n 0)use_log(loglevel::DEBUG) bind failure!;use_log(loglevel::DEBUG) bind success!;}void start(){// 服务器用不挂掉-死循环_isrunning 1;while (_isrunning){ // cout再次准备endl;char buff[1024] {0};sockaddr_in per;socklen_t len sizeof(per);// cout准备读信息endl; // 套接字长度就是字节数ssize_t rm recvfrom(_socketfd, buff, sizeof(buff) - 1, 0, (sockaddr *)per, len); // 输出型参数故取地址if (rm 0)use_log(loglevel::DEBUG) recvfrom failure!;buff[rm] 0;string per_addr inet_ntoa(per.sin_addr);uint16_t per_port ntohs(per.sin_port);// cout $client :[addr: per_addr port: per_port ] say: buffendl;// string resserver say:;// resbuff;// int sosendto(_socketfd,res.c_str(),res.size(),0,(sockaddr *)per,len);//这里接收两个全都对套接字用的是指针继承多态效果// 回调函数最后传给聊天室所有用户inetaddr ir(per);_func(_socketfd, buff, ir);}}~udpserver(){}private:// string _ip;uint16_t _port;int _socketfd;bool _isrunning;func_t _func;
};
addr.hpp:
#pragma once
#include iostream
#include string
#include sys/socket.h
#include sys/types.h
#include arpa/inet.h
#include netinet/in.h
#includecstring
using namespace std;
class inetaddr
{public:// 网络序列转主机inetaddr(sockaddr_in addr) : _addr(addr){_port ntohs(_addr.sin_port);char buff[1024];inet_ntop(AF_INET,_addr.sin_addr,buff,sizeof(_addr));_ipbuff;}// 客户端主机转网络inetaddr(const 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);}// 服务端主机转网络inetaddr( uint16_t port) : _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);}sockaddr_in *addrptr() { return _addr; }socklen_t addrlen(){return sizeof(_addr);}string ip() { return _ip; }uint16_t port() { return _port; }bool operator(const inetaddr sockin){return _ip sockin._ip _port sockin._port;}sockaddr_in sockaddr() { return _addr; } // 这里返回引用否则右值无地址可取sendtostring get_userinfo(){return ip() : to_string(port());}~inetaddr() {}private:sockaddr_in _addr;string _ip;uint16_t _port;
};
route.hpp:
#pragma once
#include iostream
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include string.h
#include memory
#include functional
#include vector
#include log.hpp
#include addr.hpp
using namespace std;class Route
{private:bool is_exit(inetaddr ir){for(int i0;i_peers.size();i){if(_peers[i]ir) return 1;}return 0;}void addpeer(inetaddr ir){_peers.push_back(ir);use_log(loglevel::DEBUG)成功添加一个用户ir.get_userinfo();}void deletepeer(inetaddr ir){for(auto iter_peers.begin();iter!_peers.end();iter ){if(*iterir){_peers.erase(iter);use_log(loglevel::DEBUG)成功删除一个用户ir.get_userinfo();}iter_peers.end()-1;//迭代器失效 再使用会coredump// break ;}}
public:Route() {}void messroute(int sockfd,string mess,inetaddr ir){mutexguard md(_m);//多线程互斥vector是共享的stl不是线程安全的if(!is_exit(ir)) addpeer(ir);string ans [ir.get_userinfo()] say: mess;for(autopeer:_peers){int sosendto(sockfd,ans.c_str(),ans.size(),0,(sockaddr *)(ir.sockaddr()),sizeof(ir.sockaddr()));if (so 0) use_log(loglevel::DEBUG) sendto failure!;}if(messQUIT) {deletepeer(ir); }}~Route() {}private:vectorinetaddr _peers;mutex _m;
};
间接包含部分
cond.hpp:
#pragma once
#include stdio.h
#include stdlib.h
#include string.h
#include iostream
#include unistd.h
#include vector
#include pthread.h
#include mutex.hppclass cond
{
public:cond() { pthread_cond_init(_cond, nullptr); }void Wait(mutex mx)
{
int n pthread_cond_wait(_cond, mx.getmutex());
(void)n;
}void notify(){int n pthread_cond_signal(_cond);(void)n;}void allnotify(){int n pthread_cond_broadcast(_cond);(void)n;}~cond() { pthread_cond_destroy(_cond); }private:pthread_cond_t _cond;
};
thread.hpp:
#ifndef THREAD_H
#define THREAD_H
#include iostream
#include string
#include pthread.h
#include cstdio
#include cstring
#include functional
#includeunistd.h
#includevector
#includequeue
using namespace std;namespace td
{static uint32_t num1; class Thread{using func_t functionvoid();public:Thread(func_t func) : _tid(0), _res(nullptr),_func(func), _isrunning(false),_isdetach(false){_nameThread-to_string(num);}static void *Routine(void *arg){Thread *self static_castThread*(arg);//需要查看是否进行了start前的detach操作pthread_setname_np(self-_tid, self-_name.c_str());// coutself-_name.c_str()endl;self-_isrunning1;if(self-_isdetach) pthread_detach(self-_tid);self-_func();return nullptr;}bool start(){if(_isrunning) return false;int n pthread_create(_tid, nullptr, Routine, this);if (n ! 0){//cerr create thread error: strerror(n) endl;return false;}else{//cout _name create success endl;return true;}}bool stop(){if(_isrunning){int n pthread_cancel(_tid);if (n ! 0){//cerr cancel thread error: strerror(n) endl;return false;}else{_isrunning false;// cout _name stop endl;return true;}}return false;}bool detach(){if(_isdetach) return false;if(_isrunning)pthread_detach(_tid);//创建成功的线程进行分离操作_isdetach1;//未创线程进行分离只进行标记return true;}bool join(){if(_isdetach) {// cout线程 _name已经被分离;不能进行joinendl;return false;}//只考虑运行起来的线程了int n pthread_join(_tid, _res);if (n ! 0){//std::cerr join thread error: strerror(n) std::endl;}else{//std::cout join success std::endl;}return true;}pthread_t Id() {return _tid;}~Thread() {}private:pthread_t _tid;string _name;void *_res;func_t _func;bool _isrunning;bool _isdetach;};
}#endif
threadpoll.hpp:
#pragma once
#include log.hpp
#include cond.hpp
#include thread.hpp
using namespace td;
const int N 5;
template class T
class Threadpool
{
private:Threadpool(int num N) : _size(num),_sleepingnums(0),_isrunning(0){for (int i 0; i _size; i){_threads.push_back(Thread([this](){ this-handletask(); }));}}// 单例只允许实例出一个对象Threadpool(const ThreadpoolT t) delete;ThreadpoolT operator(const ThreadpoolT t) delete;void Start(){if (_isrunning)//勿忘标记位return;_isrunning true;for (int i 0; i _size; i){// use_log(loglevel::DEBUG) 成功启动一个线程;;_threads[i].start();}}public:static ThreadpoolT *getinstance()//必须采用静态(不创建对象的前提下进行获得类指针){if (_ins nullptr) //双重判断--假设一个线程很快完成单例化然后后面的一群线程正好来了如果没有双层判断就会阻塞一个个发现不是空返回_ins//非常慢为了提高效率这样就不用加锁在一个个判断了还能保证线程安全。{{mutexguard mg(_lock);//静态锁if (_ins nullptr){_ins new ThreadpoolT();use_log(loglevel::DEBUG) 创建一个单例;_ins-Start();//创建单例自启动}}}use_log(loglevel::DEBUG) 获得之前创建的一个单例;return _ins;}void stop()//不能立刻停止如果队列有任务还需要线程完成完然后从handl函数退出即可{mutexguard mg(_Mutex);//这里为了防止多线程调用线程池但是单例化杜绝了这点if (_isrunning){_isrunning 0;//因此只搞个标记use_log(loglevel::DEBUG) 唤醒所有线程;//被唤醒后没有抢到锁的线程虽然休眠但是不用再次唤醒了os内设它就会让它所只要出现就去竞争_Cond.allnotify();//万一还有其他线程还在休眠就需要都唤醒--全部子线程都要退出}return;}void join(){// mutexguard mg(_Mutex);这里不能加锁如果join的主线程快的话直接就拿到锁了// 即使唤醒子线程他们都拿不到锁故继续休眠等待锁而主线程join这一直拿着 锁等子线程// 故造成了----死锁问题// 但是可能出现多线程同时访问后面把它设置单单例模式就好了use_log(loglevel::DEBUG) 回收线程;for (int i 0; i _size; i)_threads[i].join();}bool equeue(const T tk){mutexguard mg(_Mutex);if (_isrunning){_task.push(tk);if (_sleepingnums _size)_Cond.notify(); // 全休眠必须唤醒一个执行//use_log(loglevel::DEBUG) 成功插入一个任务并唤醒一个线程;return true;}return false;}void handletask(){ // 类似popqueuechar name[128];//在线程局部存储开不用加锁非全局就行pthread_getname_np(pthread_self(), name, sizeof(name));while (1){T t;{mutexguard gd(_Mutex);while (_task.empty() _isrunning)//休眠条件{_sleepingnums;_Cond.Wait(_Mutex);_sleepingnums--;// cout1endl;}if (_task.empty() !_isrunning)//醒来后发现符合退出条件就退出{use_log(loglevel::DEBUG) name 退出;break; // 代码块执行完了锁自动释放}t _task.front();_task.pop();}t();}}~Threadpool() {}private:vectorThread _threads;int _size;mutex _Mutex;cond _Cond;queueT _task;bool _isrunning;int _sleepingnums;//仅仅只是声明static ThreadpoolT *_ins;static mutex _lock;
};
//类内声明内外定义初始化
templateclass T
ThreadpoolT*ThreadpoolT ::_insnullptr;
templateclass T
mutex ThreadpoolT ::_lock;
剩下的就是mutex/log.hpp见上面代码展示这里就不重复了
优化关于网络序列和本机序列之间的转化
之前对port的网络本机序列转化 uint16 t per portntohs(per.sin_port) local.sin port htons( port); 之前对ip的网络本机序列转化 local.sin addr.s addr inet addr( ip.c str()); string per addr inet ntoa(per.sin_addr); 直接上结论 对port的操作可以不用变但是对于ip此时就要更换了 原因 对于这个ip之间转换是不安全的;也就是存存储在静态区的块地址;当多次调用它会被覆盖掉!此外由于静态区也不是线程安全的 虽然不需要手动释放内存(new);man 手册上说inet_ntoa 函数是把这个返回结果放到了静态存储区.这个时候不需要我们手动进行释放但是每次调用这个函数它都会从原来的位置进行覆盖 看一下效果 明显就出现问题了
因此对于ip的转换我们引入了新的函数(这里p就代表进程): #include arpa/inet.hint inet_pton(int af, const char *src, void *dst);#include arpa/inet.hconst char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
结合例子解释下 因此总结下当我们想port的网络本机之间转化就用ntohs/htons当ip的本机与网络转换就用inet_pton/inet_ntop! 这俩函数我们在上面实现聊天室的时候对于封装的addr.hpp内就重新应用了 其次就是对于服务端;也就是server.hpp我们是不允许拷贝等等的;因此需要禁用掉类似的接口--此时我们采取继承的方式;此时构建server的类的时候需要先构建它继承的基类;发现基类被禁用了直接报错--后续实现tcp的时候我们再采用 三·常用的网络指令
3.1ifconfig
使用该指令进行查看配置信息 上面的是子网ip下面的是本地环回ip
本地环回:要求c、s必须在一台机器上表明我们是本地通信client发送的数据不会被推送到网络而是在OS内部转一圈直接交给对应的服务器端如果被通信的端绑定的是这个环回地址: 再进行通信那么就是本地通信了;不会发送到网络经常用来进行网络代码的测试
子网ip由本地局域网LAN自动分配给连接的设备的ip我们进行本地实现udp通信就是拿它或者本地环回作为目的ip进行连接服务端的
3.2netstat
netstat 是一个用来查看网络状态的重要工具
用法netstat -【选项】 选项 n 拒绝显示别名 能显示数字的全部转化成数字 l 仅列出有在 Listen (监听) 的服務状态 p 显示建立相关链接的程序名 t (tcp)仅显示 tcp 相关选项 u (udp)仅显示 udp 相关选项 a (all)显示所有选项 默认不显示 LISTEN 相关-之后我们的TCP通信会用到 下面我们来演示下效果选项谁在前谁在后无影响
根据需要进行显示: 程序终止后自动解除这个信息: 3.3ping:
使用 ping 工具来测试本地计算机与服务器之间的网络连接情况。
下面我们测试一下 ping www.qq.com 这里了解下即可
3.4pidof:
在查看服务器的进程 id 时非常方便
用法 pidof [进程名]来查看对应pid 四·本篇小结: 通过本篇文章在有网络概念的基础上来更清楚认识网络通信是如何进行的关键ipport-socket之后又认识了相关socket相关地址结构体底层结构简单协议介绍以及一些网络通信udp的一些api接口后来基于UDP网络通信实现的简单的server-client之间的应答/词典翻译/多人聊天室等小项目最后补充了点相关小指令博主学习这块也是用了好几天然后整理笔记最近有时间复盘一下整理的博客欢迎大家阅读下期找时间更新TCP网络通信