套接字接口

我们把服务器封装成一个类,当我们定义出一个服务器对象后需要马上初始化服务器,而初始化服务器需要做的第一件事就是创建套接字。

🌎socket函数

这是Linux中创建套接字的系统调用,函数原型如下:

int socket(int domain, int type, int protocol);

参数说明:

  • domain:创建套接字的域或者叫做协议家族,也就是创建套接字的类型。该参数就相当于struct
    sockaddr结构的前16个位。如果是本地通信就设置为AF_UNIX,如果是网络通信就设置为AF_INET(IPv4)或AF_INET6(IPv6)。
  • type:创建套接字时所需的服务类型。其中最常见的服务类型是SOCK_STREAM和SOCK_DGRAM,如果是基于UDP的网络通信,我们采用的就是SOCK_DGRAM,叫做用户数据报服务,如果是基于TCP的网络通信,我们采用的就是SOCK_STREAM,叫做流式套接字,提供的是流式服务。
  • protocol:创建套接字的协议类别。你可以指明为TCP或UDP,但该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。

返回值说明:

  • 套接字创建成功返回一个文件描述符,创建失败返回-1,同时错误码会被设置。

🌎socket函数属于什么类型的接口?

网络协议栈是分层的,按照TCP/IP四层模型来说,自顶向下依次是应用层、传输层、网络层和数据链路层。而我们现在所写的代码都叫做用户级代码,也就是说我们是在应用层编写代码,因此我们调用的实际是下三层的接口,而传输层和网络层都是在操作系统内完成的,也就意味着我们在应用层调用的接口都叫做系统调用接口。

🌎socket函数是被谁调用的?

socket这个函数是被程序调用的,但并不是被程序在编码上直接调用的,而是程序编码形成的可执行程序运行起来变成进程,当这个进程被CPU调度执行到socket函数时,然后才会执行创建套接字的代码,也就是说socket函数是被进程所调用的。

🌎socket函数底层做了什么?

socket函数上面说到是被进程调用的,而每一个进程在系统层面有一个进程地址空间PCB(task_struct),文件描述符表(files_struct)以及对应打开的各种文件。而文件描述符表里包含一个数组fd_array,其中数组的0,1,2下标分别对应,标准输入,标准输出以及标准错误。
在这里插入图片描述
当我们调用socket函数创建套接字时,实际相当于我们打开了一个“网络文件”,打开后在内核层面上就形成了一个对应的struct file结构体,同时该结构体被连入到了该进程对应的文件双链表,并将该结构体的首地址填入到了fd_array数组当中下标为3的位置,此时fd_array数组中下标为3的指针就指向了这个打开的“网络文件”,最后3号文件描述符作为socket函数的返回值返回给了用户。
在这里插入图片描述
每一个struct file结构体中包含的就是对应打开文件各种信息,比如文件的属性信息、操作方法以及文件缓冲区等。其中文件对应的属性在内核当中是由struct inode结构体来维护的,而文件对应的操作方法实际就是一堆的函数指针(比如read和write)在内核当中就是由struct file_operations结构体来维护的。而文件缓冲区对于打开的普通文件来说对应的一般是磁盘,但对于现在打开的“网络文件”来说,这里的文件缓冲区对应的就是网卡。

对于一般的普通文件来说,当用户通过文件描述符将数据写到文件缓冲区,然后再把数据刷到磁盘上就完成了数据的写入操作。而对于现在socket函数打开的“网络文件”来说,当用户将数据写到文件缓冲区后,操作系统会定期将数据刷到网卡里面,而网卡则是负责数据发送的,因此数据最终就发送到了网络当中。

🌎服务端创建套接字

我们初始化服务器创建套接字时,第一个参数需要填入的就是AF_INET,表明我们要进行网络通信,因为我们是UDP服务器,面向数据报的,所以填入SOCK_DGRAM,第三个参数协议选择,可以自行选择UDP协议,也可以传入0,让socket根据第二个参数自动识别。

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>class UdpServer
{
public://服务器初始化bool InitServer(){//AF_INET,表明需要网络通信//SOCK_DGRAM,面向数据报//0,默认,根据第二个参数为面向数据报,自动识别为UDP协议_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "socket create error!!" << std::endl;return;}std::cout << "socket create success,sockfd : " << _sockfd << std::endl;}~UdpServer(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;//文件描述符
};

注意: 当析构服务器时,我们可以将sockfd对应的文件进行关闭,但实际上不进行该操作也行,因为一般服务器运行后是就不会停下来的。

这里我们可以做一个简单的测试,看看套接字是否创建成功。

int main()
{UdpServer* srv = new UdpServer();srv->InitServer();return 0;
}

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

UDP服务器端和客户端均只需1个套接字:TCP 中,套接字是一对一的关系。如要向 10 个客户端提供服务,那么除了负责监听的套接字外,还需要创建 10 套接字。但在 UDP 中,不管是服务器端还是客户端都只需要 1 个套接字。举个例子,负责邮寄包裹的快递公司可以比喻为 UDP 套接字,只要有 1 个快递公司,就可以通过它向任意地址邮寄包裹。同样,只需 1 个 UDP 套接字就可以向任意主机传送数据。

服务端绑定

现在套接字已经创建成功了,但作为一款服务器来讲,如果只是把套接字创建好了,那我们也只是在系统层面上打开了一个文件,操作系统将来并不知道是要将数据写入到磁盘还是刷到网卡,此时该文件还没有与网络关联起来。

UDP中的服务器端和客户端没有连接,UDP 不像 TCP,无需在连接状态下交换数据,因此基于 UDP 的服务器端和客户端也无需经过连接过程。也就是说,不必调用 listen() 和 accept() 函数。UDP 中只有创建套接字的过程和数据交换的过程。

由于现在编写的是不面向连接的UDP服务器,所以初始化服务器要做的第二件事就是绑定。

🌎bind函数

绑定的函数叫做bind,注意它跟c++的bind函数只是同名,功能上没有任何关联,该函数的函数原型如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数说明:

  • sockfd:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。
  • addr:网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:传入的addr结构体的长度。

返回值说明:

  • 绑定成功返回0,绑定失败返回-1,同时错误码会被设置。

🌎struct sockaddr_in结构体

在绑定时需要将网络相关的属性信息填充到一个结构体当中,这里应该填入一个struct sockaddr结构体指针,但我们应该先创建struct sockaddr_in这个结构体,再将其强转到struct sockaddr*填入,具体原因我的上篇博客对这两个结构体解释得很清楚。
附上连接:点击跳转到我的上篇博客

我们可以用grep命令在/usr/include目录下查找该结构,此时就可以找到定义该结构的文件。
在这里插入图片描述

可以发现在结构体在/usr/include/linux/in.h文件中,需要注意的是,struct sockaddr_in属于系统级的概念,不同的平台接口设计可能会有点差别。
在这里插入图片描述
可以看到,struct sockaddr_in当中的成员如下:

  • sin_family:表示协议家族。
  • sin_port:表示端口号,是一个16位的整数。
  • sin_addr:表示IP地址,是一个32位的整数。

剩下的字段一般不做处理,当然你也可以进行初始化。

其中sin_addr的类型是struct in_addr,实际该结构体当中就只有一个成员,该成员就是一个32位的整数,IP地址实际就是存储在这个整数当中的。

🌎如何理解绑定?

在进行绑定的时候需要将IP地址和端口号告诉对应的网络文件,此时就可以改变网络文件当中文件操作函数的指向,将对应的操作函数改为对应网卡的操作方法,此时读数据和写数据对应的操作对象就是网卡了,所以绑定实际上就是将文件和网络关联起来。

🌎增加IP地址和端口号

由于绑定时需要用到IP地址和端口号,因此我们需要在服务器类当中引入IP地址和端口号,在创建服务器对象时需要传入对应的IP地址和端口号,此时我们就可以根据传入的IP地址和端口号对对应的成员进行初始化。

class UdpServer
{
public:UdpServer(std::string ip,uint16_t port):_sockfd(-1),_port(port),_ip(ip){}//服务器初始化bool InitServer(){//AF_INET,表明需要网络通信//SOCK_DGRAM,面向数据报//0,默认,根据第二个参数为面向数据报,自动识别为UDP协议_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "socket create error!!" << std::endl;return false;}std::cout << "socket create success,sockfd : " << _sockfd << std::endl;return true;}~UdpServer(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;        //文件描述符uint16_t _port;     //端口号std::string _ip;    //IP地址
};

🌎服务端绑定

绑定之前,需要注意的是,在发送到网络之前需要将端口号设置为网络序列,由于端口号是16位的,因此我们需要使用htons函数将端口号转为网络序列。此外,由于网络当中传输的是整数IP,我们需要调用inet_addr函数将字符串IP转换成整数IP,然后再将转换后的整数IP进行设置。

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <memory>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>class UdpServer
{
public:UdpServer(std::string ip,uint16_t port):_sockfd(-1),_port(port),_ip(ip){}//服务器初始化bool InitServer(){//AF_INET,表明需要网络通信//SOCK_DGRAM,面向数据报//0,默认,根据第二个参数为面向数据报,自动识别为UDP协议_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "socket create error!!" << std::endl;return false;}std::cout << "socket create success,sockfd : " << _sockfd << std::endl;//填充网络通信相关信息struct sockaddr_in local;memset(&local,'\0',sizeof(struct sockaddr_in));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());//绑定int ret = bind(_sockfd,(struct sockaddr*)&local,sizeof(struct sockaddr));if(ret < 0){std::cout << "bind error!!" << std::endl;return false;}std::cout << "bind success " << std::endl;return true;}~UdpServer(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;        //文件描述符uint16_t _port;     //端口号std::string _ip;    //IP地址
};

字符串IP VS 整数IP

IP地址的表现形式有两种:

  • 字符串IP:类似于192.168.233.123这种字符串形式的IP地址,叫做基于字符串的点分十进制IP地址。
  • 整数IP:IP地址在进行网络传输时所用的形式,用一个32位的整数来表示IP地址。

🌎整数IP存在的意义

网络传输数据时是寸土寸金的,如果我们在网络传输时直接以基于字符串的点分十进制IP的形式进行IP地址的传送,那么此时一个IP地址至少就需要15个字节,但实际并不需要耗费这么多字节。

IP地址实际可以划分为四个区域,其中每一个区域的取值都是0~255,而这个范围的数字只需要用8个比特位就能表示,因此我们实际只需要32个比特位就能够表示一个IP地址。其中这个32位的整数的每一个字节对应的就是IP地址中的某个区域,我们将IP地址的这种表示方法称之为整数IP,此时表示一个IP地址只需要4个字节。

在这里插入图片描述
因为采用整数IP的方案表示一个IP地址只需要4个字节,并且在网络通信也能表示同样的含义,因此在网络通信时就没有用字符串IP而用的是整数IP,因为这样能够减少网络通信时数据的传送

🌎字符串IP和整数IP相互转换的方式

转换的方式有很多,比如我们可以定义一个位段A,位段A当中有四个成员,每个成员的大小都是8个比特位,这四个成员就依次表示IP地址的四个区域,一共32个比特位。

然后我们再定义一个联合体IP,该联合体当中有两个成员,其中一个是32位的整数,其代表的就是整数IP,还有一个就是位段A类型的成员,其代表的就是字符串IP。
在这里插入图片描述
由于联合体的空间是成员共享的,因此我们设置IP和读取IP的方式如下:

  • 当我们想以整数IP的形式设置IP时,直接将其赋值给联合体的第一个成员就行了。
  • 当我们想以字符串IP的形式设置IP时,先将字符串分成对应的四部分,然后将每部分转换成对应的二进制序列依次设置到联合体中第二个成员当中的p1、p2、p3和p4就行了。
  • 当我们想取出整数IP时,直接读取联合体的第一个成员就行了。
  • 当我们想取出字符串IP时,依次获取联合体中第二个成员当中的p1、p2、p3和p4,然后将每一部分转换成字符串后拼接到一起就行了。

注意: 在操作系统内部实际用的就是位段和枚举,来完成字符串IP和整数IP之间的相互转换的

🌎inet_addr函数

实际在进行字符串IP和整数IP的转换时,我们不需要自己编写转换逻辑,系统已经为我们提供了相应的转换函数,我们直接调用即可。

将字符串IP转换成整数IP的函数叫做inet_addr,该函数的函数原型如下:

in_addr_t inet_addr(const char *cp);

该函数使用起来非常简单,我们只需传入待转换的字符串IP,该函数返回的就是转换后的整数IP。除此之外,inet_aton函数也可以将字符串IP转换成整数IP,不过该函数使用起来没有inet_addr简单。

🌎inet_ntoa函数

将整数IP转换成字符串IP的函数叫做inet_ntoa,该函数的函数原型如下:

char *inet_ntoa(struct in_addr in);

需要注意的是,传入inet_ntoa函数的参数类型是in_addr,因此我们在传参时不需要选中in_addr结构当中的32位的成员传入,直接传入in_addr结构体即可。

运行服务器

UDP服务器的初始化就只需要创建套接字和绑定就行了,当服务器初始化完毕后我们就可以启动服务器了。

服务器实际上就是在周而复始的为我们提供某种服务,服务器之所以称为服务器,是因为服务器运行起来后就永远不会退出,因此服务器实际执行的是一个死循环代码。由于UDP服务器是不面向连接的,因此只要UDP服务器启动后,就可以直接读取客户端发来的数据。

🌎recvfrom函数

UDP服务器读取数据的函数叫做recvfrom,该函数的函数原型如下:

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:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:调用时传入期望读取的src_addr结构体的长度,返回时代表实际读取到的src_addr结构体的长度,这是一个输入输出型参数。

返回值说明:

  • 读取成功返回实际读取到的字节数,读取失败返回-1,同时错误码会被设置。

注意:

  • 由于UDP是不面向连接的,因此我们除了获取到数据以外还需要获取到对端网络相关的属性信息,包括IP地址和端口号等。
  • 在调用recvfrom读取数据时,必须将addrlen设置为你要读取的结构体对应的大小。
  • 由于recvfrom函数提供的参数也是struct sockaddr类型的,因此我们在传入结构体地址时需要将struct
    sockaddr_in
    类型进行强转。

🌎启动服务器函数

现在服务端通过recvfrom函数读取客户端数据,我们可以先将读取到的数据当作字符串看待,将读取到的数据的最后一个位置设置为’\0’,此时我们就可以将读取到的数据进行输出,同时我们也可以将获取到的客户端的IP地址和端口号也一并进行输出。

需要注意的是,我们获取到的客户端的端口号此时是网络序列,我们需要调用ntohs函数将其转为主机序列再进行打印输出。同时,我们获取到的客户端的IP地址是整数IP,我们需要通过调用inet_ntoa函数将其转为字符串IP再进行打印输出。

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <memory>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>const int BUFFERSIZE = 128;
class UdpServer
{
public:UdpServer(std::string ip,uint16_t port):_sockfd(-1),_port(port),_ip(ip){}//服务器启动void Start(){char buffer[BUFFERSIZE] = {0};while(true){struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t size = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);if(size < 0){std::cout << "recvfrom error!!" << std::endl;}buffer[size] = '\0';int port = ntohs(peer.sin_port);std::string ip = inet_ntoa(peer.sin_addr);//消息打印std::cout << ip << ":" << port << "# " << buffer << std::endl;}}//服务器初始化bool InitServer(){//AF_INET,表明需要网络通信//SOCK_DGRAM,面向数据报//0,默认,根据第二个参数为面向数据报,自动识别为UDP协议_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "socket create error!!" << std::endl;return false;}std::cout << "socket create success,sockfd : " << _sockfd << std::endl;//填充网络通信相关信息struct sockaddr_in local;memset(&local,'\0',sizeof(struct sockaddr_in));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());//绑定int ret = bind(_sockfd,(struct sockaddr*)&local,sizeof(struct sockaddr));if(ret < 0){std::cout << "bind error!!" << std::endl;return false;}std::cout << "bind success " << std::endl;return true;}~UdpServer(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;        //文件描述符uint16_t _port;     //端口号std::string _ip;    //IP地址
};

注意: 如果调用recvfrom函数读取数据失败,我们可以打印一条提示信息,但是不要让服务器退出,服务器不能因为读取某一个客户端的数据失败就退出。

🌎引入命令行参数

鉴于构造服务器时需要传入IP地址和端口号,我们这里可以引入命令行参数。此时当我们运行服务器时在后面跟上对应的IP地址和端口号即可。

由于云服务器的原因,后面实际不需要传入IP地址,因此在运行服务器的时候我们只需要传入端口号即可,目前我们就手动将IP地址设置为127.0.0.1。IP地址为127.0.0.1实际上等价于localhost表示本地主机,我们将它称之为本地环回,相当于我们一会先在本地测试一下能否正常通信,然后再进行网络通信的测试。

int main(int argc,char* argv[])
{if(argc != 2){std::cout << "Usage: " << argv[0] << " port" << std::endl;return 1;}std::string ip = "127.0.0.1";//本地环回uint16_t port = atoi(argv[1]);UdpServer* srv = new UdpServer(ip,port);srv->InitServer();srv->Start();return 0;
}

此时带上端口号运行程序就可以看到套接字创建成功、绑定成功,现在服务器就在等待客户端向它发送数据。

在这里插入图片描述
虽然现在客户端代码还没有编写,但是我们可以通过netstat命令来查看当前网络的状态,这里我们可以选择携带nlup选项。

netstat常用选项说明:

  • -n:直接使用IP地址,而不通过域名服务器。
  • -l:显示监控中的服务器的Socket。
  • -t:显示TCP传输协议的连线状况。
  • -u:显示UDP传输协议的连线状况。
  • -p:显示正在使用Socket的程序识别码和程序名称。

此时你就能查看到对应网络相关的信息,在这些信息中程序名称为./server的那一行显示的就是我们运行的UDP服务器的网络信息。
在这里插入图片描述

客户端创建套接字

同样的,我们把客户端也封装成一个类,当我们定义出一个客户端对象后也是需要对其进行初始化,而客户端在初始化时也需要创建套接字,之后客户端发送数据或接收数据也就是对这个套接字进行操作。

客户端创建套接字时选择的协议家族也是AF_INET,需要的服务类型也是SOCK_DGRAM,当客户端被析构时也可以选择关闭对应的套接字。与服务端不同的是,客户端在初始化时只需要创建套接字就行了,而不需要进行绑定操作

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <memory>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>class UdpClient
{
public:bool InitClient(){//创建套接字_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "create sockfd error!!" std::endl;return false;}return true;}~UdpClient(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;
};

客户端的绑定问题

首先,由于是网络通信,通信双方都需要找到对方,因此服务端和客户端都需要有各自的IP地址和端口号,只不过服务端需要进行端口号的绑定,而客户端不需要。

因为服务器就是为了给别人提供服务的,因此服务器必须要让别人知道自己的IP地址和端口号,IP地址一般对应的就是域名,而端口号一般没有显示指明过,因此服务端的端口号一定要是一个众所周知的端口号,并且选定后不能轻易改变,否则客户端是无法知道服务端的端口号的,这就是服务端要进行绑定的原因,只有绑定之后这个端口号才真正属于自己,因为一个端口只能被一个进程所绑定,服务器绑定一个端口就是为了独占这个端口。

而客户端在通信时虽然也需要端口号,但客户端一般是不进行绑定的,客户端访问服务端的时候,端口号只要是唯一的就行了,不需要和特定客户端进程强相关。

如果客户端绑定了某个端口号,那么以后这个端口号就只能给这一个客户端使用,就是这个客户端没有启动,这个端口号也无法分配给别人,并且如果这个端口号被别人使用了,那么这个客户端就无法启动了。所以客户端的端口只要保证唯一性就行了,因此客户端端口可以动态的进行设置,并且客户端的端口号不需要我们来设置,当我们调用类似于sendto这样的接口时,操作系统会自动给当前客户端获取一个唯一的端口号。

也就是说,客户端每次启动时使用的端口号可能是变化的,此时只要我们的端口号没有被耗尽,客户端就永远可以启动。

启动客户端

🌎增加服务端IP地址和端口号

作为一个客户端,它必须知道它要访问的服务端的IP地址和端口号,因此在客户端类当中需要引入服务端的IP地址和端口号,此时我们就可以根据传入的服务端的IP地址和端口号对对应的成员进行初始化。

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <memory>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>class UdpClient
{
public:UdpClient(std::string ip,uint16_t port):_sockfd(-1),_server_port(port),_server_ip(ip){}bool InitClient(){//创建套接字_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "create sockfd error!!" << std::endl;return false;}return true;}~UdpClient(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;uint16_t _server_port;      //服务器端口号std::string _server_ip;     //服务器ip
};

当客户端初始化完毕后我们就可以将客户端运行起来,由于客户端和服务端在功能上是相互补充的,既然服务器是在读取客户端发来的数据,那么客户端就应该想服务端发送数据。

🌎sendto函数

UDP客户端发送数据的函数叫做sendto,该函数的函数原型如下:

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:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。 addrlen:传入dest_addr结构体的长度。

返回值说明:

  • 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置。

注意:

  • 由于UDP不是面向连接的,因此除了传入待发送的数据以外还需要指明对端网络相关的信息,包括IP地址和端口号等。
  • 由于sendto函数提供的参数也是struct sockaddr类型的,因此我们在传入结构体地址时需要将struct
    sockaddr_in
    类型进行强转。

🌎启动客户端函数

现在客户端要发送数据给服务端,我们可以让客户端获取用户输入,不断将用户输入的数据发送给服务端。

需要注意的是,客户端中存储的服务端的端口号此时是主机序列,我们需要调用htons函数将其转为网络序列后再设置进struct sockaddr_in结构体。同时,客户端中存储的服务端的IP地址是字符串IP,我们需要通过调用inet_addr函数将其转为整数IP后再设置进struct sockaddr_in结构体。

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <memory>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>class UdpClient
{
public:UdpClient(std::string ip,uint16_t port):_sockfd(-1),_server_port(port),_server_ip(ip){}void Start(){std::string msg;//目标服务器信息struct sockaddr_in peer;memset(&peer,'\0',sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = htons(_server_port);peer.sin_addr.s_addr = inet_addr(_server_ip.c_str());while(true){std::cout << "Please Enter# ";getline(std::cin,msg);sendto(_sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));}}bool InitClient(){//创建套接字_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "create sockfd error!!" << std::endl;return false;}return true;}~UdpClient(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;uint16_t _server_port;      //服务器端口号std::string _server_ip;     //服务器ip
};

🌎引入命令行参数

鉴于构造客户端时需要传入对应服务端的IP地址和端口号,我们这里也可以引入命令行参数。当我们运行客户端时直接在后面跟上对应服务端的IP地址和端口号即可。

int main(int argc,char* argv[])
{if(argc != 3){std::cout << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}std::string serverip = argv[1];uint16_t serverport = atoi(argv[2]);UdpClient* clt = new  UdpClient(serverip,serverport);clt->InitClient();clt->Start();return 0;
}

客户端和服务器完整代码

🌎server

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <memory>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>const int BUFFERSIZE = 128;
class UdpServer
{
public:UdpServer(std::string ip,uint16_t port):_sockfd(-1),_port(port),_ip(ip){}//服务器启动void Start(){char buffer[BUFFERSIZE] = {0};while(true){struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t size = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);if(size < 0){std::cout << "recvfrom error!!" << std::endl;}buffer[size] = '\0';int port = ntohs(peer.sin_port);std::string ip = inet_ntoa(peer.sin_addr);//消息打印std::cout << ip << ":" << port << "# " << buffer << std::endl;}}//服务器初始化bool InitServer(){//AF_INET,表明需要网络通信//SOCK_DGRAM,面向数据报//0,默认,根据第二个参数为面向数据报,自动识别为UDP协议_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "socket create error!!" << std::endl;return false;}std::cout << "socket create success,sockfd : " << _sockfd << std::endl;//填充网络通信相关信息struct sockaddr_in local;memset(&local,'\0',sizeof(struct sockaddr_in));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());//绑定int ret = bind(_sockfd,(struct sockaddr*)&local,sizeof(struct sockaddr));if(ret < 0){std::cout << "bind error!!" << std::endl;return false;}std::cout << "bind success " << std::endl;return true;}~UdpServer(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;        //文件描述符uint16_t _port;     //端口号std::string _ip;    //IP地址
};int main(int argc,char* argv[])
{if(argc != 2){std::cout << "Usage: " << argv[0] << " port" << std::endl;return 1;}std::string ip = "127.0.0.1";//本地环回uint16_t port = atoi(argv[1]);UdpServer* srv = new UdpServer(ip,port);srv->InitServer();srv->Start();return 0;
}

🌎client

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <memory>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>class UdpClient
{
public:UdpClient(std::string ip,uint16_t port):_sockfd(-1),_server_port(port),_server_ip(ip){}void Start(){std::string msg;//目标服务器信息struct sockaddr_in peer;memset(&peer,'\0',sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = htons(_server_port);peer.sin_addr.s_addr = inet_addr(_server_ip.c_str());while(true){std::cout << "Please Enter# ";getline(std::cin,msg);sendto(_sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));}}bool InitClient(){//创建套接字_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "create sockfd error!!" << std::endl;return false;}return true;}~UdpClient(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;uint16_t _server_port;      //服务器端口号std::string _server_ip;     //服务器ip
};int main(int argc,char* argv[])
{if(argc != 3){std::cout << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}std::string serverip = argv[1];uint16_t serverport = atoi(argv[2]);UdpClient* clt = new  UdpClient(serverip,serverport);clt->InitClient();clt->Start();return 0;
}

本地测试

现在服务端和客户端的代码都已经编写完毕,我们可以先进行本地测试,此时服务器没有绑定外网,绑定的是本地环回。现在我们运行服务器时指明端口号为5678,再运行客户端,此时客户端要访问的服务器的IP地址就是本地环回127.0.0.1,服务端的端口号就是5678。

在这里插入图片描述
客户端运行之后提示我们进行输入,当我们在客户端输入数据后,客户端将数据发送给服务端,此时服务端再将收到的数据打印输出,这时我们在服务端的窗口也看到我们输入的内容。
在这里插入图片描述
此时我们再用netstat命令查看网络信息,可以看到服务端的端口是8081,客户端的端口是55210。这里客户端能被netstat命令查看到,说明客户端也已经动态绑定成功了,这就是我们所谓的网络通信。

在这里插入图片描述

INADDR_ANY

现在我们已经通过了本地测试,接下来就需要进行网络测试了,那是不是直接让服务端绑定我的公网IP,此时这个服务端就能够被外网访问了呢?

理论上确实是这样的,就比如我的服务器的公网IP是47.94.84.249,这里用ping命令也是能够ping通的。
在这里插入图片描述
现在我将服务端设置的本地环回改为我的公网IP,此时当我们重新编译程序再次运行服务端的时候会发现服务端绑定失败。
在这里插入图片描述
由于云服务器的IP地址是由对应的云厂商提供的,这个IP地址并不一定是真正的公网IP,这个IP地址是不能直接被绑定的,如果需要让外网访问,此时我们需要bind 0。系统当中提供的一个INADDR_ANY,这是一个宏值,它对应的值就是0。

因此如果我们需要让外网访问,那么在云服务器上进行绑定时就应该绑定INADDR_ANY,此时我们的服务器才能够被外网访问。

🌎绑定INADDR_ANY的好处

当一个服务器的带宽足够大时,一台机器接收数据的能力就约束了这台机器的IO效率,因此一台服务器底层可能装有多张网卡,此时这台服务器就可能会有多个IP地址,但一台服务器上端口号为5678的服务只有一个。这台服务器在接收数据时,这里的多张网卡在底层实际都收到了数据,如果这些数据也都想访问端口号为5678的服务。此时如果服务端在绑定的时候是指明绑定的某一个IP地址,那么此时服务端在接收数据的时候就只能从绑定IP对应的网卡接收数据。而如果服务端绑定的是INADDR_ANY,那么只要是发送给端口号为5678的服务的数据,系统都会可以将数据自底向上交给该服务端。
在这里插入图片描述
因此服务端绑定INADDR_ANY这种方案也是强烈推荐的方案,所有的服务器具体在操作的时候用的也就是这种方案。

当然,如果你既想让外网访问你的服务器,但你又指向绑定某一个IP地址,那么就不能用云服务器,此时可以选择使用虚拟机或者你自定义安装的Linux操作系统,那个IP地址就是支持你绑定的,而云服务器是不支持的。

🌎更改代码

因此,如果想要让外网访问我们的服务,我们这里就需要将服务器类当中IP地址相关的代码去掉,而在填充网络相关信息设置struct sockaddr_in结构体时,将设置的IP地址改为INADDR_ANY就行了。由于INADDR_ANY的值本质就是0,不存在大小端的问题,因此在设置时可以不进行网络字节序的转换。

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <memory>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>const int BUFFERSIZE = 128;
class UdpServer
{
public:UdpServer(std::string ip,uint16_t port):_sockfd(-1),_port(port),_ip(ip){}//服务器启动void Start(){char buffer[BUFFERSIZE] = {0};while(true){struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t size = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);if(size < 0){std::cout << "recvfrom error!!" << std::endl;}buffer[size] = '\0';int port = ntohs(peer.sin_port);std::string ip = inet_ntoa(peer.sin_addr);//消息打印std::cout << ip << ":" << port << "# " << buffer << std::endl;}}//服务器初始化bool InitServer(){//AF_INET,表明需要网络通信//SOCK_DGRAM,面向数据报//0,默认,根据第二个参数为面向数据报,自动识别为UDP协议_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "socket create error!!" << std::endl;return false;}std::cout << "socket create success,sockfd : " << _sockfd << std::endl;//填充网络通信相关信息struct sockaddr_in local;memset(&local,'\0',sizeof(struct sockaddr_in));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;//绑定INADDR_ANY//绑定int ret = bind(_sockfd,(struct sockaddr*)&local,sizeof(struct sockaddr));if(ret < 0){std::cout << "bind error!!" << std::endl;return false;}std::cout << "bind success " << std::endl;return true;}~UdpServer(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;        //文件描述符uint16_t _port;     //端口号std::string _ip;    //IP地址
};int main(int argc,char* argv[])
{if(argc != 2){std::cout << "Usage: " << argv[0] << " port" << std::endl;return 1;}std::string ip = "47.94.84.249";//本地环回uint16_t port = atoi(argv[1]);UdpServer* srv = new UdpServer(ip,port);srv->InitServer();srv->Start();return 0;
}

此时当我们再重新编译运行服务器时就不会绑定失败了,并且此时当我们再用netstat命令查看时会发现,该服务器的本地IP地址变成了0.0.0.0,这就意味着该UDP服务器可以在本地读取任何一张网卡里面的数据。
在这里插入图片描述

简单的UDP回声服务器

由于在进行网络测试的时候,当客户端发送数据给服务端时,服务端会将从客户端收到的数据进行打印,因此服务端是能够看到现象的。但客户端一直在向服务端发送数据,在客户端这边看不出服务端是否收到了自己发送的数据。

🌎服务端代码改写

鉴于此,我们可以将该服务器改成一个简单的回声服务器。当服务端收到客户端发来的数据后,除了在服务端进行打印以外,服务端可以调用sento函数将收到的数据重新发送给对应的客户端。

需要注意的是,服务端在调用sendto函数时需要传入客户端的网络属性信息,但服务端现在是知道客户端的网络属性信息的,因为服务端在此之前就已经通过recvfrom函数获取到了客户端的网络属性信息。

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <memory>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>const int BUFFERSIZE = 128;
class UdpServer
{
public:UdpServer(std::string ip,uint16_t port):_sockfd(-1),_port(port),_ip(ip){}//服务器启动void Start(){char buffer[BUFFERSIZE] = {0};while(true){struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t size = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);if(size < 0){std::cout << "recvfrom error!!" << std::endl;}buffer[size] = '\0';int port = ntohs(peer.sin_port);std::string ip = inet_ntoa(peer.sin_addr);//消息打印std::cout << ip << ":" << port << "# " << buffer << std::endl;std::string echo_msg = "server get!->";echo_msg += buffer;sendto(_sockfd,echo_msg.c_str(),echo_msg.size(),0,(struct sockaddr*)&peer,len);}}//服务器初始化bool InitServer(){//AF_INET,表明需要网络通信//SOCK_DGRAM,面向数据报//0,默认,根据第二个参数为面向数据报,自动识别为UDP协议_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "socket create error!!" << std::endl;return false;}std::cout << "socket create success,sockfd : " << _sockfd << std::endl;//填充网络通信相关信息struct sockaddr_in local;memset(&local,'\0',sizeof(struct sockaddr_in));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;//绑定INADDR_ANY//绑定int ret = bind(_sockfd,(struct sockaddr*)&local,sizeof(struct sockaddr));if(ret < 0){std::cout << "bind error!!" << std::endl;return false;}std::cout << "bind success " << std::endl;return true;}~UdpServer(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;        //文件描述符uint16_t _port;     //端口号std::string _ip;    //IP地址
};int main(int argc,char* argv[])
{if(argc != 2){std::cout << "Usage: " << argv[0] << " port" << std::endl;return 1;}std::string ip = "47.94.84.249";//本地环回uint16_t port = atoi(argv[1]);UdpServer* srv = new UdpServer(ip,port);srv->InitServer();srv->Start();return 0;
}

🌎客户端代码改写

服务端的代码改了之后,对应客户端的代码也得改改。当客户端发完数据给服务端后,由于服务端还会将该数据重新发给客户端,因此客户端发完数据后还需要调recvfrom来读取服务端发来的响应数据。

在客户端调用recvfrom函数接收服务端发来的响应数据时,客户端同时也需要读取服务端与网络相关的各种信息。虽然客户端早已知道服务端的网络信息了,此时服务端的网络信息已经不重要了,但还是建议不要把参数设置为空,这样可能会出问题,所以我们还是用一个临时变量将服务端的网络信息读取一下。

而客户端接收到服务端的响应数据后,将数据原封不动的打印出来就行了。此时客户端发送给服务端的数据,除了在服务端会打印显示以外,服务端还会将数据再重新发回给客户端,此时客户端也会接收到响应数据然后将该数据进行打印。

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <memory>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>const int BUFFERSIZE = 128;
class UdpClient
{
public:UdpClient(std::string ip,uint16_t port):_sockfd(-1),_server_port(port),_server_ip(ip){}void Start(){std::string msg;//目标服务器信息struct sockaddr_in peer;memset(&peer,'\0',sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = htons(_server_port);peer.sin_addr.s_addr = inet_addr(_server_ip.c_str());while(true){std::cout << "Please Enter# ";getline(std::cin,msg);sendto(_sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));char buffer[BUFFERSIZE] = {0};struct sockaddr_in tmp;socklen_t len = sizeof(tmp);ssize_t size = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&tmp,&len);if(size < 0){std::cout << "recvfrom error!!" << std::endl;}buffer[size] = '\0';std::cout << buffer << std::endl;}}bool InitClient(){//创建套接字_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){std::cout << "create sockfd error!!" << std::endl;return false;}return true;}~UdpClient(){if(_sockfd >= 0){close(_sockfd);}}
private:int _sockfd;uint16_t _server_port;      //服务器端口号std::string _server_ip;     //服务器ip
};int main(int argc,char* argv[])
{if(argc != 3){std::cout << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}std::string serverip = argv[1];uint16_t serverport = atoi(argv[2]);UdpClient* clt = new  UdpClient(serverip,serverport);clt->InitClient();clt->Start();return 0;
}

此时当我们测试回声服务器时,在服务端和客户端就都能够看到对应的现象,这样就能够判断通信是否正常了。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/85284.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/85284.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/85284.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Fashion-MNIST LeNet训练

前面使用线性神经网络softmax 和 多层感知机进行图像分类&#xff0c;本次我们使用LeNet 卷积神经网络进行 训练&#xff0c;期望能捕捉到图像中的图像结构信息&#xff0c;提高识别精度&#xff1a; import torch import torchvision from torchvision import transforms f…

EasyRTC嵌入式音视频通信SDK助力1v1实时音视频通话全场景应用

一、方案概述​ 在数字化通信需求日益增长的今天&#xff0c;EasyRTC作为一款全平台互通的实时视频通话方案&#xff0c;实现了设备与平台间的跨端连接。它支持微信小程序、APP、PC客户端等多端协同&#xff0c;开发者通过该方案可快速搭建1v1实时音视频通信系统&#xff0c;适…

查看make命令执行后涉及的预编译宏定义的值

要查看 make 命令执行后涉及的预编译宏定义&#xff08;如 -D 定义的宏&#xff09;及其值&#xff0c;可以采用以下方法&#xff1a; 1. 查看 Makefile 中的宏定义 直接检查 Makefile 或相关构建脚本&#xff08;如 configure、CMakeLists.txt&#xff09;&#xff0c;寻找 -…

【C/C++】面试常考题目

面试中最常考的数据结构与算法题&#xff0c;适合作为刷题的第一阶段重点。 ✅ 分类 & 推荐题目列表&#xff08;精选 70 道核心题&#xff09; 一、数组 & 字符串&#xff08;共 15 题&#xff09; 题目类型LeetCode编号两数之和哈希表#1盛最多水的容器双指针#11三数…

【芯片学习】555

一、引脚作用 二、原理图 三、等效原理图 1.比较器 同相输入端大于反相输入端&#xff0c;输出高电平&#xff0c;反之亦然 2.三极管 给它输入高电平就可以导通 3.模拟电路部分 4.数字电路部分 这部分的核心是RS触发器&#xff0c;R-reset代表0&#xff0c;set是置位代表1&am…

Linux《文件系统》

在之前的系统IO当中已经了解了“内存”级别的文件操作&#xff0c;了解了文件描述符、重定向、缓冲区等概念&#xff0c;在了解了这些的知识之后还封装出了我们自己的libc库。接下来在本篇当中将会将视角从内存转向磁盘&#xff0c;研究文件在内存当中是如何进行存储的&#xf…

Java-代码段-http接口调用自身服务中的其他http接口(mock)-并建立socket连接发送和接收报文实例

最新版本更新 https://code.jiangjiesheng.cn/article/367?fromcsdn 推荐 《高并发 & 微服务 & 性能调优实战案例100讲 源码下载》 1. controller入口 ApiOperation("模拟平台端现场机socket交互过程,需要Authorization")PostMapping(path "/testS…

基于递归思想的系统架构图自动化生成实践

文章目录 一、核心思想解析二、关键技术实现1. 动态布局算法2. 样式规范集成3. MCP服务封装三、典型应用场景四、最佳实践建议五、扩展方向一、核心思想解析 本系统通过递归算法实现了Markdown层级结构到PPTX架构图的自动转换,其核心设计思想包含两个维度: 数据结构递归:将…

Python包管理器 uv替代conda?

有人问&#xff1a;python的包管理器uv可以替代conda吗? 搞数据和算法的把conda当宝贝&#xff0c;其他的场景能替代。 Python的包管理器有很多&#xff0c;pip是原配&#xff0c;uv是后起之秀&#xff0c;conda则主打数据科学。 uv替代pip似乎只是时间问题了&#xff0c;它…

使用pnpm、vite搭建Phaserjs的开发环境

首先&#xff0c;确保你已经安装了 Node.js 和 npm。然后按照以下步骤操作&#xff1a; 一、使用pnpm初始化一个新的 Vite 项目 pnpm create vite 输入名字 选择模板&#xff0c;这里我选择Vanilla,也可以选择其他的比如vue 选择语言 项目新建完成 二、安装相关依赖 进入项…

JS逆向案例—喜马拉雅xm-sign详情页爬取

JS逆向案例——喜马拉雅xm-sign详情页爬取 声明网站流程分析总结 声明 本文章中所有内容仅供学习交流&#xff0c;抓包内容、敏感网址、数据接口均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff0c;若有侵权&am…

姜老师的MBTI课程:MBTI是可以转变的

我们先来看内向和外向这条轴&#xff0c;I和E内向和外向受先天遗传因素的影响还是比较大的&#xff0c;因为它事关到了你的硬件&#xff0c;也就是大脑的模型。但是我们在大五人格的排雷避坑和这套课程里面都强调了一个观点&#xff0c;内向和外向各有优势&#xff0c;也各有不…

进程同步:生产者-消费者 题目

正确答案&#xff1a; 问题类型&#xff1a; 经典生产者 - 消费者问题 同时涉及同步和互斥。 同步&#xff1a;生产者与消费者通过信号量协调生产 / 消费节奏&#xff08;如缓冲区满时生产者等待&#xff0c;空时消费者等待&#xff09;。互斥&#xff1a;对共享缓冲区的访问需…

吴恩达MCP课程(1):chat_bot

原课程代码是用Anthropic写的&#xff0c;下面代码是用OpenAI改写的&#xff0c;模型则用阿里巴巴的模型做测试 .env 文件为&#xff1a; OPENAI_API_KEYsk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx OPENAI_API_BASEhttps://dashscope.aliyuncs.com/compatible-mode…

Netty 实战篇:手写一个轻量级 RPC 框架原型

本文将基于前文实现的编解码与心跳机制&#xff0c;构建一个简单的 RPC 框架&#xff0c;包括请求封装、响应解析、动态代理调用。为打造微服务通信基础打下基础。 一、什么是 RPC&#xff1f; RPC&#xff08;Remote Procedure Call&#xff0c;远程过程调用&#xff09;允许…

边缘计算新基建:iVX 轻量生成模块的 ARM 架构突围

一、引言 随着工业 4.0 和物联网的快速发展&#xff0c;边缘计算作为连接云端与终端设备的关键技术&#xff0c;正成为推动数字化转型的核心力量。在边缘计算场景中&#xff0c;设备的实时性、低功耗和离线处理能力至关重要。ARM 架构凭借其低功耗、高能效的特点&#xff0c;成…

C# 基于 Windows 系统与 Visual Studio 2017 的 Messenger 消息传递机制详解:发布-订阅模式实现

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;10年以上C/C, C#, Java等多种编程语言开发经验&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开…

js数据类型有哪些?它们有什么区别?

js数据类型共有8种,分别是undefined,null,boolean,number,string,Object,symbol,bigint symbol和bigint是es6中提出来的数据类型 symbol创建后独一无二不可变的数据类型,它主要是为了解决出现全局变量冲突的问题 bigint 是一种数字类型的数据,它可以表示任意精度格式的整数,…

Vite打包优化实践:从分包到性能提升

前言: ​​​​​​​ 随着前端应用功能的增加&#xff0c;项目的打包体积也会不断膨胀&#xff0c;影响加载速度和用户体验。本文介绍了几种常见的打包优化策略&#xff0c;通过Vite和相关插件&#xff0c;帮助减少项目体积、提升性能&#xff0c;优化加载速度。 rollup-plugi…

C++语法系列之模板进阶

前言 本次会介绍一下非类型模板参数、模板的特化(特例化)和模板的可变参数&#xff0c;不是最开始学的模板 一、非类型模板参数 字面意思,比如&#xff1a; template<size_t N 10> 或者 template<class T,size_t N 10>比如&#xff1a;静态栈就可以用到&#…