计算机网络—UDP协议详解:特性、应用

                                        🎬慕斯主页修仙—别有洞天

                                       ♈️今日夜电波:マリンブルーの庭園—ずっと真夜中でいいのに。

                                                           0:34━━━━━━️💟──────── 3:34
                                                                🔄   ◀️   ⏸   ▶️    ☰  

                                 💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

网络字节序的概念

对应的网络字节序转换接口

地址转换函数

字符串转in_addr的函数:

in_addr转字符串的函数:

注意事项

socket编程

socket中sockaddr结构

socket在UDP中常用接口的介绍

socket

bind

recvfrom

sendto

什么是UDP协议?

通过程序来理解UDP

服务端

客户端


网络字节序的概念

        内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

        发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;

        接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;

        因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.

        TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.

        不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;

        如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

对应的网络字节序转换接口

        为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换:

#include <arpa/inet.h>  
uint32_t htonl(uint32_t hostlong);

        这里的hostlong是一个32位无符号长整型数,表示主机字节序的数值。函数返回转换后的网络字节序的32位无符号长整型数。

#include <arpa/inet.h>  
uint16_t htons(uint16_t hostshort);

        它接受一个16位无符号整数作为输入参数,并返回转换后的网络字节顺序的数值。

#include <arpa/inet.h>  
uint32_t ntohl(uint32_t netlong);

        在网络编程中,当从网络接收数据时,可能需要将数据从网络字节顺序转换为主机字节顺序,以便正确解读数据。ntohl通常用于解析IPv4地址、端口号或其他在网络传输中使用的32位数值;

#include <arpa/inet.h>  
uint16_t ntohs(uint16_t netshort);

        在网络编程中,当从网络接收数据时,可能需要将数据从网络字节顺序转换为主机字节顺序,以便正确解读数据。ntohs通常用于解析端口号或其他在网络传输中使用的16位数值;

地址转换函数

        我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换:

字符串转in_addr的函数:


    inet_aton是一个用于将点分十进制表示的IPv4地址字符串转换为网络字节序的32位整数的函数。它的原型如下:

#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);

参数:

  • cp:指向点分十进制表示的IPv4地址字符串的指针。
  • inp:指向一个struct in_addr结构体的指针,用于存储转换后的32位整数。

返回值:

  • 成功时返回1,失败时返回0。

示例代码:

#include <stdio.h>
#include <arpa/inet.h>int main() {const char *ip_str = "192.168.1.1";in_addr_t ip_addr;ip_addr = inet_addr(ip_str);if (ip_addr == INADDR_NONE) {printf("无效的IP地址");return 1;}printf("转换后的IP地址:%d", ip_addr);return 0;
}

    inet_addr 是一个在早期的 Unix-like 系统(如 Linux)中用于将点分十进制的 IP 地址字符串转换为网络字节序的 32 位整数的函数。然而,需要注意的是,这个函数在现代的编程实践中已经较少使用,因为其功能可以被更安全的函数(如 inet_pton)所替代,并且 inet_addr 在处理非法的 IP 地址时可能不会返回错误。

函数原型

#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);

参数

  • cp:这是一个指向以 null 结尾的字符串的指针,该字符串表示一个点分十进制的 IPv4 地址(例如 "192.168.1.1")。

返回值:

如果输入的字符串是一个有效的 IPv4 地址,inet_addr 将返回一个表示该地址的 32 位无符号整数(网络字节序)。如果输入的字符串不是一个有效的 IPv4 地址,函数将返回 INADDR_NONE(通常是 -1)。

使用示例:

#include <stdio.h>
#include <arpa/inet.h>int main() {const char *ip_str = "192.168.1.1";in_addr_t ip_addr = inet_addr(ip_str);if (ip_addr == INADDR_NONE) {printf("Invalid IP address\n");} else {printf("IP address in network byte order: %u\n", ip_addr);}return 0;
}

    inet_pton 函数用于将点分十进制表示的IP地址转换为网络字节序的二进制形式。该函数原型如下:

#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);

其中:

  • af 参数指定地址族类型,可以是 AF_INET(IPv4)或 AF_INET6(IPv6)。
  • src 参数指向包含点分十进制表示的 IP 地址字符串。
  • dst 参数指向一个缓冲区,用于存储转换后的二进制结果。

返回值:

  • 成功时返回1,失败时返回0。如果出现其他错误,则返回-1。

使用示例:

#include <stdio.h>
#include <arpa/inet.h>int main() {struct sockaddr_in sa;char *ip_str = "192.168.1.1";if (inet_pton(AF_INET, ip_str, &(sa.sin_addr))) {printf("转换成功,二进制表示为:
");printf("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x
",(unsigned char)sa.sin_addr.s_addr & 0xff,(unsigned char)(sa.sin_addr.s_addr >> 8) & 0xff,(unsigned char)(sa.sin_addr.s_addr >> 16) & 0xff,(unsigned char)(sa.sin_addr.s_addr >> 24) & 0xff,(unsigned char)(sa.sin_addr.s_addr >> 32) & 0xff,(unsigned char)(sa.sin_addr.s_addr >> 40) & 0xff,(unsigned char)(sa.sin_addr.s_addr >> 48) & 0xff,(unsigned char)(sa.sin_addr.s_addr >> 56) & 0xff);} else {printf("无效的IP地址
");}return 0;
}

in_addr转字符串的函数:

    inet_ntoa 是一个常用于将网络字节序的 IPv4 地址(通常是一个 in_addr 结构的实例或该结构的 in_addr_t 类型的值)转换为其点分十进制字符串表示形式的函数。这个函数在早期的网络编程中非常常见,但同样地,现代编程实践中更推荐使用 inet_ntop 函数,因为它提供了更强大的功能,包括对 IPv6 的支持以及更详细的错误处理。

函数原型:

#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);

参数:

  • in:一个 in_addr 结构体的实例,其中包含要转换的网络字节序的 IPv4 地址。

返回值:

inet_ntoa 返回一个指向静态内存区域中存储的点分十进制字符串的指针。这意味着连续调用 inet_ntoa 可能会覆盖之前的结果,因为它总是使用相同的静态缓冲区。因此,如果你需要保留转换后的字符串,应该立即将其复制到另一个缓冲区中。

使用示例:

#include <stdio.h>
#include <arpa/inet.h>int main() {struct in_addr addr;if (inet_aton("192.168.1.1", &addr) == 1) {char *ip_str = inet_ntoa(addr);if (ip_str != NULL) {printf("IP address in dot-decimal notation: %s\n", ip_str);} else {printf("Error converting IP address\n");}} else {printf("Invalid IP address\n");}return 0;
}

    inet_ntop函数是一个用于将网络字节序的IPv4和IPv6地址从二进制格式转换为人类可读的字符串格式的函数。它在网络编程中广泛使用,尤其是当需要将IP地址从二进制形式转换为点分十进制或其他格式时。

函数原型:

#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数:

  • af:地址族(Address Family),指定了IP地址的类型。对于IPv4,它应该是AF_INET;对于IPv6,它应该是AF_INET6
  • src:指向存储要转换的二进制IP地址的缓冲区的指针。
  • dst:指向存储转换后的字符串形式IP地址的缓冲区的指针。
  • size:目标缓冲区dst的大小,以防止缓冲区溢出。

返回值:

如果转换成功,inet_ntop函数返回一个指向转换后的字符串形式IP地址的指针(即dst的值)。如果发生错误,则返回NULL,并设置相应的错误码(通过errno变量获取)。

使用示例:

#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>int main() {struct in_addr addr;addr.s_addr = inet_addr("192.168.0.1");char str[INET_ADDRSTRLEN];const char *result = inet_ntop(AF_INET, &addr, str, INET_ADDRSTRLEN);if (result == NULL) {perror("inet_ntop");return 1;}printf("IP address in string format: %s\n", str);return 0;
}

        在这个示例中,我们首先将字符串形式的IP地址"192.168.0.1"转换为in_addr结构体的实例。然后,我们使用inet_ntop函数将该地址转换为点分十进制的字符串形式,并将结果存储在str数组中。最后,我们打印出转换后的字符串。

注意事项

    inet_addr 仅支持 IPv4 地址。对于 IPv6 地址,因此推荐使用 inet_pton 函数inet_ntoa 仅支持 IPv4 地址。对于 IPv6 地址,你应该使用 inet_ntop 函数

        man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放. 因为inet_ntoa把结果放到自己内部的一个静态存储区, 第二次调用时的结果会覆盖掉上一次的结果. 如果有多个线程调用 inet_ntoa, 是否会出现异常情况呢? 在APUE中, 明确提出inet_ntoa不是线程安全的函数
        因此,在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题;

        在inet_ntop中,为确保目标缓冲区dst足够大,以容纳转换后的字符串形式IP地址。对于IPv4地址,通常使用INET_ADDRSTRLEN作为缓冲区大小是安全的

socket编程

socket中sockaddr结构

    sockaddr是一个通用的套接字地址结构体,用于在网络编程中表示不同类型的套接字地址。这个结构体最初在网络编程函数诞生时就被使用,当时主要是为了IPv4协议。为了向前兼容,现在的sockaddr在很多时候退化为了传递地址给函数的作用,具体的地址类型(如sockaddr_insockaddr_in6)由地址族(address family)确定,然后在函数内部再强制类型转化为所需的地址类型。

sockaddr结构体定义如下:

struct sockaddr {unsigned short sa_family;   // 地址族,用于指定地址类型,例如AF_INET(IPv4)char sa_data[14];           // 地址数据,具体格式和长度取决于地址族的不同
};

        其中,sa_family字段用来指定地址族,即地址类型。常见的取值有AF_INET(IPv4)、AF_INET6(IPv6)和AF_UNIX(UNIX域套接字)等。sa_data字段用来存储实际的地址数据,其格式和长度会根据地址族的不同而变化。

        socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;

        为了更方便地使用,通常会使用sockaddr的变体结构,如sockaddr_in(用于IPv4地址)和sockaddr_in6(用于IPv6地址)。这些变体结构体在sockaddr的基础上进行了扩展,增加了一些额外的字段来保存特定类型的地址信息。例如,sockaddr_in结构体包含了sin_family(地址族)、sin_port(端口号)、sin_addr(IP地址信息)以及sin_zero(为了保持与sockaddr结构大小相同而保留的空字节)等字段。如下为三个sockaddr的结构图示:分别为:struct sockaddr(常用于强转另外两个)、struct sockaddr_in(用于网络的套接字)、struct sockaddr_un(用于本地的套接字

        如果我们要使用socket进行网络编程,那么首先做好对于sockaddr的初始化是很重要的,它在网络编程中扮演着至关重要的角色。它的主要作用是用于表示套接字的地址信息,这包括地址族、端口号以及具体的IP地址或路径等信息。例如下面对于sockaddr_in的示例(通过结构体来设置好地址族(确定使用的协议)、确定好自身的ip、确定好对应的端口。以便后续bind将用户态的sockaddr_in设置进内核态):

sockaddr_in addr;
addr.sin_family = AF_INET; //确定协议
addr.sin_addr.s_addr = inet_addr(ip);//将点分十进制的IPv4地址转换成一个长整数型数
addr.sin_port = htons(port);//注意转换位网络字节序。需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的

socket在UDP中常用接口的介绍

socket

        socket函数是一种在网络编程中广泛使用的函数,它用于根据指定的地址族、数据类型和协议来分配一个套接口(socket)的描述字及其所用的资源。以下是关于socket函数的详细解释:

函数声明

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
  • domain(协议域/协议族):决定了socket的地址类型。常用的协议族有AF_INET(IPv4)、AF_INET6(IPv6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等。在通信中,必须采用与协议族对应的地址。例如,AF_INET决定了要使用IPv4地址(32位)与端口号(16位)的组合。
  • type(socket类型):指定了socket的类型。常用的socket类型有SOCK_STREAM(流式套接字,用于TCP)、SOCK_DGRAM(数据报套接字,用于UDP)、SOCK_RAW(原始套接字,允许对底层协议如IP或ICMP进行直接访问)等。
  • protocol(协议):通常情况下,可以将其设置为0,让系统自动选择type类型对应的默认协议。

返回值

  • 当socket函数成功创建了一个套接字时,它返回一个有效的套接字描述符(socket descriptor)。这个描述符是一个非负整数,用于后续的网络操作,如绑定、监听、连接、发送和接收数据等。
  • 如果在创建套接字时发生错误,socket函数返回-1,并设置全局变量errno以指示错误原因。此时,可以调用errno变量或perror()函数来获取具体的错误信息。常见的错误码包括EACCES(权限不足)、EADDRINUSE(地址已经被占用)、EAFNOSUPPORT(地址族不支持)、EINVAL(参数无效)、EMFILE(达到进程允许打开的最大文件数目)、ENFILE(系统打开文件数目过多)、ENOBUFS/ENOMEM(内存不足)、EPROTONOSUPPORT(协议不支持)等。

        需要注意的是,socket函数返回的套接字描述符本质上是一个文件描述符,因此在系统中,它被当作文件来对待。这意味着可以使用与文件操作类似的系统调用来对其进行读写操作。

bind

        bind函数在网络编程中扮演着至关重要的角色,它主要用于将一个本地协议地址(包括IP地址和端口号)赋予一个套接字。以下是关于bind函数的详细解释:

函数声明

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:这是由socket()函数返回的文件描述符,代表已经创建的套接字。
  • addr:这是一个指向特定协议地址结构的指针,如struct sockaddr_instruct sockaddr_un,它包含了地址、端口和可能的IP地址信息。
  • addrlen:这是地址结构的长度,通常以字节为单位。对于IPv4,通常使用sizeof(struct sockaddr_in);对于IPv6,使用sizeof(struct sockaddr_in6);对于Unix域套接字,使用sizeof(struct sockaddr_un)

返回值

  • 如果bind函数成功执行,它返回0。
  • 如果出现错误,返回-1,并设置全局变量errno以指示错误原因。常见的错误包括EACCES(权限不足)、EADDRINUSE(地址已经被使用)、EADDRNOTAVAIL(地址不可用)、EAFNOSUPPORT(地址族不支持该套接字类型)、EINVAL(套接字未打开)、ENOTSOCK(文件描述符不是套接字)等。

使用场景

  • 在TCP服务器程序中,bind函数通常用于指定服务器应监听的端口号。服务器在启动时捆绑其众所周知的端口,以便客户端可以连接到它。
  • 对于UDP套接字,bind函数同样用于指定接收数据的端口号。
  • 在Unix域套接字中,bind函数可以用来指定套接字在文件系统中的路径名。

注意事项

  • 在调用bind函数之前,套接字必须处于未连接状态(对于面向连接的套接字如TCP)。
  • 如果addr参数中的地址或端口号为0,系统将为套接字自动选择一个可用的地址或端口号。
  • 在多线程环境中,应确保对bind函数的调用是线程安全的,避免竞态条件。
  • 绑定的本质:将用户态的sockaddr_in设置进内核变为系统态。

recvfrom

        recvfrom函数是一个在POSIX兼容操作系统(如Linux)中用于接收数据的系统调用。它主要用于从指定的套接字接收数据,并适用于面向无连接的协议,如UDP(用户数据报协议)。

recvfrom函数的原型如下:

#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:用于控制接收数据的方式。常用的选项有:
    • MSG_WAITALL:阻塞等待直到len字节的数据接收完毕。
    • MSG_DONTWAIT:非阻塞模式,如果没有数据可读,立即返回-1,同时errno设置为EAGAIN或EWOULDBLOCK。
  • src_addr:指向一个sockaddr结构体,用于存储发送方的地址信息。如果不需要这个信息,可以设置为NULL。
  • addrlen:表示src_addr结构体的长度。

返回值:

  • 成功时,返回接收到的字符数(字节数)。
  • 如果没有可用数据或者连接已经关闭,返回0。
  • 如果出现错误,返回-1,并设置errno错误号。此时可以通过perror()函数来打印出错误信息。

注意事项:

  • 在调用recvfrom函数之前,需要先使用bind函数将socket绑定到一个地址上。
  • 如果套接字是非阻塞的,recvfrom函数可能会在没有接收到任何数据时返回-1,并设置errno为EAGAIN或EWOULDBLOCK。
  • 如果接收到的数据比缓冲区还大,那么只会取缓冲区大小的数据,并将剩余的数据丢弃。

sendto

sendto函数是一个系统调用,用于将数据从指定的套接字发送到目标地址。它通常用于UDP(用户数据报协议)通信,因为UDP是无连接的,所以sendto函数允许你向一个特定的地址发送数据报,而不需要事先建立连接。

sendto函数的原型如下:

#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:已经创建好的socket文件描述符。
  • buf:指向要发送的数据的缓冲区。
  • len:要发送的数据长度。
  • flags:发送选项标志,可以是0或者像MSG_DONTWAIT这样的选项。MSG_DONTWAIT表示非阻塞发送,如果发送缓冲区满,则不等待直接返回。
  • dest_addr:目标地址的sockaddr结构体指针。对于IPv4,这通常是一个指向struct sockaddr_in的指针;对于IPv6,则是一个指向struct sockaddr_in6的指针。
  • addrlen:目标地址结构体的长度,例如sizeof(struct sockaddr_in)sizeof(struct sockaddr_in6)

返回值:

sendto函数的返回值是一个long类型的整数,表示发送的字节数。具体返回值有以下几种可能:

  • 如果返回值大于0,则表示数据已经成功发送到了目标地址。返回值代表实际发送的字节数。
  • 如果返回值等于0,表示发送的数据长度为0。这可能是因为buf指向的空间长度为0,或者在使用UDP协议时,sendto函数成功地发送了0字节的数据。
  • 如果返回值等于-1,表示发送过程中出现了错误。此时,可以通过检查errno的值来确定具体的错误原因。例如,如果errno为EINTR,表示sendto函数被一个信号中断了;如果errno为EAGAIN或EWOULDBLOCK,表示发送缓冲区已满,无法立即发送数据(这通常发生在使用了MSG_DONTWAIT标志的情况下)。

        需要注意的是,sendto函数不保证数据的可靠传输。也就是说,发送的数据可能会丢失,或者接收方可能无法按照发送的顺序接收数据。如果需要可靠的数据传输,应该使用TCP协议而不是UDP。

        此外,在使用sendto函数之前,需要确保已经通过socket函数创建了一个套接字,并且(对于面向连接的套接字类型)已经通过connect函数与目标地址建立了连接(尽管对于UDP,连接通常不是必需的,但也可以通过connect建立默认的目标地址)。同时,也需要确保目标地址是有效的,并且发送的数据缓冲区是正确设置的。

什么是UDP协议?

        UDP协议,全称为用户数据报协议(User Datagram Protocol),是一种无连接的传输层协议,它为网络应用程序提供了一种在IP网络上发送封装好的IP数据包的方法。以下是对UDP协议的一些详细理解:

  1. 无连接性:与TCP协议不同,UDP不建立持久的连接。这意味着每个数据报都是独立发送的,无需事先建立或维护任何连接状态。
  2. 不可靠性:UDP不保证数据报的传递,没有流控制和应答确认机制。因此,它可能会发生数据丢失、重复或者乱序到达的情况。
  3. 头部简单:相比于TCP,UDP的报文头更简单,只包含端口号、校验和以及数据长度等信息,这使得它的开销较小。
  4. 快速传输:由于UDP没有复杂的传输机制,它在传输速度上通常比TCP快,适用于对传输速度和延迟要求较高的应用。
  5. 校验和:虽然UDP是不可靠的,但它通过校验和来提供一定程度的数据完整性检查,可以检测出数据在传输过程中是否发生变化。
  6. 应用场景:UDP适用于那些即使偶尔出现数据丢失也不影响整体功能的实时应用,如在线游戏、实时视频或音频通信、DNS查询等。
  7. 基于IP数据包服务:UDP构建在IP协议之上,增加了一些简单的功能,如端口号和校验和,以支持不同的应用程序在同一台设备上的数据传输。
  8. 与TCP的关系:尽管UDP和TCP都属于传输层协议,但它们有着截然不同的特性。TCP是面向连接的、可靠的协议,提供了数据排序、错误检测和流量控制等功能,而UDP则更加轻量级和简单。
  9. 抓包分析:在实际的网络调试中,可以使用Wireshark等工具来捕获和分析UDP数据包,帮助诊断网络问题和优化性能。

        重点总结如下:传输层协议、无连接、不可靠传输、面向数据报

通过程序来理解UDP

服务端

        实际上对于服务端最开始的代码都是差不多的!步骤:1、创建一个struct sockaddr_in结构体,将本服务器对应的族、端口、ip地址填好。2、使用socket函数按照对应的族、选取的协议(UDP、TCP)来获取sockfd。3、使用bind将上面获取的sockfd、sockaddr_in进行绑定,实际上就是:将用户态的sockaddr_in设置进内核变为系统态!4、根据需求用recvfrom、sendto来收发消息。

UdpServer.hpp

#pragma once#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
using namespace std;const uint16_t defaultport = 8888;
std::string defaultip = "0.0.0.0";
const int size = 1024;using func_t = function<string(const string &)>;enum
{SOCKET_ERR = 1,BIND_ERR
};class UdpServer
{
public:UdpServer(uint16_t port = defaultport): _sockfd(0), _ip(defaultip), _port(port), _isrunning(false){}void UdpInit(){_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){lg.LogMessage(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));exit(SOCKET_ERR);}struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = inet_addr(_ip.c_str());// local.sin_addr.s_addr = htonl(INADDR_ANY); //设置为ip为0,表示自动选择地址local.sin_port = htons(_port);if (bind(_sockfd, (const struct sockaddr *)&local, sizeof(local)) < 0){lg.LogMessage(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}lg.LogMessage(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));}void run(func_t func) // 使用回调函数是为了将应用层与传输层分离{_isrunning = true;char inbuffer[size];while (_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);bzero(&client, len);bzero(&inbuffer, sizeof(inbuffer));ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&client, &len);if (n < 0){lg.LogMessage(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}inbuffer[n] = 0;std::string info = inbuffer;std::string echo_string = func(info);//回调传入的函数sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(const struct sockaddr *)&client,len);}}~UdpServer(){if (_sockfd > 0)close(_sockfd);}private:int _sockfd;string _ip;uint16_t _port;bool _isrunning;
};

main.cc

#include "UdpServer.hpp"
#include <memory>void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}std::string Handler(const std::string &str)//简单处理字符串后返回
{std::string res = "Server get a message: ";res += str;std::cout << res << std::endl;return res;
}std::string ExcuteCommand(const std::string &cmd)//远程运行指令
{// SafeCheck(cmd);FILE *fp = popen(cmd.c_str(), "r");if(nullptr == fp){perror("popen");return "error";}std::string result;char buffer[4096];while(true){char *ok = fgets(buffer, sizeof(buffer), fp);if(ok == nullptr) break;result += buffer;}pclose(fp);return result;
}int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> svr(new UdpServer(port));svr->UdpInit();cout<<endl;svr->run(Handler);return 0;
}

客户端

        对于客户端而言,大致的初始化过程同服务端是差不多的,但是对于客户端而已,客户端可以虽然也需要绑定,但是不需要用户显示的bind!,一般由OS自由的选择!一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!系统什么时候给我bind呢?首次发送数据的时候!

#include <iostream>
#include <cstdlib>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " serverip serverport\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());socklen_t len = sizeof(server);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socker error" << endl;return 1;}string message;char buffer[1024];while (true){cout << "Please Enter@ ";getline(cin, message);// std::cout << message << std::endl;// 1. 数据 2. 给谁发sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len);struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;cout << buffer << endl;}}close(sockfd);return 0;
}

                       感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

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

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

相关文章

Linux---命令行参数

一、命令行参数 在介绍命令行参数前&#xff0c;我想问大家一个问题&#xff0c;在以前写C/C时&#xff0c;main 函数可不可以带参数&#xff1f; 答案是可以带的&#xff0c;int main(int argc, char* argv[]){}&#xff0c;但平时写代码时也证明了&#xff0c;main 函数的参…

uniapp对接极光推送(国内版以及海外版)

勾选push&#xff0c;但不要勾选unipush 国内版 网址&#xff1a;极光推送-快速集成消息推送功能,提升APP运营效率 (jiguang.cn) 进入后台&#xff0c;并选择对应应用开始配置 配置安卓包名 以及ios推送证书&#xff0c;是否将生产证书用于开发环境选择是 ios推送证书…

五款常用在线JavaScript加密混淆工具详解:jscrambler、JShaman、jsfack、ipaguard和jjencode

摘要 本篇技术博客将介绍五款常用且好用的在线JavaScript加密混淆工具&#xff0c;包括 jscrambler、JShaman、jsfack、freejsobfuscator 和 jjencode。通过对这些工具的功能及使用方法进行详细解析&#xff0c;帮助开发人员更好地保护和加密其 JavaScript 代码&#xff0c;提…

Linux学习教程 Linux入门教程(超全面 超详细)收藏这一篇就够了

Linux是什么&#xff1f; linux是一个开源、免费的操作系统&#xff0c;其稳定性、安全性、处理多并发能力已经得到业界的认可&#xff0c;目前大多数企业级应用甚至是集群项目都部署运行在linux操作系统之上&#xff0c;很多软件公司考虑到开发成本都首选linux&#xff0c;在…

【NFS】NFS使用汇总

1. NFS介绍 NFS(Network File System)&#xff0c;网络文件系统&#xff0c;它可以让不同主机能够通过 TCP/IP 网络共享资源。它从宏观主体上简化来看&#xff0c;就是两部分&#xff1a;服务端和客户端。 服务端&#xff0c;可以认为它就是来存东西的&#xff0c;这个东西对…

蓝桥杯刷题_day7_动态规划_路径问题

文章目录 DAY7下降路径最小和最小路径和地下城游戏 DAY7 下降路径最小和 【题目描述】 给你一个 n x n 的 方形 整数数组 matrix &#xff0c;请你找出并返回通过 matrix 的下降路径 的 最小和 。 下降路径 可以从第一行中的任何元素开始&#xff0c;并从每一行中选择一个元…

【Java面试题】Redis中篇(高可用:主从复制、哨兵、集群)

文章目录 高可用14.Redis如何保证高可用&#xff1f;15.Redis的主从复制&#xff1f;16.Redis主从有几种常见的拓扑结构&#xff1f;17.Redis的主从复制原理了解吗&#xff1f;18.说说主从数据同步的方式&#xff1f;19.主从复制存在的问题&#xff1f;20.Redis Sentinel(哨兵)…

信息素养和社会责任

1.信息素养&#xff1a; 信息素养是一种了解、收集、评估、和利用信息的知识结构能力。 信息素养的四个要素&#xff1a;信息意识&#xff0c;信息知识&#xff0c;信息能力&#xff0c;信息道德 信息意识是先导&#xff08;前提&#xff09;&#xff0c;是对关键信息具有持…

elementui的table根据是否符合需求合并列

<el-table :data"tableData" border style"width: 100%;" :span-method"objectSpanMethodAuto"><!-- 空状态 --><template slot"empty"><div><img src"/assets/images/noData.png" /></di…

基于SSM+Jsp+Mysql的母婴用品网站

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

新能源汽车充电桩主板的常见故障及解决办法

电桩主板作为充电桩的核心组件&#xff0c;直接影响着充电桩运行的安全性与稳定性。然而&#xff0c;在使用过程中&#xff0c;充电桩主板会因多种原因而出现一些故障情况&#xff0c;了解这些原因并采取相应的应对方法对维护充电桩的正常运行起着至关重要的作用。接下来&#…

3-zookeeper之ZAB协议

Zookeeper ZAB协议 概述 ZAB(Zookeeper Automic Broadcast)是一套专门为Zookeeper设计的用于进行原子广播和崩溃恢复的协议ZAB协议主要包含了两个功能 原子广播&#xff1a;保证数据一致性崩溃恢复&#xff1a;保证集群的高可用 ZAB协议本身是基于2PC算法来进行的设计&#…

U盘位置不可用,如何轻松应对数据恢复难题

在日常工作和生活中&#xff0c;U盘作为一种便捷的存储设备&#xff0c;经常被用于数据传输和备份。然而&#xff0c;有时我们可能会遇到这样一个问题&#xff1a;当插入U盘时&#xff0c;系统提示“位置不可用”或“无法访问”&#xff0c;这让人倍感困扰。面对这种情况&#…

wpsword求和操作教程

wpsword求和怎么操作&#xff1a; 1、首先&#xff0c;单纯的数据是无法求和的&#xff0c;所以我们必须要“插入”一个“表格” 2、接着将需要求和的数据填入到表格中。 3、填完后&#xff0c;进入“布局”选项卡。 4、然后打开其中的“公式” 5、在其中选择求和公式“SUM”并…

从0到1部署私域NuGet库:实战指南,让你轻松掌握!

引言 私域NuGet包的重要性&#xff1a;代码复用和团队协同。通过将公共组件、库或工具打包成NuGet包&#xff0c;并在私域中共享&#xff0c;团队成员可以更方便地引用和使用这些资源。其次私域NuGet包有助于依赖管理。通过私域NuGet包&#xff0c;团队可以集中管理这些依赖&a…

案例研究|DataEase实现物业数据可视化管理与决策支持

河北隆泰物业服务有限责任公司&#xff08;以下简称为“隆泰物业”&#xff09;创建于2002年&#xff0c;总部设在河北省高碑店市&#xff0c;具有国家一级物业管理企业资质&#xff0c;通过了质量体系、环境管理体系、职业健康安全管理体系等认证。自2016年至今&#xff0c;隆…

启信宝商业大数据助力全国经济普查

近日&#xff0c;合合信息旗下启信宝收到中国青年创业就业基金会感谢信&#xff0c;对启信宝协同助力全国经济普查和服务青年创业就业研究表达感谢。 第五次全国经济普查是新时代新征程上一次重大国情国力调查&#xff0c;是对国民经济“全面体检”和“集中盘点”&#xff0c;…

virtualbox 设置虚拟机 centos 网络

在VirtualBox中为运行CentOS系统的虚拟机配置网络连接&#xff0c;您通常可以选择以下几种网络模式之一&#xff0c;以满足不同的网络需求&#xff1a; NAT (Network Address Translation): 功能&#xff1a;允许虚拟机通过宿主机的网络连接访问互联网&#xff0c;同时也可以从…

学习鸿蒙基础(10)

目录 一、轮播组件 Swiper 二、列表-List 1、简单的List 2、嵌套的List 三、Tabs容器组件 1、系统自带tabs案例 2、自定义导航栏&#xff1a; 一、轮播组件 Swiper Entry Component struct PageSwiper {State message: string Hello Worldprivate SwCon: SwiperControl…

Ribbon简介

目录 一 、概念介绍 1、Ribbon是什么 2、认识负载均衡 2.1 服务器端的负载均衡 2.2 客户端的负载均衡 3、Ribbon工作原理 4、Ribbon的主要组件 IClientConfig ServerList ServerListFilter IRule Iping ILoadBalancer ServerListUpdater 5、Ribbon支持…