当前位置: 首页 > news >正文

网络编程套接字(2): 简单的UDP网络程序

文章目录

  • 网络编程套接字(2): 简单的UDP网络程序
    • 3. 简单的UDP网络程序
      • 3.1 服务端创建
        • (1) 创建套接字
        • (2) 绑定端口号
        • (3) sockaddr_in结构体
        • (4) 数据的接收与发送
          • 接收
          • 发送
      • 3.2 客户端创建
      • 3.3 代码编写
        • (1) v1_简单发送消息
        • (2) v2_小写转大写
        • (3) v3_模拟命令行解释器
        • (4) v4_多线程版本的群聊系统
        • (5) v5_Windows与Linux配合聊天室

网络编程套接字(2): 简单的UDP网络程序

3. 简单的UDP网络程序

3.1 服务端创建

(1) 创建套接字

在这里插入图片描述

create an endpoint for communication: 创建用于通信的端点

头文件:#include <sys/types.h>         #include <sys/socket.h>函数原型:int socket(int domain, int type, int protocol);参数说明:第一个参数domain:   指定套接字的通信域第二个参数type:     指定套接字的服务类型(套接字的种类) 第三个参数protocol: 代表创建套接字的协议(默认为0),0,系统会自动判断是tcp还是udp返回值: 套接字创建成功: 返回一个文件描述符套接字创建失败: 返回-1, 并且设置错误码

关于socket参数详细介绍:

(1) domain: 指定套接字的通信域,相当于 struct sockaddr结构体的前16比特位(2字节)

在这里插入图片描述

domain的选项是以宏的形式给出的,我们直接选用即可。常用就是上面框住的两个:

  • AF_UNIX,本地通信
  • AF_INET(IPv4)或者 AF_INET6(IPv6),网络通信

(2) type: 指定套接字的服务类型

在这里插入图片描述

该参数的选项也是像domain一样以宏的形式给出,直接选用。常用的是上面两个:

  • SOCK_STREAM: 基于TCP的网络通信,流式套接字,提供的是流式服务(对应TCP的特点:面向字节流)

  • SOCK_DGRAM: 基于UDP的网络通信,套接字数据报,提供的用户数据报服务(对应UDP的特点:面向数据报)

(2) 绑定端口号

在这里插入图片描述

bind a name to socket:将名称绑定到套接字

头文件:#include <sys/types.h>          #include <sys/socket.h>函数原型:int bind(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);参数说明:第一个参数sockfd:  文件描述符, 即要绑定的套接字第二个参数addr:    网络相关的结构体, 包含IP地址、端口号等第三个参数addrlen: 传入结构体addr(第二个参数)的实际长度大小返回值:绑定成功: 返回0绑定失败: 返回-1,并且设置错误码

参数addr的类型是:struct sockaddr *,也就是如图的结构体

在这里插入图片描述

我们需要做的就是:定义一个 sockaddr_in 的结构体,即上图的第二个结构体,然后对该结构体进行内容填充,填完就把给结构体传给第二个参数addr,需要强制类型转换

(3) sockaddr_in结构体

在这里插入图片描述

  • __SOCKADDR_COMMON是一个宏

在这里插入图片描述

#define	__SOCKADDR_COMMON(sa_prefix) sa_family_t sa_prefix##family

sa_prefix:代表外面传入的参数sin_

sa_prefix##family:##代表合并拼接,意思是sa_prefix与family合并拼接

sa_prefix就是sin_,则sa_prefix##family表示sin_family

sa_family_t:代表16位整数

在这里插入图片描述

就是这16位地址类型

在这里插入图片描述

  • sin_port: 是当前服务器需要绑定的端口号,它的类型是 in_port_t,代表16位的整数

在这里插入图片描述

  • sin_addr: 代表IP地址,它的类型是一个in_addr的结构体,它里面的内容是32位的整数

在这里插入图片描述

  1. 关于这个IP地址:我们要传入字符串风格的,但是这里需要4字节整数风格,所以需要转化,比如"1.1.1.1"-> uint32_t,问:能不能强转呢?
    不能强转, 强转只能改变类型, 不改变二进制构成

  2. 我们转化完了还是本主机的4字节序列,需要网络序列,所以要将主机序列转化成为网络序列

上面的2步用 inet_addr函数就可以完成

在这里插入图片描述

  1. 但是我们的云服务器,或者一款服务器,一般不要指明某一个确定的IP

所以这里的ip地址我们填 INADDR_ANY,这是一个宏,代表 0.0.0.0,叫做任意地址绑定

  • sin_zero: 表示该结构体的填充字段(即上面讲的sin_family,sin_port,sin_addr.s_add)

总结: 未来使用这个函数时,需要所以填充:sin_family,sin_port,sin_addr.s_addr这3个字段,因为不关注其他字段,所以在填充之前需要对该结构体清空,我们可以采用 memset或 bzero函数来完成。

bind的作用:

上面如果我们只设置了sockaddr_in这个结构体,它只是在用户空间的特定函数栈帧上,不在内核中,所以bind的作用就是把文件字段进行绑定关联,这样这个文件就是网络文件

(4) 数据的接收与发送

接收

在这里插入图片描述

receive a message from a socket:从套接字接收消息

头文件: #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:(输出)src_addr结构体的长度返回值:成功: 返回实际读到的字节数失败: 失败返回-1,并设置错误码

socklen_t 是一个32位的无符号整数

  • 参数src_addr与addrlen:输入输出型参数

  • src_addr: 输入时传入对应套接字的接收缓冲区,输出时包含客户端的ip和port

  • addrlen: 输入时传入对应套接字的接收缓冲区,输出时表示实际输出的结构体大小

我们做的是定义一个 sockaddr_in 的结构体,把结构体传给参数src_addr,需要强制类型转换

发送

在这里插入图片描述

send a message on a socket: 在套接字上发送消息

头文件:#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表示阻塞式发送第五个参数dest_addr:下面解释第六个参数addrlen:dest_addr结构体的长度返回值:成功: 实际发送的字节数失败: 失败返回-1,并设置错误码
  • dest_addr和addrlen 是一个输入型参数

  • dest_addr:指向目的地址的结构体指针,表示要发给谁

  • addrlen:表示目的地址结构体的长度

我们做的是定义一个 sockaddr_in 的结构体,然后对该结构体进行内容填充,填完就把给结构体传给dest_addr**,需要强制类型转换**

3.2 客户端创建

还是3步:创建套接字,bind(不需要自己绑定,由OS自动分配),处理数据接收与发送

3.3 代码编写

这里一共提供5个版本的udp代码

err.hpp:这个代码是公用的后续不在给出

#pragma once
enum
{USAGE_ERR=1,SOCKET_ERR,BIND_ERR,
};

(1) v1_简单发送消息

客户端向服务端发送消息,服务端收到后再把消息发回给客户端

udp_server.hpp

#include<iostream>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include"err.hpp"
using namespace std;namespace ns_server
{const static uint16_t default_port=8080;class Udpserver{public:Udpserver(uint16_t port=default_port):port_(port){cout<<"server addr: "<<port_<<endl;}void InitServer(){// 1. 创建socket接口, 打开网络文件(本质)sock_=socket(AF_INET,SOCK_DGRAM,0);if(sock_<0){cerr<<"create socket error: "<<strerror(errno)<<endl;exit(SOCKET_ERR);}cout<<"create socket success: "<<sock_<<endl;   // sock_ = 3// 2. 给服务器指明IP地址和Port端口号// 填充一下服务器的IP和Portstruct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!// 清空localbzero(&local,sizeof(local));  // 用memset也可以// 填充sockaddr_in结构local.sin_family=AF_INET;local.sin_port=htons(port_);         // 端口号要出现在网络中, 主机序列转网络序列// 使用 inet_addr就可以做下面两件事情:// (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成// (2) 需要将主机序列转化成为网络序列// (3)云服务器,或者一款服务器,一般不要指明某一个确定的IPlocal.sin_addr.s_addr=INADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    // 把套接字字段和文件字段进行关联  --- 网络文件int n=bind(sock_,(struct sockaddr*)&local,sizeof(local));    if(n<0){cerr<<"bind socket error: "<<strerror(errno)<<endl;exit(BIND_ERR);}cout<<"bind socket success: "<<sock_<<endl;   }void Start(){char buffer[1024];       // 保存用户数据的缓冲区while(true){// 收到来自客户端发送的消息struct sockaddr_in peer;          // 远端socklen_t len = sizeof(peer);     // 这里一定要写清楚, 未来你传入的缓冲区大小// 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加'\0'int n = recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,                                  &len);  if(n>0)    // 读取数据成功buffer[n]='\0';elsecontinue;// 提取client信息   --- debugstring clientip=inet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的uint16_t clientport=ntohs(peer.sin_port);  // 网络序列转主机序列cout<< clientip << "-" <<clientport<< "# "<<buffer<<endl;// 把消息发给别人// 网络套接字本质是文件, 往文件中写入时\0并不需要写到文件中, \0是C语言的规定// 谁给我发的, 我就把消息转给谁// peer结构体字段是从网络中拿的, 本来就是网络序列, 直接发就行sendto(sock_,buffer,strlen(buffer),0,(struct sockaddr*)&peer, 		  	                        sizeof(peer));}}~Udpserver(){}private:int sock_;         //套接字(文件描述符)uint16_t port_;    //端口号(本地主机序列构建的port)};
}

udp_server.cc

#include"udp_server.hpp"
using namespace ns_server;// 运行格式: ./udp_server port// 使用手册
static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}int main(int argc,char*argv[])
{if(argc != 2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);         //命令行参数转换成uint16_t类型unique_ptr<Udpserver> usvr(new Udpserver(port));usvr->InitServer();     // 服务器初始化usvr->Start();return 0;
}

udp_client.cc

#pragma once#include<iostream>
#include"err.hpp"
#include<sys/types.h>
#include<sys/socket.h>
#include<cstring>
#include<string>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}// udp_client serverip serverport
int main(int argc,char*argv[])
{if(argc !=3){usage(argv[0]);exit(USAGE_ERR);}// 拿到服务端的ip和端口号string serverip=argv[1];uint16_t serverport=atoi(argv[2]);// 1. 创建套接字int sock=socket(AF_INET,SOCK_DGRAM,0);if (sock < 0){cerr << "create socket error: " << strerror(errno) << endl;exit(SOCKET_ERR);}// 2. 关于客户端的绑定// client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip:        serverport]// 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?// client的port要随机让OS分配防止client出现启动冲突 // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化// 明确服务器是谁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());// 3. 向服务器发送消息(这里暂时由用户充当)while(true){// 用户输入string message;cout<< "please Enter# ";cin>>message;// 什么时候bind呢?// 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP, (1)bind (2)构建发送的数据报文// 发送sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)&server,                        sizeof(server));// 把消息再收回来(回显回来)char buffer[1024];struct sockaddr_in temp;socklen_t len =sizeof(temp);int n=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);if(n>0){buffer[n]='\0';cout<<"server echo# "<<buffer<<endl;}}
}

运行结果:

先运行服务端,再启动客户端,客户端先用本地环回进行测试,测试成功

在这里插入图片描述

运行程序后看到套接字是创建成功的,对应得到到的文件描述符是3,这也很好理解,因为0、1、2默认被标准输入流、标准输出流和标准错误流占用了,此时最小的、未被使用用的文件描述符就是3

(2) v2_小写转大写

v2在v1版本的基础增加了业务处理,上层使用了回调函数实现大小写转换

udp_server.hpp

#pragma once#include<iostream>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<functional>
#include"err.hpp"
using namespace std;namespace ns_server
{const static uint16_t default_port=8080;using func_t =function<string(string)>;    //这是一个函数class Udpserver{public:Udpserver(func_t cb, uint16_t port=default_port):service_(cb),port_(port){cout<<"server addr: "<<port_<<endl;}void InitServer(){// 1. 创建socket接口, 打开网络文件(本质)sock_=socket(AF_INET,SOCK_DGRAM,0);if(sock_<0){cerr<<"create socket error: "<<strerror(errno)<<endl;exit(SOCKET_ERR);}cout<<"create socket success: "<<sock_<<endl;   // sock_ = 3// 2. 给服务器指明IP地址和Port端口号// 填充一下服务器的IP和Portstruct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!// 清空localbzero(&local,sizeof(local));  // 用memset也可以// 填充sockaddr_in结构local.sin_family=AF_INET;local.sin_port=htons(port_);         // 端口号要出现在网络中, 主机序列转网络序列// 使用 inet_addr就可以做下面两件事情:// (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成// (2) 需要将主机序列转化成为网络序列// (3)云服务器,或者一款服务器,一般不要指明某一个确定的IPlocal.sin_addr.s_addr=INADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    // 把套接字字段和文件字段进行关联  --- 网络文件int n=bind(sock_,(struct sockaddr*)&local,sizeof(local));    if(n<0){cerr<<"bind socket error: "<<strerror(errno)<<endl;exit(BIND_ERR);}cout<<"bind socket success: "<<sock_<<endl;   }void Start(){char buffer[1024];       // 保存用户数据的缓冲区while(true){// 收到来自客户端发送的消息struct sockaddr_in peer;                 // 远端socklen_t len = sizeof(peer);             // 这里一定要写清楚, 未来你传入的缓冲区大小// 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加'\0'int n = recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,                                  &len);  if(n>0)    // 读取数据成功buffer[n]='\0';elsecontinue;// 提取client信息   --- debugstring clientip=inet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的uint16_t clientport=ntohs(peer.sin_port);  // 网络序列转主机序列cout<< clientip << "-" <<clientport<< "# "<<buffer<<endl;// 做业务处理string response=service_(buffer);// 把消息发给别人// 网络套接字本质是文件, 往文件中写入时\0并不需要写到文件中, \0是C语言的规定// 谁给我发的, 我就把消息转给谁// peer结构体字段是从网络中拿的, 本来就是网络序列, 直接发就行sendto(sock_,response.c_str(),response.size(),0,(struct sockaddr*)&peer, 						sizeof(peer));}}~Udpserver(){}private:int sock_;         //套接字(文件描述符)uint16_t port_;    //端口号(本地主机序列构建的port)func_t service_;    //我们的网络服务器刚刚解决的是网络IO的问题, 要进行业务处理(一个类内的回调方法)};
}

udp_server.cc

#include"udp_server.hpp"
using namespace ns_server;// 运行格式: ./udp_server port// 使用手册
static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}// 上层的业务处理, 不关心网络发送, 只负责信息处理即可// 这里是小写转大写
string transactionString(string request)   // request就是一个字符串
{string ret;char c;for(auto&r:request){if(islower(r)){c=toupper(r);ret.push_back(c);}else{ret.push_back(r);}}return ret;
}
int main(int argc,char*argv[])
{if(argc != 2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);         //命令行参数转换成uint16_t类型unique_ptr<Udpserver> usvr(new Udpserver(transactionString,port));usvr->InitServer();     // 服务器初始化usvr->Start();return 0;
}

udp_client.cc

#pragma once#include<iostream>
#include"err.hpp"
#include<sys/types.h>
#include<sys/socket.h>
#include<cstring>
#include<string>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}// udp_client serverip serverport
int main(int argc,char*argv[])
{if(argc !=3){usage(argv[0]);exit(USAGE_ERR);}// 拿到服务端的ip和端口号string serverip=argv[1];uint16_t serverport=atoi(argv[2]);// 1. 创建套接字int sock=socket(AF_INET,SOCK_DGRAM,0);if (sock < 0){cerr << "create socket error: " << strerror(errno) << endl;exit(SOCKET_ERR);}// 2. 关于客户端的绑定// client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip:        		serverport]// 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?// client的port要随机让OS分配防止client出现启动冲突 // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化// 明确服务器是谁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());// 3. 向服务器发送消息(这里暂时由用户充当)while(true){// 用户输入string message;cout<< "please Enter# ";cin>>message;// 什么时候bind呢?// 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP, (1)bind (2)构建发送的数据报文// 发送sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)&server,                        sizeof(server));// 把消息再收回来(回显回来)char buffer[1024];struct sockaddr_in temp;socklen_t len =sizeof(temp);int n=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);if(n>0){buffer[n]='\0';cout<<"server echo# "<<buffer<<endl;}}
}

运行结果:

在这里插入图片描述

(3) v3_模拟命令行解释器

v3是在v2原有的业务处理下修改了功能,只要我们在客户端输入命令服务端就会返回运行结果,popen函数可以实现简单的命令行解释

udp_server.hpp

#pragma once#include<iostream>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<functional>
#include"err.hpp"
using namespace std;namespace ns_server
{const static uint16_t default_port=8080;using func_t =function<string(string)>;    //这是一个函数class Udpserver{public:Udpserver(func_t cb, uint16_t port=default_port):service_(cb),port_(port){cout<<"server addr: "<<port_<<endl;}void InitServer(){// 1. 创建socket接口, 打开网络文件(本质)sock_=socket(AF_INET,SOCK_DGRAM,0);if(sock_<0){cerr<<"create socket error: "<<strerror(errno)<<endl;exit(SOCKET_ERR);}cout<<"create socket success: "<<sock_<<endl;   // sock_ = 3// 2. 给服务器指明IP地址和Port端口号// 填充一下服务器的IP和Portstruct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!// 清空localbzero(&local,sizeof(local));  // 用memset也可以// 填充sockaddr_in结构local.sin_family=AF_INET;local.sin_port=htons(port_);         // 端口号要出现在网络中, 主机序列转网络序列// 使用 inet_addr就可以做下面两件事情:// (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成// (2) 需要将主机序列转化成为网络序列// (3)云服务器,或者一款服务器,一般不要指明某一个确定的IPlocal.sin_addr.s_addr=INADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    // 把套接字字段和文件字段进行关联  --- 网络文件int n=bind(sock_,(struct sockaddr*)&local,sizeof(local));    if(n<0){cerr<<"bind socket error: "<<strerror(errno)<<endl;exit(BIND_ERR);}cout<<"bind socket success: "<<sock_<<endl;   }void Start(){char buffer[1024];       // 保存用户数据的缓冲区while(true){// 收到来自客户端发送的消息struct sockaddr_in peer;                 // 远端socklen_t len = sizeof(peer);             // 这里一定要写清楚, 未来你传入的缓冲区大小// 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加'\0'int n = recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,                                  &len);  if(n>0)    // 读取数据成功buffer[n]='\0';elsecontinue;// 提取client信息   --- debugstring clientip=inet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的uint16_t clientport=ntohs(peer.sin_port);  // 网络序列转主机序列cout<< clientip << "-" <<clientport<< "# "<<buffer<<endl;// 做业务处理string response=service_(buffer);// 把消息发给别人// 网络套接字本质是文件, 往文件中写入时\0并不需要写到文件中, \0是C语言的规定// 谁给我发的, 我就把消息转给谁// peer结构体字段是从网络中拿的, 本来就是网络序列, 直接发就行sendto(sock_,response.c_str(),response.size(),0,(struct sockaddr*)&peer, 						sizeof(peer));}}~Udpserver(){}private:int sock_;         //套接字(文件描述符)uint16_t port_;    //端口号(本地主机序列构建的port)func_t service_;    //我们的网络服务器刚刚解决的是网络IO的问题, 要进行业务处理(一个类内的回调方法)};
}

udp_server.cc

#include"udp_server.hpp"
using namespace ns_server;// 运行格式: ./udp_server port// 使用手册
static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}// 上层的业务处理, 不关心网络发送, 只负责信息处理即可
static bool isPass(string &command)
{auto pos=command.find("rm");if(pos!=string::npos) return false;pos=command.find("mv");if(pos!=string::npos) return false;pos=command.find("while");if(pos!=string::npos) return false;pos=command.find("kill");if(pos!=string::npos) return false;return true;
}// 让同学们, 在你的本地把命令给我, server再把结果给你!
string excuteCommand(string command)   // command就是一个命令
{// 1. 安全检查if(!isPass(command))return "you are a bad man";// 2. 业务逻辑处理FILE*fp=popen(command.c_str(),"r");if(fp==nullptr)return "None";// 3. 获取结果char line[1024];string ret;while(fgets(line,sizeof(line),fp)!=NULL){ret+=line;}pclose(fp);return ret;
}int main(int argc,char*argv[])
{if(argc != 2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);         //命令行参数转换成uint16_t类型unique_ptr<Udpserver> usvr(new Udpserver(excuteCommand,port));usvr->InitServer();     // 服务器初始化usvr->Start();return 0;
}

udp_server.cc

#pragma once#include<iostream>
#include"err.hpp"
#include<sys/types.h>
#include<sys/socket.h>
#include<cstring>
#include<string>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}// udp_client serverip serverport
int main(int argc,char*argv[])
{if(argc !=3){usage(argv[0]);exit(USAGE_ERR);}// 拿到服务端的ip和端口号string serverip=argv[1];uint16_t serverport=atoi(argv[2]);// 1. 创建套接字int sock=socket(AF_INET,SOCK_DGRAM,0);if (sock < 0){cerr << "create socket error: " << strerror(errno) << endl;exit(SOCKET_ERR);}// 2. 关于客户端的绑定// client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip: serverport]// 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?// client的port要随机让OS分配防止client出现启动冲突 // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化// 明确服务器是谁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());// 3. 向服务器发送消息(这里暂时由用户充当)while(true){// 用户输入string message;cout<< "[遇健的服务器]# ";getline(cin,message);// 什么时候bind呢?// 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP, (1)bind (2)构建            发送的数据报文// 发送sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)&server,                       sizeof(server));// 把消息再收回来(回显回来)char buffer[2048];struct sockaddr_in temp;socklen_t len =sizeof(temp);int n=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);if(n>0){buffer[n]='\0';cout<<"server echo# "<<buffer<<endl;}}
}

运行结果:

在这里插入图片描述

(4) v4_多线程版本的群聊系统

v4在v3的基础上加入了之前写的生产消费者模型,多线程实现了群聊系统

LockGuard.hpp

#include<iostream>
#include<pthread.h>
using namespace std;class Mutex   //自己不维护锁,由外部传入
{
public:Mutex(pthread_mutex_t* mutex):_pmutex(mutex){}void lock(){pthread_mutex_lock(_pmutex);}void unlock(){pthread_mutex_unlock(_pmutex);}~Mutex(){}private:pthread_mutex_t* _pmutex;   //锁的指针
};class LockGuard  //自己不维护锁,由外部传入
{
public:LockGuard(pthread_mutex_t* mutex):_mutex(mutex){_mutex.lock();}~LockGuard(){_mutex.unlock();}private:Mutex _mutex;   //锁的指针
};

RingQueue.hpp

#include<iostream>
#include<pthread.h>
#include<vector>
#include<time.h>
#include<sys/types.h>
#include<unistd.h>
#include<semaphore.h>
#include<mutex>
using namespace std;// 生产者和消费者要有自己的下标来表征生产和消费要访问哪个资源
static const int N=5;template<class T>
class RingQueue
{
private:void P(sem_t &s){sem_wait(&s);}void V(sem_t &s){sem_post(&s);}void Lock(pthread_mutex_t &m){pthread_mutex_lock(&m);}void Unlock(pthread_mutex_t &m){pthread_mutex_unlock(&m);}public:RingQueue(int num=N):_ring(num),_cap(num){sem_init(&_data_sem,0,0);sem_init(&_space_sem,0,num);_c_step=_p_step=0;pthread_mutex_init(&_c_mutex,nullptr);pthread_mutex_init(&_p_mutex,nullptr);}void push(const T&in)     // 对应生产者{// 1.信号量的好处:// 可以不用在临界区内部做判断, 就可以知道临界资源的使用情况// 2.什么时候用锁, 什么时候用sem? --- 你对应的临界资源, 是否被整体使用!// 生产 --- 先要申请信号量// 信号量申请成功 - 则一定能访问临界资源P(_space_sem);Lock(_p_mutex);// 一定要有对应的空间资源给我!不用做判断, 是哪一个资源给生产者呢_ring[_p_step++]=in;_p_step%=_cap;V(_data_sem);Unlock(_p_mutex);}void pop(T*out)           // 对应消费者{// 消费P(_data_sem);    // 1.   先申请信号量是为了更高效Lock(_c_mutex);  // 2. *out=_ring[_c_step++];_c_step%=_cap;V(_space_sem);Unlock(_c_mutex);}~RingQueue(){sem_destroy(&_data_sem);sem_destroy(&_space_sem);pthread_mutex_destroy(&_c_mutex);pthread_mutex_destroy(&_p_mutex);}
private:vector<T> _ring;int _cap;           // 环形队列容器大小sem_t _data_sem;    // 只有消费者关心sem_t _space_sem;   // 只有生产者关心int _c_step;        // 消费位置int _p_step;        // 生产位置pthread_mutex_t _c_mutex;pthread_mutex_t _p_mutex;
};

udp_server.hpp

#include<iostream>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<functional>
#include"err.hpp"
#include<unordered_map>
#include"ringQueue1.hpp"
#include"lockGuard.hpp"
#include"thread.hpp"
using namespace std;// 群聊系统 --- 一个线程收消息, 一个线程发消息
// 标识一个客户端: ip+port , 使用unordered_map构建<ip+port, 客户端套接字>来表示某个用户发的消息namespace ns_server
{const static uint16_t default_port=8080;using func_t =function<string(string)>;    //这是一个函数class Udpserver{public:Udpserver(uint16_t port=default_port):port_(port){cout<<"server addr: "<<port_<<endl;pthread_mutex_init(&_lock,nullptr);p=new Thread(1,bind(&Udpserver::Recv,this));c=new Thread(2,bind(&Udpserver::Broadcast,this));}void StartServer(){// 1. 创建socket接口, 打开网络文件(本质)sock_=socket(AF_INET,SOCK_DGRAM,0);if(sock_<0){cerr<<"create socket error: "<<strerror(errno)<<endl;exit(SOCKET_ERR);}cout<<"create socket success: "<<sock_<<endl;   // sock_ = 3// 2. 给服务器指明IP地址和Port端口号// 填充一下服务器的IP和Portstruct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!// 清空localbzero(&local,sizeof(local));  // 用memset也可以// 填充sockaddr_in结构local.sin_family=AF_INET;local.sin_port=htons(port_);         // 端口号要出现在网络中, 主机序列转网络序列// 使用 inet_addr就可以做下面两件事情:// (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成// (2) 需要将主机序列转化成为网络序列// (3)云服务器,或者一款服务器,一般不要指明某一个确定的IPlocal.sin_addr.s_addr=INADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    // 把套接字字段和文件字段进行关联  --- 网络文件int n=bind(sock_,(struct sockaddr*)&local,sizeof(local));    if(n<0){cerr<<"bind socket error: "<<strerror(errno)<<endl;exit(BIND_ERR);}cout<<"bind socket success: "<<sock_<<endl;   p->run();c->run();}void addUser(const string&name,const struct sockaddr_in&peer){// online[name]=peerLockGuard lockguard(&_lock);auto iter=onlineuser.find(name);if(iter!=onlineuser.end())        // 存在(找到了)直接返回return;onlineuser.insert(make_pair(name,peer));   // 不存在(没找到)就插入}void Recv(){char buffer[1024];       // 保存用户数据的缓冲区while(true){// 收到来自客户端发送的消息struct sockaddr_in peer;                 // 远端socklen_t len = sizeof(peer);             // 这里一定要写清楚, 未来你传入的缓冲区大小// 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加'\0'int n = recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,                                  &len);  if(n>0)    // 读取数据成功buffer[n]='\0';elsecontinue;// 提取client信息   --- debugstring clientip=inet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的uint16_t clientport=ntohs(peer.sin_port);  // 网络序列转主机序列cout<< clientip << "-" <<clientport<< "# "<<buffer<<endl;// 构建一个用户, 并检查string name=clientip;name+="-";name+=to_string(clientport);// 构建哈希表来存储用户 - 如果不存在,就插入;如果存在,什么都不做addUser(name,peer);  string message=name+">>"+buffer;  _rq.push(message);    // 消息放入环形队列中}}// 发消息  --- 给所有在线用户void Broadcast(){while(true){string sendstring;_rq.pop(&sendstring);    // 从环形队列中读到了消息   vector<struct sockaddr_in> v;   // 把需要发送的信息放到(拷贝)一个数组中<这是内存级的拷贝>{LockGuard lockguard(&_lock);for (auto user:onlineuser){v.push_back(user.second);}}for(auto user: v){sendto(sock_,sendstring.c_str(),sendstring.size(),0,(struct sockaddr*)&user,sizeof(user));cout<<"send done ..."<<sendstring<<endl;}}}~Udpserver(){pthread_mutex_destroy(&_lock);p->join();c->join();delete p;delete c;}private:int sock_;         //套接字(文件描述符)uint16_t port_;    //端口号(本地主机序列构建的port)unordered_map<string, struct sockaddr_in> onlineuser;   // 保存在线用户 --- 需要加锁保证安全pthread_mutex_t _lock;RingQueue<string> _rq;Thread*p;Thread*c;};
}

udp_server.cc

#include"udp_server.hpp"
using namespace ns_server;// 运行格式: ./udp_server port// 使用手册
static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}// 上层的业务处理, 不关心网络发送, 只负责信息处理即可int main(int argc,char*argv[])
{if(argc != 2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);         //命令行参数转换成uint16_t类型unique_ptr<Udpserver> usvr(new Udpserver(port));usvr->StartServer();return 0;
}

udp_client.cc

#pragma once#include<iostream>
#include"err.hpp"
#include<sys/types.h>
#include<sys/socket.h>
#include<cstring>
#include<string>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}// udp_client serverip serverport
int main(int argc,char*argv[])
{if(argc !=3){usage(argv[0]);exit(USAGE_ERR);}// 拿到服务端的ip和端口号string serverip=argv[1];uint16_t serverport=atoi(argv[2]);// 1. 创建套接字int sock=socket(AF_INET,SOCK_DGRAM,0);if (sock < 0){cerr << "create socket error: " << strerror(errno) << endl;exit(SOCKET_ERR);}// 2. 关于客户端的绑定// client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip: serverport]// 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?// client的port要随机让OS分配防止client出现启动冲突 // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化// 明确服务器是谁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());// 3. 向服务器发送消息(这里暂时由用户充当)while(true){// 用户输入string message;cout<< "[遇健的服务器]# ";getline(cin,message);// 什么时候bind呢?// 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP, (1)bind (2)构建发送的数据报文// 发送sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)&server,                        sizeof(server));// 把消息再收回来(回显回来)char buffer[2048];struct sockaddr_in temp;socklen_t len =sizeof(temp);int n=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);if(n>0){buffer[n]='\0';cout<<"server echo# "<<buffer<<endl;}}
}

运行结果:

在这里插入图片描述

在这里插入图片描述

(5) v5_Windows与Linux配合聊天室

我们可以以Linux云服务器作为服务端,Windows作为客户端,在Windows下我们要修改成Windows下的接口,同时开放云服务器的端口号,使用v4版本的服务端代码

Windows下的客户端

#define _CRT_SECURE_NO_WARNINGS#include<iostream>
#include<WinSock2.h>
#include<string>
#include<cstring>
using namespace std;#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")uint16_t serverport = 8080;
std::string serverip = "47.108.235.67";//std::string serverip = "127.0.0.1";int main()
{WSADATA WSAData;if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0){cerr << "init error" << endl;return -1;}SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);if (sock < 0){cerr << "create socket error: " << strerror(errno) << endl;exit(-2);}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());// 3. 向服务器发送消息(这里暂时由用户充当)while (true){// 用户输入string message;cout << "Please Enter Your Message# ";getline(cin, message);// 发送sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server,                      sizeof(server));char buffer[2048];struct sockaddr_in temp;int len = sizeof(temp);int n = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&temp,                           &len);if (n > 0){buffer[n] = '\0';cout << buffer << endl;     // 往1号文件描述符输出}}closesocket(sock);WSACleanup();return 0;
}

运行结果:

在这里插入图片描述

相关文章:

网络编程套接字(2): 简单的UDP网络程序

文章目录 网络编程套接字(2): 简单的UDP网络程序3. 简单的UDP网络程序3.1 服务端创建(1) 创建套接字(2) 绑定端口号(3) sockaddr_in结构体(4) 数据的接收与发送接收发送 3.2 客户端创建3.3 代码编写(1) v1_简单发送消息(2) v2_小写转大写(3) v3_模拟命令行解释器(4) v4_多线程版…...

Android Mvvm设计模式的详解与实战教程

一、介绍 在开发设计模式中&#xff0c;模式经历了多次迭代&#xff0c;从MVC到MVP&#xff0c;再到如今的MVVM。发现的过程其实很简单&#xff0c;就是为了项目更好的管理。 设计模式严格来说属于软件工程的范畴&#xff0c;但是如今在各大面试中或者开发中&#xff0c;设计模…...

软考A计划-系统集成项目管理工程师-小抄手册(共25章节)-下

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…...

渗透测试是什么?怎么做?

渗透测试报告 一、什么是渗透测试&#xff1f; 渗透测试是可以帮助用户对目前自己的网络、系统、应用的缺陷有相对直观的认识和了解。渗透测试尽可能地以黑客视角对用户网络安全性进行检查&#xff0c;对目标网络、系统和应用的安全性作深入的探测&#xff0c;发现系统最脆弱的…...

【软件安装】Python安装详细教程(附安装包)

软件简介 Python由荷兰数学和计算机科学研究学会的Guido van Rossum 于1990 年代初设计&#xff0c;作为一门叫做ABC语言的替代品。Python提供了高效的高级数据结构&#xff0c;还能简单有效地面向对象编程。Python语法和动态类型&#xff0c;以及解释型语言的本质&#xff0c…...

微信小程序的form表单提交

获取input有两种方法&#xff1a; 第一&#xff1a;bindsubmit方法 注意&#xff1a; 1.使用form里面定义bindsubmit事件 2.bindsubmit事件需要配合button里面定义的formType“submit” 操作 3.设置input的name值来获取对应的数据 <form bindsubmit"formSubmit"…...

WOFOST模型与PCSE模型应用

实现作物产量的准确估算对于农田生态系统响应全球变化、可持续发展、科学粮食政策制定、粮食安全维护都至关重要。传统的经验模型、光能利用率模型等估产模型原理简单&#xff0c;数据容易获取&#xff0c;但是作物生长发育非常复杂&#xff0c;中间涉及众多生理生化过程&#…...

5-W806-RC522-SPI

main.c #include <stdio.h> #include "wm_hal.h" #include "rc522.h"int main(void) {SystemClock_Config(CPU_CLK_160M);printf("enter main\r\n");HAL_Init();RC522_Init();PcdReset();M500PcdConfigISOType ( A );//设置工作方式IC_te…...

Python实现自动登录+获取数据

前言 Dy这个东西想必大家都用过&#xff0c;而且还经常刷&#xff0c;今天就来用代码&#xff0c;获取它的视频数据 环境使用 Python 3.8 Pycharm 模块使用 requests selenium json re 一. 数据来源分析 1. 明确需求 明确采集网站以及数据内容 网址: https://www.dy.co…...

yolov8热力图可视化

安装pytorch_grad_cam pip install grad-cam自动化生成不同层的bash脚本 # 循环10次&#xff0c;将i的值从0到9 for i in $(seq 0 13) doecho "Running iteration $i";python yolov8_heatmap.py $i; done热力图生成python代码 import warnings warnings.filterwarn…...

【SpringBoot】第一篇:redis使用

背景&#xff1a; 本文是教初学者如何正确使用和接入redis。 一、引入依赖 <!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><depen…...

Springboot profile多环境配置

1. 前言 profile用于多环境的激活和配置&#xff0c;用来切换生产&#xff0c;测试&#xff0c;本地等多套不通环境的配置。如果每次去更改配置就非常麻烦&#xff0c;profile就是用来切换多环境配置的。 2. 配置方法 三种方式。 2.1 多profile文件方式 在resource目录下新…...

(1)进程与线程区别

1.什么是线程、进程 进程&#xff1a;操作系统资源分配的基本单位线程&#xff1a;处理器任务调度和执行的基本单位。 一个进程至少有一个线程&#xff0c;线程是进程的一部分&#xff0c;所以线程也被称为轻权进程或者轻量级进程。 2.并行与并发 一个基本的事实前提&#x…...

学习JAVA打卡第四十天

对象的字符串表示 在此类中我们讲过&#xff0c;所有的类都默认是java.lang包中object类的子类或间接子类。 Object类有一个public String toString&#xff08;&#xff09;方法,一个对象通过调用该方法可以获得该对象的字符串表示。一个对象调用toString法&#xff08;&…...

【跟小嘉学 Rust 编程】十四、关于 Cargo 和 Crates.io

系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…...

防关联指纹浏览器:高效地管理你的Facebook账户

Facebook&#xff0c;作为全球最受欢迎社交平台的第一名已经成为我们日常和工作中不可或缺的一部分了。不管是用于日常分享、媒体营销、还是店铺运营&#xff0c;Facebook都占据着重要的位置。多个Facebook账户的优势非常明显&#xff0c;然而&#xff0c;当你需要同时管理他们…...

前端学习记录~2023.8.15~JavaScript重难点实例精讲~第7章 ES6(1)

第 7 章 ES6 前言7.1 let关键字和const关键字7.1.1 let关键字&#xff08;1&#xff09;let关键字的特性&#xff08;2&#xff09;使用let关键字的好处 7.1.2 const关键字&#xff08;1&#xff09;const关键字的特性 7.2 解构赋值7.2.1 数组的解构赋值&#xff08;1&#xff…...

WebSocket详解以及应用

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;websocket、网络、长连接、前端☀️每日 一言&#xff1a;任何一个你不喜欢而又离不开的地方&#xff0c;任何一种你不喜欢而又无法摆脱的生活&#xff0c;都是监狱&#xff01; 一、前言 我们在…...

如何评估开源项目的活跃度和可持续性?

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…...

远程Linux/ubuntu服务器后台不间断运行py文件/sh脚本

通常我们在生产环境中运行一些项目时需要将程序不间断的运行在服务器上&#xff0c;并且将日志文件打印到某个文件中&#xff0c;直到程序运行结束&#xff0c;下面介绍了在Linux服务器上不间断运行py文件的方式&#xff0c;以及如何保存相应的日志信息。 对于 .py 文件&#x…...

记录一个诡异的bug

将对接oa跳转到会议转写的项目oa/meetingtranslate项目发布到天宫&#xff0c;结果跳转到successPage后报错 这一看就是successPage接口名没对上啊&#xff0c;查了一下代码&#xff0c;没问题啊。 小心起见&#xff0c;我就把successPage的方法请求方式从Post改为Get和POST都…...

Xamarin.Android中的Fragment

目录 1、Activity中使用Fragment2、Fragment与Activity通信3、Fragment与其他的Fragment通信 1、Activity中使用Fragment 一般而言&#xff0c;会在activity中添加一个加载fragment的方法。通过点击菜单的按钮&#xff0c;加载不同的fragment。其样子一般是这样的&#xff1a;…...

portainer初体验

官方文档 安装 docker 这里采用的的是国内汉化的一个镜像&#xff0c;版本号2.16.2。 地址 docker run -d --restartalways --name"portainer" -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock 6053537/portainer-ce体验 访问9000端口。 尝试&#x…...

4G数传方案(合宙cat1模块)

一. 合宙Cat1简介 合宙 Air724 模组推出的低功耗&#xff0c;超小体积&#xff0c;高性能嵌入式 4G Cat1 核心版&#xff0c;标准的 2.54 排针、最小成本的进项 2G、4G Cat4 切换&#xff1b;主要功能如下: 实际测试工作环境为-35℃-75℃&#xff1b; 支持 5-12V 供电或者 3.7…...

ElasticSearch - 海量数据索引拆分的一些思考

文章目录 困难解决方案初始方案及存在的问题segment merge引入预排序 拆分方案设计考量点如何去除冗余数据按什么维度拆分&#xff0c;拆多少个最终的索引拆分模型演进历程整体迁移流程全量迁移流程流量回放比对验证异步转同步多索引联查优化效果 总结与思考参考 困难 索引数据…...

【SA8295P 源码分析】83 - SA8295P HQNX + Android 完整源代码下载方法介绍

【SA8295P 源码分析】83 - SA8295P HQNX + Android 完整源代码下载方法介绍 一、高通官网 Chipcode 下载步骤介绍1.1 高通Chipcode 下载步骤1.2 高通 ReleaseNote 下载方法二、高通 HQX 代码介绍2.1 完整的 HQX 代码结构:sa8295p-hqx-4-2-4-0_hlos_dev_qnx.tar.gz2.2 sa8295p-…...

【设计模式--原型模式(Prototype Pattern)

一、什么是原型模式 原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;它的主要目的是通过复制现有对象来创建新的对象&#xff0c;而无需显式地使用构造函数或工厂方法。这种模式允许我们创建一个可定制的原型对象&#xff0c;然后通过复制…...

初识 Redis

初识 Redis 1 认识NoSQL1.1 结构化与非结构化1.2 关联和非关联1.3 查询方式1.4. 事务1.5 总结 2 Redis 概述2.1 应用场景2.2 特性 3 Resis 全局命令4 Redis 基本数据类型4.1 String4.1.1 常用命令4.1.2 命令的时间复杂度4.1.3 使用场景 4.2 Hash4.2.1 常用命令4.2.2 命令的时间…...

php灵异事件,啥都没干数据变了?

这篇文章也可以在我的博客查看 搞WordPress&#xff0c;难免跟php打交道 然而这弱类型语言实在坑有点多 这不今儿又踩了个大坑直接时间-1&#x1f605; 问题 话不多说直接上代码 <?php $items [1,2];foreach ($items as &$item) {/*empty loop*/} print_r($items)…...

【ffmpeg】基于需要使用videocapture的opencv编译配置(C++)

目录 配置简介ffmpeg源码编译方法记录gstreamer命令行安装方法opencv的编译项记录 配置简介 opencv使用videocapture读取视频流时&#xff0c;需要借助底层的ffmpeg库。如果不能正确编译&#xff0c;会报错&#xff0c;现记录正确编译配置方法。 ffmpeg源码编译方法记录 ope…...

微信公众号小说代理和网站结合怎么做/网络营销师是干什么的

Class文件格式采用一种类似C语言结构体的结构来存储数据&#xff0c;这种数据结构只有两种数据类型&#xff1a;无符号数和表。 无符号数属于基本的数据类型&#xff0c;数据项的不同长度分别用u1, u2, u4, u8表示&#xff0c; 分别表示一种数据项在class文件中占据一个字…...

wordpress标题怎么写/百度网址名称是什么

题目要求 问题描述&#xff1a;一个正整数有可能可以被表示为 n(n>2) 个连续正整数之和&#xff0c;如&#xff1a; 1512345 15456 1578 编写程序&#xff0c;根据输入的任何一个正整数&#xff0c;找出符合这种要求的所有连续正整数序列。 样例输入&#xff1a;15 样例输出…...

wordpress文章页随机文章/网络营销推广的基本手段

PDF文件设置了打开密码&#xff0c;但是文件如果不经常使用&#xff0c;还有可能会忘记正确密码或者记住的密码输入的时候显示错误。但是文件内容很重要一定要打开文件&#xff0c;这种时候&#xff0c;我们只能找回正确的打开密码才能够解决问题。 【PDF解密大师】密码找回_破…...

wordpress怎么加表格/东莞网站营销策划

JDK安装后&#xff0c;没有配置环境变量&#xff0c;也可以java -version查看到版本信息 原因是:jdk安装过程&#xff0c;java、javaw、javaws三个命令被复制到C:\windows\system32目录下 所以&#xff0c;如果运行命令javac&#xff0c;会提示“javac不是内部或外部命令” 其实…...

网站开发出来有后台么/站长工具高清

将永恒君的百宝箱设为星标 精品文章第一时间读在之前的文章(批量替换隐藏的神秘字符)分享了一个这样的实例需求&#xff1a;在网银、证券账户中下载csv格式的明细文件&#xff0c;在查看的过程中想将数据进行求和&#xff0c;发现居然不能实现&#xff1f;无论是用求和函数公式…...

静态网站提交表单怎么做/环球军事网最新军事新闻最新消息

默认使用sqllite数据库 修改为mysql数据库 创建数据库 在app models中编写创建数据库类 from django.db import models class Book(models.Model):#表明book django 会自动使用项目名我们自定义的表名# 如果没有自定义主键&#xff0c;django会自动添加一个主键&#xff0c;字段…...