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

基于UDP的简易网络通信程序

目录

0.前言

1.前置知识

网络通信的大致流程

IP地址

端口号(port)

客户端如何得知服务器端的IP地址和端口号?

服务器端如何得知客户端的IP地址和端口号?

2.实现代码

代码模块的设计

服务器端代码

成员说明

成员实现

UdpServer(uint16_t port) 的实现

~UdpServer() 的实现

void InitServer() 的实现

void Start() 的实现

客户端代码

3.程序拓展

4.源代码附录

客户端代码

服务器端代码

服务端启动逻辑代码(main.cc)


0.前言

1.目标:使用UDP协议实现客户端程序和服务器端程序之间的通信。

2.功能:客户端给服务器发送什么消息,服务器端就给客户端响应什么消息。

3.效果:

客户端效果:当用户根据提示Please Enter# 输入消息之后,按下回车键,发送给服务器端,立马就收到了服务器端发送过来的相同的消息。

服务器端效果:服务器端接收到客户端发送过来的消息之后,会打印客户端的ip地址和端口号,以及用户发送过来的数据。服务器端给客户端发送的消息并不会在服务器端显示。

1.前置知识

网络通信的大致流程

客户端想要将信息发送到服务器端主机,客户端用户在客户端主机的应用层输入的数据,必须要自顶向下贯穿TCP/IP网络协议栈,并封装每一层的协议报头,然后到达物理层的设备,然后通过物理的介质将信息传输到服务器端主机的物理层的设备,然后将信息自底向上贯穿TCP/IP协议栈,进行解包和分用,将客户端用户输入的信息传输到服务器端应用层的程序。

服务器端主机给客户端主机发送的信息也是如此。

如果你想要更加细致的研究这个过程可以参考这篇文章 :文章链接icon-default.png?t=O83Ahttps://blog.csdn.net/D5486789_/article/details/142029716?spm=1001.2014.3001.5501

IP地址

什么是IP地址呢?我们可以这样理解。假如A主机想要将信息发送到B主机,但是在整个网络中的主机数量非常多,所以A主机必须要知道那一台主机是B主机,这个时候,就可以给每一台主机一个编号,用户在网络中标识唯一的一台主机,这个编号就是IP地址。由于历史发展的原因,IP地址有IPV4和IPV6两个版本,但我们主要使用IPV4。

IPv4地址由32位二进制数组成,通常用点分十进制的方式来表示,即每组8位二进制数转换成一个十进制数,并用点(.)分隔,例如192.168.1.1。

端口号(port)

什么是端口号呢?我们可以这样理解。当A主机已经有能力通过IP地址在网络中如此众多的主机中找到B主机,并将信息发送到B主机在物理层的设备上,此时消息需要自底向上贯穿TCP/IP协议栈交付到B主机的应用层,但是B主机应用层启动的软件程序可能不止一个,发送的消息到底要交付给哪一个软件程序呢?所以,A主机还需要有能力将发送给B主机的信息正确的交付给B主机中对应的软件程序。这个时候可以给每个启动的软件程序一个编号,用于唯一的标识一个启动的软件程序,这个编号就是端口号

端口号是一个16位的数字,范围从0到65535。其中,一些端口号被预留给特定的服务或应用程序,这些端口号被称为“知名端口号”或“系统端口号”。例如,HTTP服务通常使用端口号80,HTTPS服务通常使用端口号443,FTP服务使用端口号21等。这些知名端口号在Internet上被广泛认可和使用,使得客户端可以很容易地找到并连接到相应的服务。客户端程序的端口号在1024之后随便选。

如果你有操作系统的知识的话,你就知道我上面所说的软件程序就是操作系统中的进程,但是标识系统中唯一的一个进程不是有进程PID吗?为什么还要端口号呢?

我们可以这样理解:

用户使用角度:用户访问服务器端的服务程序的时候,需要知道该服务的IP地址和端口号,并且用户一旦认定了这个IP地址和端口号,当用户再次想访问对应的服务的时候,输入的还是这个IP地址和端口号,所以这就要求服务端程序所在主机的IP地址和自己的端口号是不能改变的;但是进程PID不同,每次启动同一个程序时,系统分配给进程的PID是会变化的,所以在网络通信中不能使用进程PID来唯一标识唯一的一个进程。

从操作系统的角度:进程PID是属于操作系统中进程管理的范畴,网络通信是数据操作系统中网络管理的范畴,不同的模块之间最好是低耦合的,一层变化不会影响另一层。所以,操作系统中网络管理模块需要有独属于自己的,用于唯一的标识一个进程的设计,这个设计就是端口号。

总结:不同的两台主机之间通信,必须要提前知道对方的IP地址和端口号,这样才能在整个错综复杂的网络中有目的的找到对方,并将信息准确的交付给对方启动的应用程序。

客户端如何得知服务器端的IP地址和端口号?

客户端需要知道服务器端的IP地址和端口号,那客户端是如何得知服务器端的IP地址和端口号的呢?

我们可以设想一下,开发人员开发出一个应用服务之后,要如何让别人来访问自己开发出的应用服务呢?是不是需要提前告诉用户呢?只有提前告诉用户,用户才知道访问对应的服务需要输入特定的IP地址和端口号。

但是IP地址和端口号太难记忆了,不方便用户使用,于是,便有了域名。比如,当我们想要买东西时,我们就可以在浏览器输入 www.taobao.com,浏览器就会将用户输入的域名解析成为指定的IP地址和端口号。

所以,客户端要想得知服务器端的IP地址和端口号,一定是商业公司提前做了宣传。

服务器端如何得知客户端的IP地址和端口号?

那服务器端是如何得知客户端的IP地址和端口号的呢?

我们可以这样理解:当用户输入指定的IP地址和端口号之后,用户输入的信息在自定向下贯穿TCP/IP协议栈的时候,不是会进行封装吗?这个过程就会将用户所使用的主机的IP地址和该主机上客户端程序的端口号也封装进用户发送的数据中,形成一个数据包,当服务器端拿到这个数据包时,就能知道客户端的IP地址和端口号。

所以,服务器端之所以能知道客户端的IP地址和端口号,是客户端发送给服务器端的。

好了,有了上面这些知识,我们就可以编写代码了。但是还有一些网络通信的基础知识在 “实现代码” 部分讲解。

2.实现代码

注意,在使用网络编程相关接口时,我们需要包含这四个头文件

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <netinet/in.h>

代码模块的设计

客户端只需要实现UdpClient.cc,服务器端需要实现UdpServer.hpp、Main.cc。

服务器端代码

服务器类代码总览:

成员说明

成员变量说明:

在整个服务器类中,成员变量我们设计三个,分别是_sockfd、_port。

_sockfd:这是一个文件描述符,服务器端程序在进行网络通信的过程中,接收的数据从该文件描述符所关联的文件中拿;发送的数据,通过该文件描述符所关联的文件发送。

_port:这是服务器端程序所使用的端口号,是我们开发人员给手动的给服务器端程序绑定的。

成员函数说明:

在真个服务器类中一共四个成员函数,分别是 UdpServer(uint16_t port)、 ~UdpServer()、 void InitServer()、 void Start()。

UdpServer(uint16_t port):构造函数,负责成员变量的初始化,将_port 初始化为我们开发人员给服务器端程序指定的port;port是在启动服务器端程序时,我们自己指定的。

~UdpServer():析构函数,负责成员变量的释放,但并没有成员变量管理资源,所以不需要写啥。

void InitServer():该成员函数负责初始化服务器端程序,也就是为网络通信做必要的准备。比如:创建套接字,绑定端口号。

void Start():该成员函数负责启动服务器,当调用这个函数之后,我们的服务器端程序就可以进行消息的收和发了。

成员实现

成员实现部分,我们主要实现成员函数。

UdpServer(uint16_t port) 的实现

_sockfd我们先初始化为defaultfd,这是我们定义的const 静态全局变量,也就是-1。

_port 成员变量初始化为port,port 是需要我们在启动服务器端程序时手动输入的。

_isrunnig 大家不用管,不影响。

UdpServer(uint16_t port) : _sockfd(defaultfd), _port(port), _isrunning(false)
{}
~UdpServer() 的实现

额,这个嘛,确实没啥要析构的,使用默认的就可以。

~UdpServer()
{}
void InitServer() 的实现

这个成员方法的实现我们分为两步,第一步为创建udp socket套接字,第二步为绑定本地信息和网络信息。

先看看代码:后面有非常详细的解释。

void InitServer()
{// 1. 创建udp socket 套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);exit(SOCKET_ERROR);}LOG(INFO, "socket create success, sockfd: %d\n", _sockfd);// 填充sockaddr_in结构struct sockaddr_in local; bzero(&local, sizeof(local)); //将该字段全部清空为0.local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY; // htonl(INADDR_ANY);// 2.bind sockfd和网络信息(IP + Port)int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);exit(BIND_ERROR);}LOG(INFO, "socket bind success\n");
}

第一步解释:

我们首先创建udp socket。大家可以这样理解,服务器端的程序需要进行消息的收和发,收消息一定是该主机的物理层的网络设备先收到,也就是网卡,网卡收到的消息要放在哪里呢?在回答这个问题之前,我们需要先明确一点 “Linux下一切皆文件”,硬件也是一种文件。所以操作系统要为网卡分配一个收发消息的缓冲区,用来存放收到的消息和发送的消息。再简单一点理解就是,创建udp socket,相当于把网卡打开了,就可以通过网卡收发消息了。

第一步实现:

实现第一步需要使用这个函数:

函数功能:通过制定的协议创建用于网络通信的文件。

第一个参数:domain 表明我们要进行何种通信,是本地通信,还是网络通信,我们实现的是网络通信,所以将domain设置为 AF_INET即可。

第二个参数:type 指定了套接字的类型,是面向数据报的套接字类型还是面向字节流的套接字类型,UDP是面向数据报的通信协议,所以我们将其设置为SOCK_DGRAM即可。

第三个参数:protocol 用于指定特定的协议,但是前两个参数已经表明我们要使用UDP协议了,所以设置为0即可。

返回类型:

失败时:返回-1,并设置错误码。

成功时:返回新创建的文件描述符。这个文件描述符后续会被用于其他套接字函数,如bind()listen()accept()connect()send()recv()等,这也是我们设计一个_sockfd 成员变量的原因。

第二步解释:

前面我们说过,socket函数返回值是一个文件描述符,与该文件描述符所关联的文件用于该主机在网络通信中收发数据;可是,光光有一个收发数据的文件,还是远远不够的,还需要将该文件和主机的网络信息(比如IP地址,端口号等)关联起来,这样,客户端就能通过指定的IP地址和端口号,找到提供服务的服务端程序,并向该程序用于网络通信的文件中发送数据。服务器端向客户端发送消息也是如此。

第二步实现:

实现这一步需要使用bind函数:

函数功能:将通信主机的本地信息(sockfd)和网络信息关联起来。

第一个参数:sockfd 是我们需要关联的文件描述符。

第二个参数:struct sockaddr 中包含本机的网络信息。

第三个参数:这个参数指定了 addr 参数所指向的结构的长度。对于 sockaddr_in,这个长度通常是 sizeof(struct sockaddr_in)

说明一下:

        addr的类型是 struct sockaddr,但实际上addr的类型是 struct sockaddr_in。这是因为,socket编程不仅可以用于网络通信(使用struct sockaddr_in结构),还可以用于本地通信(使用struct sockaddr_un结构),还有其他通信……通信方式这么多,操作系统的开发者就要多开发几套通信接口给用户使用,不仅仅开发的人头疼,使用的人也头疼。于是,操作系统的开发人员便想到一种方案,提供统一的接口,用户想要进行那种通信,就传入特定的参数即可,该函数内部自动做解析,判断用户要进行何种通信。也就是说,为了统一接口的使用,设计了struct sockaddr 类型。

        需要注意的是,这个参数是一个结构体类型,所以我们需要先填充结构体中的字段之后,再将该结构体对象作为参数传递进去。struct sockaddr_in结构中的字段如下所示:

struct sockaddr_in {  sa_family_t    sin_family;   // 地址族,对于 IPv4 来说,这个值总是 AF_INET  uint16_t       sin_port;     // 端口号,网络字节顺序  struct in_addr sin_addr;     // IPv4 地址,网络字节顺序    
};  // 其中,struct in_addr 结构体定义如下:  
struct in_addr {  uint32_t s_addr; // IPv4 地址,网络字节顺序  
};

sin_family:我们设置为AF_INET,表示要进行网络通信。

sin_port:表示我们给该服务器端程序绑定的端口号。

sin_addr:表示该服务器端程序所在主机的IP地址。

htons和htonl函数的介绍:

函数功能:将主机序列转换成网络序列。

   

        由于历史发展的原因,产生了大端机和小端机,大端机和小端机之间可能也要进行通信,数据需要经过网络,那么数据在网络中是大端序列还是小端序列呢?为了解决这个问题,网络中的序列被规定为大端序列,小端机发送的数据要经过网络就需要转化成网络序列,也就是大端序列,大端机发送的数据已经是大端的了,就不需要进行转化了。

        对于IP地址,在主机中显示的时候,显示出来的是 点分十进制的IP地址,但是,在网络中传输的时候,为了节省空间,往往被设计为四字节的IP地址

        所以我们发送的端口号 需要转换成网络序列,需要使用htons函数,表示将一个16位的整数转换成网络序列。

在认识一个函数:

        发送的IP地址 需要转换成四字节的IP同时也需要转换成网络序列,可以使用inet_addr函数,这一个函数会帮我们完成这两步事情。

这句代码是什么意思呢?—— local.sin_addr.s_addr = INADDR_ANY;  

我们将 local.sin_addr.s_addr 设置为INADDR_ANY,这是为什么呢?因为如果我们指定了服务器端的IP地址,客户端只能通过特定的IP地址来访问这个服务端程序,也就是说 这个服务端程序只能通过绑定的IP地址来访问;但是一台主机不止一个IP地址,可能有公网IP,内网IP,不同的IP需要提供给不同的用户使用,需要保证用户使用该主机的不同IP地址能访问到指定的服务端程序。所以我们将服务器端的IP地址绑定为0,在代码实现方面,库当中将INADDR_ANY定义为0,并且0 的大端序列和小端序列是相同的。

void Start() 的实现

当我们完成服务器端程序网络通信的初始化工作之后,我们的服务器端程序就可以通信了,这段代码也体现了我们这个通信程序的功能 —— 接收客户端的消息,给客户端返回同样的消息。

先看代码再解释:

void Start()
{// 一直运行,直到管理者不想运行了, 服务器都是死循环// UDP是面向数据报的协议while (true){char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer); // 必须初始化成为sizeof(peer)// 1. 我们要让server先收数据ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (n > 0){buffer[n] = 0;LOG(DEBUG, "get message: %s\n",  buffer);// 2. 我们要将server收到的数据,发回给对方sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);}}
}

先介绍基于UDP协议进行网络通信的两个函数:

我们的程序是基于UDP协议进行通信的,UDP协议是面向数据报的协议,所以我们不能使用一般的文件读写接口从基于UDP网络通信的文件中读写数据,而要使用UDP协议的网络通信读写接口 —— recvfrom和sendto。

recvfrom:

函数功能:从指定的网络通信文件中读取数据。

参数说明:

1.sockfd 表明我们要从那个文件中读。

2.buf 是一个输出型参数,表明我们读取的数据放到哪里。

3.len 表明我们要读取多长的数据。

4.flags 是一个标志位,表示我们以什么方式读,通常设为0。

5.src_addr 表明发送数据方的网络信息(比如:发送方的IP地址和端口号信息。),也就是说要知道是谁给我发的消息。

6.addrlen 是指向一个整数的指针,该整数在调用前应该包含 src_addr 指向的结构体的大小;在调用后,它将包含实际写入 src_addr 的字节数。

返回值:该函数执行成功的返回值表示接收到的字节数。如果连接已正常终止,则返回0。如果发生错误,则返回-1,并设置相应的errno来指示错误的类型。

sendto:

函数功能:通过指定的网络通信文件发送数据。

参数介绍:

1.sockfd 表示我们要通过哪个通信文件发送数据。

2.buf 表示我们要发送的数据在内存中存储的位置。

3.len 表明我们要发送多长的数据。

4.flags 表明我们要以什么方式发送数据。

5.dest_addr 表明接收数据方的网络信息,也就是说要把数据发送给谁。

6.addrlen 表明dest_addr所指向的对象的大小。

返回值:如果发送成功,则返回发送的字节数(可能小于请求发送的字节数,如果发送缓冲区空间不足或网络限制)。如果发生错误,则返回-1,并设置errno来指示错误的类型。

代码解释:

作为服务器端程序,肯定是需要一直运行的,这样才能确保客户随时随地都能访问,所以是在一个死循环中,直到管理者不想让这个程序执行了。

服务器端程序肯定是要先接收消息的,所以我们使用recvfrom这个函数将接收到的信息存放在buffer这个缓冲区中,同时通过输出型参数 struct sockaddr_in peer 获取发送方的IP地址和端口号,如果接收消息成功,立马通过sendto函数将存放在buffer中的数据发送给对方,因为我们通过输出型参数获取到了发送方的网络信息,所以我们同样可以使用这个网络信息将数据发回给对方。

至此,服务器端的代码就编写完毕了。

客户端代码

当我们在启动客户端程序的时候,要使用这样的命令 ./udpclient 127.0.0.1 8888,这样的命令,其中127.0.0.1是服务器端的IP地址,8888是服务器端程序的端口号。Usage函数帮我们检查启动客户端程序的格式是否正确。

补充:127.0.0.1是本地环回IP,主要用于测试。

第一步:创建UDP socket。

客户端程序的第一步还是创建基于UDP协议通信的套接字,也就是创建自己用于该服务中进行网络通信的文件。创建的方式和服务器端一模一样。

第二步:绑定客户端的本地信息和网络信息。(特别注意)

客户端需不需要绑定客户端的本地信息和网络信息呢?答案是要,但是,不能够由我们开发人员在代码中手动的绑定,比如:你写的客户端程序绑定端口号为8888,我写的客户端程序也绑定端口号为8888,但是用户主机上的一个端口号只能唯一标识一个进程。所以客户端程序的端口号不能手动绑定, 只能由用户主机上的操作系统自动分配。所以这一步我们什么也不需要做!

接下来的工作就和在服务器端程序中的差不多了,客户端程序也是在死循环中跑的,只能让用户手动的退出。客户端程序通过sendto接口向客户端发送数据,通过recvfrom接口接收客户端发过来的数据。

void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 1. 创建socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr << "socket error" << std::endl;}// 构建目标主机的socket信息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());std::string message;// 2. 直接通信即可while(true){std::cout << "Please Enter# ";std::getline(std::cin, message);sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));struct sockaddr_in peer;socklen_t len = sizeof(peer);char buffer[1024];ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if(n > 0){buffer[n] = 0;std::cout << "server echo# " << buffer << std::endl;}}return 0;
}

至此,就可以完成客户端和服务器端的通信了。

3.程序拓展

我们写的服务器端代码中,接收到用户的信息之后,直接就返回相同的信息;那我们能不能改变服务器端的返回逻辑呢?针对客户端的不同信息,给客户响应不同的结果,这一点可以通过回调函数实现,读者可以自行拓展。

4.源代码附录

客户端代码

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 3)      {Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 1. 创建socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr << "socket error" << std::endl;}// 构建目标主机的socket信息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());std::string message;// 2. 直接通信即可while(true){std::cout << "Please Enter# ";std::getline(std::cin, message);sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));struct sockaddr_in peer;socklen_t len = sizeof(peer);char buffer[1024];ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if(n > 0){buffer[n] = 0;std::cout << "server echo# " << buffer << std::endl;}}return 0;
}

服务器端代码

#pragma once#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>enum
{SOCKET_ERROR = 1,BIND_ERROR,USAGE_ERROR
};const static int defaultfd = -1;class UdpServer
{
public:UdpServer(uint16_t port) : _sockfd(defaultfd), _port(port), _isrunning(false){}void InitServer(){// 1. 创建udp socket 套接字 --- 必须要做的_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);exit(SOCKET_ERROR);}LOG(INFO, "socket create success, sockfd: %d\n", _sockfd);// 2.0 填充sockaddr_in结构struct sockaddr_in local; // struct sockaddr_in 系统提供的数据类型。local是变量,用户栈上开辟空间。int a = 100; a = 20;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port); // port要经过网络传输给对面,先到网络,_port:主机序列-> 主机序列,转成网络序列// a. 字符串风格的点分十进制的IP地址转成 4 字节IP// b. 主机序列,转成网络序列// in_addr_t inet_addr(const char *cp) -> 同时完成 a & b// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // "192.168.3.1" -> 字符串风格的点分十进制的IP地址 -> 4字节IPlocal.sin_addr.s_addr = INADDR_ANY; // htonl(INADDR_ANY);// 2.1 bind sockfd和网络信息(IP(?) + Port)int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);exit(BIND_ERROR);}LOG(INFO, "socket bind success\n");}void Start(){while (true){char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer); // 必须初始化成为sizeof(peer)// 1. 我们要让server先收数据ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (n > 0){buffer[n] = 0;LOG(DEBUG, "get message: %s\n",  buffer);// 2. 我们要将server收到的数据,发回给对方sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);}}_isrunning = false;}~UdpServer(){}private:int _sockfd;uint16_t _port;  // 服务器所用的端口号
};

服务端启动逻辑代码(main.cc)

#include <iostream>
#include <memory>
#include "UdpServer.hpp"void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " local_port\n" << std::endl;
}// ./udpserver portint main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);exit(USAGE_ERROR);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port); usvr->InitServer();usvr->Start();return 0;
}

相关文章:

基于UDP的简易网络通信程序

目录 0.前言 1.前置知识 网络通信的大致流程 IP地址 端口号&#xff08;port&#xff09; 客户端如何得知服务器端的IP地址和端口号&#xff1f; 服务器端如何得知客户端的IP地址和端口号&#xff1f; 2.实现代码 代码模块的设计 服务器端代码 成员说明 成员实现 U…...

AI大模型在知识管理平台上的应用:泛微·采知连实现自动采集.精准搜索.智能问答.主动推荐

AI技术的发展&#xff0c;正在推动组织知识管理模式发生变革。知识管理系统通过各种应用实现知识体系落地&#xff0c;当前聚焦于整合生成式AI技术&#xff0c;以提升业务效率。 组织在数字化进程中面临着知识增量增多、知识更新频率变快、知识与业务结合更紧密等挑战&#xff…...

JavaEE:文件内容操作(一)

文章目录 文件内容的读写---数据流字节流和字符流打开和关闭文件文件资源泄漏try with resources 文件内容的读写—数据流 文件内容的操作,读文件和写文件,都是操作系统本身提供了API,在Java中也进行了封装. Java中封装了操作文件的这些类,我们给它们起了个名字,叫做"文…...

无人机视角下落水救援检测数据集

无人机视角下落水救援检测数据集&#xff0c;利用无人机快速搜索落水者对增加受害者的生存机会至关重要&#xff0c;该数据集共收集12万帧视频图像&#xff0c;涵盖无人机高度从10m-60m高度&#xff0c;检测包括落水者&#xff08;11万标注量&#xff09;、流木&#xff08;900…...

openssl+keepalived安装部署

文章目录 OpenSSL安装下载地址编译安装修改系统配置版本 Keepalived安装下载地址安装遇到问题安装完成配置文件 keepalived运行检查运行状态查看系统日志修改服务service重新加载systemd检查配置文件语法错误 OpenSSL安装 下载地址 ​ 考虑到后面设备可能没法连接到外网&…...

float存储原理

float存储原理基于IEEE 754标准&#xff0c;主要包括符号位、指数位和有效数字位三部分。以下是对其存储原理的具体介绍&#xff1a; 符号位&#xff1a;符号位是浮点数中用于表示正负的位。在单精度浮点数&#xff08;32位&#xff09;中&#xff0c;最左边的第1位是符号位&a…...

DAY 9 - 10 : 树

树的概念 定义 树&#xff08;Tree&#xff09;是n&#xff08;n≥0&#xff09;个节点的有限集合T&#xff0c;它满足两个条件 &#xff1a; 1.有且仅有一个特定的称为根&#xff08;Root&#xff09;的节点。 2.其余的节点可以分为m&#xff08;m≥0&#xff09;个互不相交的…...

【python计算机视觉编程——9.图像分割】

python计算机视觉编程——9.图像分割 9.图像分割9.1 图割安装Graphviz下一步&#xff1a;正文9.1.1 从图像创建图9.1.2 用户交互式分割 9.2 利用聚类进行分割9.3 变分法 9.图像分割 9.1 图割 可以选择不装Graphviz&#xff0c;因为原本觉得是要用&#xff0c;后面发现好像用不…...

北斗赋能万物互联:新质生产力的强劲驱动力

在数字化转型的大潮中&#xff0c;中国自主研制的北斗卫星导航系统&#xff0c;作为国家重大空间基础设施&#xff0c;正以前所未有的力量推动着万物互联时代的到来&#xff0c;成为新质生产力发展的重要基石。本文将深入剖析北斗系统如何以其独特的技术优势和广泛应用场景&…...

时序预测 | Matlab实现GA-CNN遗传算法优化卷积神经网络时间序列预测

时序预测 | Matlab实现GA-CNN遗传算法优化卷积神经网络时间序列预测 目录 时序预测 | Matlab实现GA-CNN遗传算法优化卷积神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 | Matlab实现GA-CNN遗传算法优化卷积神经网络时间序列预测&#xff…...

如何保证消息不重复消费

在使用消息队列&#xff08;Message Queue, MQ&#xff09;时&#xff0c;确保消息不被重复消费是非常重要的&#xff0c;因为重复消费可能导致数据不一致或者业务逻辑出错。要保证消息不被重复消费&#xff0c;可以采取以下几种策略&#xff1a; 1. 消息确认机制 大多数消息…...

HTTP请求工具类

HTTP请求工具类 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL;public class HttpUtils {/*** 发送GET请求并获取响应结果* * param url 请求的URL* return 响应结果…...

谷歌的 DataGemma 人工智能是一个统计精灵

谷歌正在扩大其人工智能模型家族&#xff0c;同时解决该领域的一些最大问题。 今天&#xff0c;该公司首次发布了 DataGemma&#xff0c;这是一对开源的、经过指令调整的模型&#xff0c;在缓解幻觉挑战方面迈出了一步&#xff0c;幻觉是指大型语言模型&#xff08;LLM&#xf…...

【Python爬虫系列】_021.异步请求aiohttp

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)文章合集 👈👈...

源码运行springboot2.2.9.RELEASE

1 环境要求 java 8 maven 3.5.2 2 下载springboot源码 下载地址 https://github.com/spring-projects/spring-boot/releases/tag/v2.2.9.RELEASE 3 修改配置 修改spring-boot-2.2.9.RELEASE/pom.xml 修改spring-boot-2.2.9.RELEASE/spring-boot-project/spring-boot-tools…...

王者荣耀改重复名(java源码)

王者荣耀改重复名 项目简介 “王者荣耀改重复名”是一个基于 Spring Boot 的应用程序&#xff0c;用于生成王者荣耀游戏中的唯一名称。通过简单的接口和前端页面&#xff0c;用户可以输入旧名称并获得一个新的、不重复的名称。 功能特点 生成新名称&#xff1a;提供一个接口…...

Python 全栈系列271 微服务踩坑记

说明 这个坑花了10个小时才爬出来 碰到一个现象&#xff1a;将微服务改造为并发后&#xff0c;请求最初很快&#xff0c;然后就出现大量的失败&#xff0c;然后过一会又能用。 过去从来没有碰到这个问题&#xff0c;要么是一些比较明显的资源&#xff0c;或者逻辑bug&#xff0…...

环境搭建2(游戏逆向)

#include<iostream> #include<windows.h> #include<tchar.h> #include<stdio.h> #pragma warning(disable:4996) //exe应用程序 VOID PrintUI(CONST CHAR* ExeName, CONST CHAR* UIName, CONST CHAR* color, SHORT X坐标, SHORT y坐标, WORD UIwide, W…...

快手自研Spark向量化引擎正式发布,性能提升200%

Blaze 是快手自研的基于Rust语言和DataFusion框架开发的Spark向量化执行引擎&#xff0c;旨在通过本机矢量化执行技术来加速Spark SQL的查询处理。Blaze在快手内部上线的数仓生产作业也观测到了平均30%的算力提升&#xff0c;实现了较大的降本增效。本文将深入剖析blaze的技术原…...

用网卡的ap模式抓嵌入式设备的网络包

嵌入式设备不像pc上&#xff0c;有一些专门的工具比如wareshark来抓包&#xff0c;嵌入式设备中&#xff0c;有的可能集成了tcpdump&#xff0c;可以用来进行简单的抓包&#xff0c;但是不方便分析&#xff0c;况且有的嵌入式设备不一定就集成了tcpdump工具。 关于tcpdump工具…...

centos 7 升级Docker 与Docker-Compose 到最新版本

一 升级docker 可参考docker官方升级 1, 查看docker 信息 docker info 2,查看docker 版本 docker --version 3 升级前 可停止docker : sudo systemctl stop docker 4 查看已安装的docker 并卸载 [rootlocalhost docker]# yum list installed | grep docker docker.x86…...

Docker_启动redis,容易一启动就停掉

现象以及排查过程 最近在使用docker来搭建redis服务&#xff0c;但是在启动redis哨兵容器时&#xff0c;总是发现这个容器启动后立马就停止了。首先想到的是不是服务器资源不够用了导致的这个现象&#xff0c;排查后发现不是资源问题。再者猜测是不是启动报错了&#xff0c;查看…...

微服务中间件之Nacos

Nacos&#xff08;Dynamic Naming and Configuration Service&#xff09;是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它提供了服务注册与发现、配置管理以及服务健康监测等核心功能&#xff0c;旨在帮助开发人员更轻松地构建和管理微服…...

C++: 类和对象(上)

&#x1f4d4;个人主页&#x1f4da;&#xff1a;秋邱-CSDN博客☀️专属专栏✨&#xff1a;C&#x1f3c5;往期回顾&#x1f3c6;&#xff1a;从C语言过渡到C&#x1f31f;其他专栏&#x1f31f;&#xff1a;C语言_秋邱 ​ 面向过程和面向对象 C 语言被认为是面向过程的编程…...

Unity程序基础框架

概述 单例模式基类 没有继承 MonoBehaviour 继承了 MonoBehaviour 的两种单例模式的写法 缓存池模块 &#xff08;确实挺有用&#xff09; using System.Collections; using System.Collections.Generic; using UnityEngine;/// <summary> /// 缓存池模块 /// 知识点 //…...

TiDB 数据库核心原理与架构_Lesson 01 TiDB 数据库架构概述课程整理

作者&#xff1a; 尚雷5580 原文来源&#xff1a; https://tidb.net/blog/beeb9eaf 注&#xff1a;本文基于 TiDB 官网 董菲老师 《TiDB 数据库核心原理与架构&#xff08;101) 》系列教程之 《Lesson 01 TiDB 数据库架构概述》内容进行整理和补充。 课程链接&#xff1a;…...

计算机毕业设计Python深度学习垃圾邮件分类检测系统 朴素贝叶斯算法 机器学习 人工智能 数据可视化 大数据毕业设计 Python爬虫 知识图谱 文本分类

基于朴素贝叶斯的邮件分类系统设计 摘要&#xff1a;为了解决垃圾邮件导致邮件通信质量被污染、占用邮箱存储空间、伪装正常邮件进行钓鱼或诈骗以及邮件分类问题。应用Python、Sklearn、Echarts技术和Flask、Lay-UI框架&#xff0c;使用MySQL作为系统数据库&#xff0c;设计并实…...

多核DSP(6000系列)设计与调试技巧培训

​课程介绍&#xff1a; 为帮助从事DSP开发工程师尽快将DSP技术转化为产品&#xff0c;在较短时间内掌握DSP设计技术和问题的解决方法&#xff0c;缩短产品开发周期、增强产品竞争力、节省研发经费。我们特组织了工程实践和教学经验丰富的专家连续举办了多期DSP C6000的培训&a…...

JMeter脚本开发

环境部署 Ubuntu系统 切换到root用户 sudo su 安装上传下载的命令 apt install lrzsz 切换文件目录 cd / 创建文件目录 mkdir java 切换到Java文件夹下 cd java 输入rz回车 选择jdk Linux文件上传 解压安装包 tar -zxvf jdktab键 新建数据库 运行sql文件 选择sql文件即…...

LabVIEW编程快速提升的关键技术

在LabVIEW程序员的成长道路上&#xff0c;以下几个概念和技术的掌握可以显著提升自我能力&#xff1a; 模块化编程&#xff1a;学会将程序分解成小而独立的模块&#xff08;如子VI&#xff09;&#xff0c;提高程序的可读性、可维护性和可扩展性。这种方式不仅能帮助快速定位问…...