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

如何看到网站的制作公司河南软件开发

如何看到网站的制作公司,河南软件开发,如何做淘宝联盟网站的推广,开发公司资料员岗位职责及工作内容UDP编程接口基本使用 本篇介绍 在前面网络基础部分已经介绍了网络的基本工作模式#xff0c;有了这些理论基础之后#xff0c;下面先从UDP编程开始从操作部分深入网络 在本篇中#xff0c;主要考虑下面的内容#xff1a; 创建并封装服务端#xff1a;了解创建服务端的…UDP编程接口基本使用 本篇介绍 在前面网络基础部分已经介绍了网络的基本工作模式有了这些理论基础之后下面先从UDP编程开始从操作部分深入网络 在本篇中主要考虑下面的内容 创建并封装服务端了解创建服务端的基本步骤创建并封装客户端测试客户端和服务端通信了解创建客户端的基本步骤和二者通信测试云服务器与本地进行通信从本地通信到真正实现网络通信 根据上面的内容本次设计的服务器功能就是接受客户端发送的信息并向客户端返回服务端收到的信息 创建并封装服务端 创建服务器类 因为需要对服务器进行封装所以首先创建服务器类的基本框架本次设计的服务器一旦启动就不再关闭除非手动关闭所以可以提供两个接口 start启动服务器接口stop停止服务器接口 所以基本框架如下 class UdpServer { public:UdpServer(){}// 启动服务器void start(){}// 停止服务器void stop(){}~UdpServer(){}private: };创建服务器套接字 既然要创建服务器首先就是对服务器的相关信息进行设置。首先需要创建socket文件描述可以使用socket接口该接口原型如下 int socket(int domain, int type, int protocol);该接口的第一个参数表示网络协议家族可以选择的选择选项有很多其中包括AF_UNIX和AF_INET因为本次是网络通信所以该参数选择AF_INET第二个参数表示协议类型在网络通信部分分为两种TCP和UDP对应的值分别为SOCK_STREAM和SOCK_DGRAM因为本次是UDP所以选择SOCK_DGRM 根据Linux操作手册的描述 SOCK_STREAMProvides sequenced, reliable, two-way, connection-based byte streams提供序列化的、可靠的、双工的、面向有连接的字节流SOCK_DGRAMSupports datagrams (connectionless, unreliable messages of a fixed maximum length)支持数据包即无连接、不可靠的固定长度信息 全双工、半双工和单工是描述通信双方在数据传输时的交互模式具体对比如下 全双工Full Duplex双方可以同时互相发送和接收数据就像电话通话两端都能同时说话和听对方半双工Half Duplex双方均可发送和接收数据但同一时间只能有一方传输。例如对讲机通信时一旦你在讲话另一方必须等待直到你停止后才能回应单工Simplex数据只能单向传输通信只有一端发送而另一端只接收。例如广播视频信号中电视台只能发送信号观众只能接收信号 第三个参数表示指定采用的具体协议。通常传入0表示让系统自动选择适合domain和type参数的默认协议 该接口返回值为一个新套接字的文件描述符否则返回-1并设置错误码 根据这个接口的描述可以知道当前服务器类需要一个成员_socketfd用于接收socket的返回值代码如下 class UdpServer { public:UdpServer(): _socketfd(-1){// 创建服务器套接字_socketfd socket(AF_INET, SOCK_DGRAM, 0);}// ...private:int _socketfd; // 套接字文件描述符 };在创建服务器套接字失败时可以考虑使用日志系统显示相关的信息一旦服务器创建异常说明此时服务器无法正常创建可以直接退出函数为了保证可读性可以将错误码定义为宏代码如下 // 错误码枚举类 enum class errorNumber {ServerSocketFail 1, // 创建套接字失败 };class UdpServer { public:UdpServer(): _socketfd(-1){// 创建服务器套接字_socketfd socket(AF_INET, SOCK_DGRAM, 0);if (_socketfd 0){LOG(LogLevel::FATAL) 服务器启动异常 strerror(errno);exit(static_castint(errorNumber::ServerSocketFail));}LOG(LogLevel::INFO) 服务器启动成功 _socketfd;}// ...private:int _socketfd; // 套接字文件描述符 };绑定服务器IP地址和端口 前面的过程只是创建了一个可以写入的位置socket接口可以类比文件部分的open接口在网络部分接下来的步骤并不是写入而应该是绑定端口和IP地址确保其他计算机可以找到当前服务器和具体进程。在Linux中绑定可以使用bind接口该接口原型如下 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);该接口的第一个参数表示需要绑定的套接字对应的文件描述符第二个参数表示套接字结构第三个参数表示套接字结构的大小 如果绑定成功该接口返回0否则返回-1并设置错误码 对于第一个参数和第三个参数来说二者作用和含义明显此处不作过多介绍下面就第二个参数详细介绍 在[Socket编程基础]部分提到sockaddr可以理解为sockaddr_in结构和sockaddr_un的父类而因为本次创建的是网络通信所以要使用的结构就是sockaddr_in既然参数部分是sockaddr结构而不是sockaddr_in那么在传递实参时就需要进行强制类型转换 那么既然需要用户传递sockaddr_in结构那么这个结构中就存在一些属性需要用户去设置。在[Socket编程基础]部分的示意图已经了解到sockaddr_in有下面的几种成员 16位地址类型用于区分当前是何种类型的通信对应的成员名是sin_family16位端口号对应的成员名是sin_port32位IP地址对应的成员名是sin_addr8字节填充 因为第四个成员可以不需要考虑只是用于占位所以可以忽略下面就前面三种类型进行详细介绍 首先是16为地址类型其类型是sa_family_t。在底层该类型是一个宏 struct sockaddr_in {__SOCKADDR_COMMON (sin_);// ... };#define __SOCKADDR_COMMON(sa_prefix) \sa_family_t sa_prefix##familytypedef unsigned short int sa_family_t;这个宏利用到了[C语言的##运算符]其含义是将sa_prefix##替换为宏传递的值因为在__SOCKADDR_COMMON (sin_)设置sa_prefix对应的sin_所以拼接后为sin_family因为sa_family_t代表的是unsigned short int所以sin_family就是一个unsigned short int的值 因为当前是网络通信所以对应值就是AF_INET但是需要注意这里传递的AF_INET和前面在socket接口传递的AF_INET含义不同在使用socket时指定AF_INET表明了socket所使用的协议族它决定了内部数据结构和通信规则而在调用bind时指定AF_INET是因为需要通过这个成员来正确解析后续的地址信息 第二个成员是16位端口号对于端口来说其类型是in_port_t。在底层对应源码如下 struct sockaddr_in {// ...in_port_t sin_port; /* Port number. */// ... };typedef uint16_t in_port_t;typedef __uint16_t uint16_t;typedef unsigned short int __uint16_t;所以本质in_port_t也是unsigned short int但是因为使用unsigned short int和__uint16_t都不够简单所以直接使用uint16_t 需要注意的是在[Socket编程基础]提到过网络字节流时使用的都是大端所以如果当前服务器是小端存储那么就需要转换否则就不需要转换。这里有两种处理方式 判断当前设备是否是大端如果是就直接写端口号否则就需要对端口号进行小端到大端的转化具体判断方式参考[进制转换与类型在内存的存储方式]不论是大端还是小端都进行转换如果是大端就不变否则就变成大端 本次考虑第二种处理方式系统提供了相关的接口处理大小端转换问题如下 uint16_t htons(uint16_t hostshort);最后考虑第三个成员IP地址其类型是一个结构体struct in_addr其原型如下 struct sockaddr_in {// ...struct in_addr sin_addr; /* Internet address. */// ... };typedef uint32_t in_addr_t; struct in_addr {in_addr_t s_addr; };typedef __uint32_t uint32_t; typedef unsigned int __uint32_t;实际上就是一个结构体包含了一个unsigned int类型的成员所以在底层IP地址是一个无符号整数在设置IP地址时需要具体指定到sin_addr 在底层第四个成员如下 struct sockaddr_in {/* Pad to size of struct sockaddr. */unsigned char sin_zero[sizeof (struct sockaddr)- __SOCKADDR_COMMON_SIZE- sizeof (in_port_t)- sizeof (struct in_addr)]; };以上就是bind接口的第二个参数的详细介绍下面根据上面的介绍对指定的套接字进行绑定 因为需要创建端口号和IP地址所以需要增加两个成员其中端口号使用的类型是uint16_t而IP地址是字符串类型之所以使用字符串是为了在使用时更方便但是如果使用字符串字符串使用的格式是点分十进制而需要的是一个无符号整型的整数此时就需要进行转换并且还需要将IP地址也转换为大端字节序这个问题对应地解决方案是inet_addr接口其原型如下 in_addr_t inet_addr(const char *cp);这个接口可以将指定的点分十进制字符串格式的IP地址转换为in_addr_t类型并且转换为大端字节序 接着对创建的端口号成员和IP地址成员进行初始化本次可以考虑给定一个默认的值8080和127.0.0.1也可以让用户在创建服务器时自行设定 在构造函数内部首先就是创建一个struct sockaddr_in结构的对象接着就是根据前面的提示对结构体成员进行填充因为结构体对象一旦创建就不能再通过整体赋值的方式初始化只能通过对每一个成员单独赋值初始化。但是填充具体值之前建议将struct sockaddr_in进行清空操作即全部初始化为0下面有两种方法 使用memset接口进行清空使用bzero接口进行清空 本次考虑使用bzero接口进行其原型如下 #include strings.hvoid bzero(void *s, size_t n);这个接口和memset接口的效果相同 清空后就是对每个成员进行赋值初始化 初始化struct sockaddr_in对象后就可以对指定套接字的对应文件描述符进行绑定。同样考虑使用日志系统显示相关信息 综上代码如下 // 错误码枚举类 enum class ErrorNumber {// ...BindSocketFail, // 绑定失败 };// 默认端口和IP地址 const std::string default_ip 127.0.0.1; const uint16_t default_port 8080;class UdpServer { public:UdpServer(const std::string ip default_ip, uint16_t port default_port): // ... , _ip(ip), _port(port){// ...// 绑定端口号和IP地址struct sockaddr_in saddrIn;saddrIn.sin_family AF_INET;saddrIn.sin_port htons(_port);saddrIn.sin_addr.s_addr inet_addr(_ip.c_str());// 使用reinterpret_cast强制类型转换int ret bind(_socketfd, reinterpret_castconst sockaddr *(saddrIn), sizeof(sockaddr_in));if (ret 0){LOG(LogLevel::FATAL) Bind error strerror(errno);exit(static_castint(ErrorNumber::BindSocketFail));}LOG(LogLevel::INFO) Bind success;}// ...private:// ...uint16_t _port; // 端口号std::string _ip; // 点分十进制IP地址 };至此服务器创建完成总结一下创建服务器一共分为两步 创建服务器套接字对应的文件描述符根据套接字对应的文件描述符进行协议家族、端口号和IP地址进行绑定 启动服务器 启动服务器就需要用到一个变量标记当前服务器是否已经启动所以需要一个成员_isRunning该变量初始化为false如果当前服务器并没有启动就可以启动服务器否则就不需要启动 所谓的启动服务器就是让服务器执行指定的任务本次服务端就是负责接收信息并回复客户端发送的信息 因为服务器一般情况下一旦启动就不会再关闭为了模拟这种情况考虑服务器启动后就是一个死循环在这个循环内部就是服务器执行任务的逻辑所以基本结构如下 // 启动服务器 void start() {if (!_isRunning){_isRunning true;while (true){}} }接下来就是考虑服务器执行的任务接收客户端的消息并返回客户端的消息。对于这个任务可以拆为两个任务 接收客户端消息返回客户端接收到的消息 首先考虑接收客户端消息接收客户端消息可以使用recvfrom接口其原型如下 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);如果仔细观察上面的接口可以发现其和之前文件部分的read接口很类似该接口的第一个参数表示用于接收的套接字对应的文件描述符第二个参数表示缓冲区用于存储接收到的数据第三个参数表示缓冲区的大小第四个参数表示是否是一个标记位传递0表示使用默认的阻塞模式和行为第五个参数表示客户端的套接字结构第六个参数表示客户端套接字结构的大小 对于前四个参数都很好理解关键是第五个参数为什么服务器端接收还需要知道客户端套接字结构具体来说为什么服务器端接收需要知道客户端的端口和IP地址。最简单的解释就是因为UDP是无连接协议服务器没有固定的连接信息所以每次收到数据包时需要知道数据包的来源客户端的IP和端口以便在需要回复数据时能正确定位到发送方另外获取客户端信息有助于日志记录、错误排查以及实时监控这样可以更准确地定位是哪个客户端发来了数据以及可能出现的问题所在 但是需要注意后两个参数是输出型参数也就是说第四个参数和第五个参数的值并不需要用户指定 该接口返回读取到的字节数否则返回-1 根据这个接口的介绍可以设计第一个任务的逻辑如下同样考虑结合日志显示相应的信息 建议接收数据时留下一个位置用于存放\0 // 启动服务器 void start() {if (!_isRunning){_isRunning true;while (true){// 1. 接收客户端信息char buffer[1024] {0};struct sockaddr_in peer;socklen_t length sizeof(peer);// sizeof(buffer) - 1留下一个位置存放\0ssize_t ret recvfrom(_socketfd, buffer, sizeof(buffer) - 1, 0, reinterpret_caststruct sockaddr *(peer), length);if (ret 0){struct sockaddr_in temp static_caststruct sockaddr_in(peer);// 1.1 打印信息LOG(LogLevel::INFO) buffer;}}} }为了知道是哪一个客户端发送的消息可以考虑打印出客户端消息的同时打印出客户端的端口和IP地址此处需要注意 首先因为recvfrom接口的第四个参数类型是struct sockaddr这个结构中默认不带有端口和IP地址所以还需要转换回网络套接字结构对象 接着因为网络使用的是大端而一般的客户机都是小端所以考虑将收到的端口和IP地址转换回小端字节序对应地可以使用接口 // 将大端字节序端口转换为小端字节序 uint16_t ntohs(uint16_t netshort); // 将IP地址转换为小端字节序并按照点分十进制存储到一个静态空间注意此处返回的不是字符串 char *inet_ntoa(struct in_addr in);对于「将IP地址转换为小端字节序并按照点分十进制存储」可以使用接口inet_ntop该接口原型如下 const char *inet_ntop(int af, const void * src, char *dst, socklen_t size);该接口的第一个参数表示协议族网络通信传递AF_INET第二个参数传递struct in_addr类型变量的地址第三个参数传递一个用于存储结果的空间地址第四个参数传递第三个参数的大小 因为这个接口可以在函数的内部创建一个临时空间作为接口的第三个实参如果是多线程情况下就不会出现多个线程访问同一块空间的问题 该接口返回一个IP地址字符串 示例代码如下 void test() {struct sockaddr_in local;char ipbuffer[64];const char *ip ::inet_ntop(AF_INET, local.sin_addr, ipbuffer, sizeof(ipbuffer)); }结合这两个接口完善信息的打印 // ... struct sockaddr_in temp reinterpret_caststruct sockaddr_in(peer);// 1.1 打印信息 LOG(LogLevel::INFO) Client: inet_ntoa(temp.sin_addr) : ntohs(temp.sin_port) send: buffer; // ...至此服务器可以显示从客户端接收的消息第一个任务完成接下来处理第二个任务服务器向客户端回复收到的信息。既然是回复那么肯定涉及到发送信息此时就可以使用sendto接口其原型如下 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);如果仔细观察上面的接口可以发现其和之前文件部分的write接口很类似前四个参数和recvfrom一样不再赘述下面主要介绍第五个参数 第五个参数表示目标网络套接字结构既然是发送肯定需要知道对方的端口和IP地址所以第五个参数需要使用者自己创建对象并填充对应值 第六个参数和recvfrom一样 该接口返回成功发送的字节数否则返回-1 因为在前面已经获取到了客户端的端口和IP地址只需要直接赋值就可以正确设置客户端的网络套接字结构所以基本逻辑如下 // 2. 回应客户端 struct sockaddr_in peer_temp; peer_temp.sin_family AF_INET; peer_temp.sin_addr.s_addr temp.sin_addr.s_addr; peer_temp.sin_port temp.sin_port;ssize_t n sendto(_socketfd, buffer, sizeof(buffer), 0, reinterpret_castconst struct sockaddr *(peer_temp), sizeof(peer_temp));if (n 0) {// 2.1 打印回复信息LOG(LogLevel::INFO) Server received: buffer , and send to: inet_ntoa(temp.sin_addr) : ntohs(temp.sin_port); }停止服务器 停止服务器的逻辑很简单只需要判断_isRunning是否为true如果为true就调用文件部分提到的close接口关闭_socketfd并将_isRunning设置为false即可为了保证对象销毁时可以自动释放考虑在析构函数中调用停止服务器的接口 “停止服务器接口” // 停止服务器 void stop() {if (_isRunning)close(_socketfd); }“析构函数” ~UdpServer() {stop(); }创建并封装客户端 创建客户端类 对客户端进行封装首先需要大致的框架因为客户端主要是向服务器发送内容所以主要任务就是发送信息也就是启动客户端对应地客户端也可以有停止客户端的接口所以基本框架如下 class UdpClient { public:UdpClient(){}// 启动客户端void start(){}// 结束客户端void stop(){}~UdpClient(){}private: };创建客户端套接字 创建客户端套接字的方式和服务端此处不再赘述代码如下 enum class ErrorNumber {ClientSocketFail 1, // 创建套接字失败 };class UdpClient { public:UdpClient(): _socketfd(-1){_socketfd socket(AF_INET, SOCK_DGRAM, 0);if (_socketfd 0){LOG(LogLevel::FATAL) Client initiate error: strerror(errno);exit(static_castint(ErrorNumber::ClientSocketFail));}LOG(LogLevel::INFO) Client initiated: _socketfd;}// ...private:int _socketfd; // 套接字文件描述符 };*绑定客户端IP地址和端口 实际上客户端并不需要绑定IP地址和端口如果客户端由程序员绑定那么假设有两个公司上线的客户端使用的端口是一样的就会出现一个软件先打开之后可以正常收到服务器发送的数据但是另外一个软件的服务器就无法正确发送信息到对应的软件上即一个端口只能对应一个进程但是一个进程可以有多个端口 那么客户端难道不需要端口吗并不是如果客户端没有端口那么服务器只能通过IP地址找到具体客户端设备但是找不到对应地进程既然如此客户端的端口怎么确定实际上这个端口由操作系统自行分配 那么服务器端又为什么需要程序员手动绑定端口号因为服务器端口号如果是随机的而软件中请求服务器的端口号是固定的那么一个软件可能在某一天可以正常收到服务器发送的数据但是下一次因为服务器端口号是变化的就无法正常收到信息 综上所述服务器端需要程序员手动绑定IP地址和端口号而客户端不需要程序员手动绑定IP地址和端口号由操作系统自行分配并绑定 启动客户端 启动客户端和启动服务端的设计思路基本一致基本框架如下 // 启动客户端 void start() {if (!_isRunning){_isRunning true;while (true){}} }因为客户端的任务是向服务器端发送数据所以需要知道服务端的IP地址和端口号同样可以给定一个默认IP地址和端口也可以由用户自行设置 class UdpClient { public:UdpClient(const std::string ip default_ip, uint16_t port default_port): //..., _ip(ip), _port(port){// ...}// ...private:// ...std::string _ip; // 服务器IP地址uint16_t _port; // 服务器端口号// ... };下面就是设计客户端的任务本次设计客户端的任务为向服务器发送信息并回显服务器回复的信息同样将这个任务拆为两个任务如下 向服务器发送信息回显服务器回复的信息 首先设计第一个任务既然是向服务器发送信息那么就要使用到sendto接口这个接口在前面已经介绍过此处不再赘述代码如下 // 1. 向服务器发送数据 struct sockaddr_in local; bzero(local, 0); local.sin_family AF_INET; local.sin_port htons(_port); local.sin_addr.s_addr inet_addr(_ip.c_str());// 1.1 读取输入信息 std::string message; getline(std::cin, message);// 1.2 发送数据 ssize_t ret sendto(_socketfd, message.c_str(), message.size(), 0, reinterpret_castconst struct sockaddr *(local), sizeof(local));if (ret 0)LOG(LogLevel::WARNING) 客户端未发送成功;接着设计第二个任务回显服务器信息本质就是接收服务器的信息并显示所以需要使用recvfrom接口同样这个接口在前面已经介绍过此处不再赘述代码如下 // 2. 回显服务器的信息 struct sockaddr_in temp; socklen_t length sizeof(temp); char buffer[1024] {0}; ssize_t n recvfrom(_socketfd, buffer, sizeof(buffer) - 1, 0, reinterpret_caststruct sockaddr *(temp), length);if (n 0)LOG(LogLevel::INFO) 收到服务器信息 buffer;停止客户端 思路与服务器端一致代码如下 “停止客户端接口” // 结束客户端 void stop() {if (_isRunning)close(_socketfd); }“析构函数” ~UdpClient() {stop(); }测试 测试步骤 先启动服务端再启动客户端客户端向服务器端发送信息 测试目标 客户端可以正常向服务器端发送信息服务端可以正常显示客户端信息并正常向客户端返回客户端发送的信息客户端可以正常显示服务端回复的信息 测试代码如下 “客户端” #include udp_client.hpp#include memoryusing namespace UdpClientModule; using namespace LogSystemModule;int main(int argc, char *argv[]) {std::shared_ptrUdpClient client;if (argc 1){// 创建客户端对象——使用默认端口和IP地址client std::make_sharedUdpClient();}else if (argc 3){// 获取到用户输入的端口和IP地址std::string ip argv[1];uint16_t port std::stoi(argv[2]);// 创建客户端对象——用户自定义端口和IP地址client std::make_sharedUdpClient(ip, port);}else{LOG(LogLevel::ERROR) 错误使用正确使用为 argv[0] IP地址 端口号或者二者都不存在;exit(3);}// 启动客户端client-start();return 0; }“服务端” #include udp_server.hpp #include memoryusing namespace UdpServerModule;int main() {// 创建UdpServerModule对象std::shared_ptrUdpServer udp_server std::make_sharedUdpServer();udp_server-start();return 0; }本次设计的客户端支持用户从命令行输入端口和IP地址否则就直接使用默认下面是一种结果 测试云服务器与本地进行通信 因为此时需要确保服务端运行在云服务器的公网IP上否则客户端无法找到服务端服务端测试代码修改如下 #include udp_server.hpp #include memoryusing namespace UdpServerModule;int main(int argc, char *argv[]) {// 创建UdpServerModule对象std::shared_ptrUdpServer udp_server;if (argc 1){udp_server std::make_sharedUdpServer();}else if (argc 3){std::string ip argv[1];uint16_t port std::stoi(argv[2]);udp_server std::make_sharedUdpServer(ip, port);}udp_server-start();return 0; }测试云服务器与本地进行通信最直接的步骤如下 将服务端程序拷贝到云服务器本地作为客户端通过云服务器的公网IP地址连接云服务器的服务端客户端向云服务器发送信息 根据上面的步骤依次进行 将服务端程序拷贝到云服务器 因为传输到云服务器的文件默认是没有可执行权限的所以需要使用chmod指令设置可执行权限 接着指定IP地址为云服务器公网IP地址端口为8080运行云服务器的服务端 从上图可以看到虽然创建套接字成功但是绑定失败。之所以出现这个问题是因为云服务器的公网IP地址是不允许用户自行绑定的但是如果是虚拟机就可以进行绑定IP地址非127.0.0.1地址 那么有没有什么办法解决呢有但是在实现解决方案之前先了解下面的知识 前面的代码在启动时都为服务器设置启动IP地址但是思考一个问题启动服务器真的需要指定IP地址吗并不需要那如果不需要指定服务器端IP地址客户端怎么找到服务器呢回到这个问题之前先解释为什么启动服务器不需要指定IP地址。实际上之所以不需要IP地址是因为一台服务器可能有多个IP地址此时如果服务器固定IP地址那么此时就会出现服务器只能接收传送到固定IP地址的信息就算服务器有很多IP地址也只有一个IP地址可以使用很明显这个效果并不符合UDP协议的特点因为UDP协议是面向无连接的既然都不需要连接为什么还需要指定IP地址所以启动服务器不需要指定IP地址。有了这个概念之后再解释没有指定服务器端IP地址客户端怎么找到服务器实际上只需要客户端启动的时候指定已经知道的服务器IP地址和端口号即可对应的服务器只需要设置好端口号即可完成通信 有了上面的概念对服务器端代码修改如下 “服务器端封装代码” // 默认端口和IP地址// const std::string default_ip 127.0.0.1; 去除// ...class UdpServer{public:UdpServer(uint16_t port default_port /* const std::string ip default_ip 服务器端不需要指定IP地址 */): _socketfd(-1), _port(port), _isRunning(false) /* , _ip(ip) */{// ...// 绑定端口号和IP地址// ...// saddrIn.sin_addr.s_addr inet_addr(_ip.c_str());// 服务器端IP地址设置为任意saddrIn.sin_addr.s_addr INADDR_ANY;// ...}// ...private:// ...// std::string _ip; // 点分十进制IP地址——去除// ...};“主函数” #include udp_server.hpp #include memoryusing namespace UdpServerModule;int main(int argc, char *argv[]) {// 创建UdpServerModule对象std::shared_ptrUdpServer udp_server;if (argc 1){udp_server std::make_sharedUdpServer();}else if (argc 2){// std::string ip argv[1]; 去除uint16_t port std::stoi(argv[1]);udp_server std::make_sharedUdpServer(port);}else{LOG(LogLevel::ERROR) 错误使用正确使用 argv[0] 端口或者不写;exit(4);}udp_server-start();return 0; }在上面的服务器端封装代码中因为不需要指定IP地址在绑定时对于struct sockaddr_in中的IP地址字段设置为INADDR_ANY表示0.0.0.0即任意IP地址 在底层实际上就是0 #define INADDR_ANY ((in_addr_t) 0x00000000)现在再按照前面提到的三步进行云服务器与本地进行通信 将服务端程序拷贝到云服务器 前面两步不变只演示最后一步 可以看到服务器已经正常启动了 本地作为客户端通过云服务器的公网IP地址连接云服务器的服务端 启动本地的客户端 客户端向云服务器发送信息 如果客户端可以正常发送信息并且回显服务器回复的信息服务器可以正常显示来自客户端的信息并且正常回复客户端收到的信息那么说明连接成功 从上图中可以看到客户端和服务端已经可以正常通信至此从前面的本地通信测试完成了网络通信 需要注意如果使用修改后的代码可以实现本地通信但是使用修改后的代码无法实现网络通信例如客户端正确指定云服务器IP地址和端口号并正常启动服务器也是正常启动但是客户端发送消息服务端并没有反应此时可能是云服务器的安全组问题如果云服务器的安全组并没有允许其他设备通过UDP协议向当前云服务器发送信息那么就会出现客户端发送消息服务端没有反应的情况。对于这种情况可以在云服务的安全组配置中允许UDP协议和对应的端口 部分细节优化 在前面对服务端和客户端进行封装时可以发现有些代码其实是可以进行合并的还有一部分代码是可以进行封装的下面就这些代码进行优化 合并重复代码 在出现错误时服务端和客户端的退出码应该是一致的此时可以对错误码枚举类进行抽取放到一个公共的文件中 // 文件errors.hpp中 #pragma once// 错误码枚举类 enum class ErrorNumber {SocketFail 1, // 创建套接字失败供服务端和客户端使用BindSocketFail, // 绑定失败供服务端使用 };再在服务端封装文件和客户端封装文件中引入该文件并修改指定位置的代码即可不再演示 封装struct sockaddr_in #pragma once#include iostream #include string #include cstring #include cerrno #include sys/socket.h #include netinet/in.h #include arpa/inet.hnamespace SockAddrInModule {class SockAddrIn{private:// 大端转换为本地端口值void NetPort2Local(){_port ntohs(_s_addr_in.sin_port);}// 将IP地址转换为小端字节序并按照点分十进制存储void NetIP2Local(){char buffer[1024] {0};const char *ip inet_ntop(AF_INET, _s_addr_in.sin_addr, buffer, sizeof(buffer));_ip buffer;}public:// 无参构造SockAddrIn(){}// 根据指定的sockaddr_in对象进行构造SockAddrIn(const struct sockaddr_in s): _s_addr_in(s){// 转换为本地小端用于使用NetIP2Local();NetPort2Local();}// 根据具体端口构造SockAddrIn(uint16_t port): _port(port){// 内部通过传入的端口对sockaddr_in对象进行初始化_s_addr_in.sin_family AF_INET;_s_addr_in.sin_port htons(_port);_s_addr_in.sin_addr.s_addr INADDR_ANY;}// 根据端口和IP地址构造SockAddrIn(uint16_t port, std::string ip): _ip(ip), _port(port){_s_addr_in.sin_family AF_INET;_s_addr_in.sin_port htons(_port);_s_addr_in.sin_addr.s_addr inet_addr(_ip.c_str());}// 重载struct sockaddr *operator(){return reinterpret_caststruct sockaddr *(_s_addr_in);}// 获取struct sockaddr_in对象长度socklen_t getLength(){return sizeof(_s_addr_in);}// 返回IP地址std::string getIp(){return _ip;}// 返回端口号uint16_t getPort(){return _port;}~SockAddrIn(){}private:struct sockaddr_in _s_addr_in;std::string _ip;uint16_t _port;}; }根据上面的封装修改服务端和客户端 “服务端” class UdpServer { public:UdpServer(uint16_t port default_port): // ..., _sa_in(port)// ...{// ...// 绑定端口号和IP地址// struct sockaddr_in saddrIn;// saddrIn.sin_family AF_INET;// saddrIn.sin_port htons(_port);// // saddrIn.sin_addr.s_addr inet_addr(_ip.c_str());// // 服务器端IP地址设置为任意// saddrIn.sin_addr.s_addr INADDR_ANY;// 使用reinterpret_cast强制类型转换// int ret bind(_socketfd, reinterpret_castconst sockaddr *(saddrIn), sizeof(sockaddr_in));int ret bind(_socketfd, _sa_in, _sa_in.getLength());// ...}// 启动服务器void start(){if (!_isRunning){_isRunning true;while (true){// 1. 接收客户端信息// ...struct sockaddr_in peer;// ...if (ret 0){// 1.1 打印信息// struct sockaddr_in temp peer;// LOG(LogLevel::INFO) Client: // inet_ntoa(temp.sin_addr) :// ntohs(temp.sin_port)// send: buffer;SockAddrIn temp(peer);LOG(LogLevel::INFO) Client: temp.getIp() : temp.getPort() send: buffer;// 2. 回应客户端// ssize_t n sendto(_socketfd, buffer, sizeof(buffer), 0, reinterpret_castconst struct sockaddr *(temp), sizeof(temp));ssize_t n sendto(_socketfd, buffer, sizeof(buffer), 0, temp, temp.getLength());if (n 0){// 2.1 打印回复信息// LOG(LogLevel::INFO) Server received: // buffer// , and send to: // inet_ntoa(temp.sin_addr) :// ntohs(temp.sin_port);LOG(LogLevel::INFO) Server received: buffer , and send to: temp.getIp() : temp.getPort();}}}}}// ...private:// ...// uint16_t _port; // 端口号// // std::string _ip; // 点分十进制IP地址——去除SockAddrIn _sa_in;// ... };“客户端” class UdpClient{public:UdpClient(const std::string ip default_ip, uint16_t port default_port): // ..., _sa_in(port, ip){// ...}// 启动客户端void start(){if (!_isRunning){_isRunning true;while (true){// 1. 向服务器发送数据// struct sockaddr_in local;// bzero(local, 0);// local.sin_family AF_INET;// local.sin_port htons(_port);// local.sin_addr.s_addr inet_addr(_ip.c_str());// ...// 1.2 发送数据// ssize_t ret sendto(_socketfd, message.c_str(), message.size(), 0, local, sizeof(local));ssize_t ret sendto(_socketfd, message.c_str(), message.size(), 0, _sa_in, _sa_in.getLength());// ...}}}// ...private:// ...// std::string _ip; // 服务器IP地址// uint16_t _port; // 服务器端口号SockAddrIn _sa_in;// ...};
http://www.ho-use.cn/article/10819840.html

相关文章:

  • 省级建设网站域名解析wordpress主页
  • 网站制作网站制作公司章丘营销型网站设计公司
  • 织梦做的网站图片路径在哪里微网站推广
  • 惠州网站设计哪家好seo网络营销外包公司
  • 保定做网站电话机械设备行业网站建设
  • 建网站非要做外链吗薪酬体系搭建工具
  • 做网站难吗_挣钱吗东莞东坑网站设计
  • 建站小程序快速上线你们公司的网站都备案了吗
  • 建设的基本流程网站怎么做网页赚取点击率从而赚钱
  • 深圳网站制作网站建设零基础网站制作视频教程
  • 用什么网站可以做电子书设计网页代码源代码
  • 贵州省城乡住房和建设厅网站老干支部网站建设方案
  • 电商网站首页设计规范手机上的软件网站建设
  • 化工原材料网站建设怎么学平面设计啊
  • 自己可以做百度网站吗电子商务教材电子版
  • 柯城网站建设免费在线观看高清影片
  • 大流量网站开发网站备案被注销
  • 联英人才网重庆招聘网百度seo排名优化助手
  • 成都大丰网站建设例表网网站排名优化服务商
  • 英文网站建设 潍坊手机电商网站 模板
  • 建设外贸企业网站wordpress 小学生
  • 常熟市住房建设局网站门户网站设计与开发
  • 做家常便饭网站韶关住房和城乡建设网站
  • 建工网官方网站标准网站建设哪家便宜
  • 微网站如何建立的地下城钓鱼网站怎么做
  • 可以做翻译兼职的网站有哪些wordpress服务器要求
  • 重庆秀山网站建设价格google chrome官网
  • 怎么建设网站最便宜公司网站简介怎么做
  • 山西推广型网站制作风铃网做微网站要钱吗
  • 动漫网站怎么做的郴州seo公司