打怪升级之UDP数据接收实验的服务端代码
C++语言下的windows网络编程(SOCKET)
本文的主要目的是以C++语言在windows下实现UDP网络服务端的代码(VS2022)。
所需的头文件调用
#include<iostream>
#include<WinSock2.h>
#include<WS2tcpip.h>
#include<stdlib.h>
#pragma comment(lib,"ws2_32.lib")
WSA初始化
WSA的初始化可以归结到WSAStartup()函数中去:https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsastartup。
WSAStartup()返回值不为0时,说明出现了错误,使用WSAGetLastError()获取报错码。
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")//初始化WSAWSADATA wsaDATA;WORD wVersion = MAKEWORD(2, 2);if (WSAStartup(wVersion , &wsaDATA) != 0){printf("WSAS errocode is %d\n",WSAGetLastError());return -1;}
错误代码的查询可以前往windows文档查看:https://learn.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2。
SOCKET初始化
初始化主要函数位socket()函数,文档:https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket。
其返回值不为0时发生错误,错误代码使用VS文档中可以自查的代码文档:https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes。
也可以使用WSAGetLastError里的error数据。
使用getlasterror返回的错误值是系统、WSA等的错误值,如果你觉得直接的int值有问题,可以直接打印出来。
int sockfd;sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err is \n");return -1;}
这里阅读原文文档可以知道,错误的socket函数会返回INVALID_SOCKET值。(正常的话返回不是0)(INVALID_SOCKET就是一个22长的整形 -1)
perror函数是一个输出给std标准流的报错函数。
IP地址
IP地址主要就是定义一个结构体:sockaddr与sockaddr_in
struct sockaddr{unsigned short sa_family; //地址类型,AF_XXXchar sa_data[14]; //14字节的端口和地址
};struct sockaddr_in{short int sin_family; //地址类型unsigned short int sin_port;//端口号struct in_addr sin_addr; //地址unsigned char sin_zero[8]; //为了保持与struct sockaddr一样的长度。
};struct in_addr{unsigned long s_addr; //地址
};
这两种地址都是16字节的形式。包含了IP族名称,端口号和IP地址。
对于我们常用的IPV4来说,常用sockaddr_in这个结构体表示。
最终输入到bind函数中去的,只要时一个16位结构体数据就可以了,注意其中的字节序和IP地址可能会有的点序问题即可。
//函数将整形变量的网络字节顺序big-endian。
int htons();
//将一个网络字节顺序转换为主机字节顺序
int ntohs();
//函数将无符号长整形变量的网络字节顺序big-endian。
int htonl();
//函数将网络字节顺序变为主机顺序。
int ntonl();//将主机字节序的IP地址转换为网络字节序,网络字节序为整形数
int inet_pton();
//将大端的整形数, 转换为小端的点分十进制的IP地址 (字符串)
int inet_ntop();
上述是字节序调整函数。
#include<WinSock2.h>#include<WS2tcpip.h>//inet_pton函数在<WS2tcpip.h>头文件中。sockaddr_in Addr;Addr.sin_family = AF_INET;inet_pton(AF_INET,"100.65.90.180",&Addr.sin_addr);Addr.sin_port = htons(1234);
上述是地址写入举例,其中inet_pton是VS2022版本后由于不支持原来的inet_addr函数而更新的新函数,目的是将变量1指定(IP族格式)的变量2(String IP)按正确的方式写入变量3(可用于bind的地址结构体)中。
这里图省事就没有判断inet_pton返回值所代表的合法性了。
无论是socketaddr结构体还是socketaddr_in结构体,亦或是你自己定义的某个16字节的结构体,在保证顺序正确的前提下,都可以输入bind函数中作为IP地址的绑定。还是那句话,不管宏展开的变量叫什么,变量的本质不过是某一串二进制数据罢了。
如果你不是想要绑定某个特定的IP,而是直接接收所有的IP地址来源,你可以使用:
Addr.sin_addr.s_addr = htonl(INADDR_ANY);
这里的INADDR_ANY是提前定义好的变量名。
bind()
没什么好说的,bind就完事了。bind的返回值也是一个判断值,其文档是:https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind。
bind(sockfd, (sockaddr*)&Addr, sizeof(Addr));
注意输入函数的变量格式,地址结构体的输入方法是一个地址的地址指针。
加上错误判断的话就是:
if (bind(sockfd, (struct sockaddr *)&Addr, sizeof(Addr)) != 0){perror("bind err is");return -1;}
bind返回值是基于0的判断条件的。
read()
可以直接使用WSA下的recvfrom函数接收来自指定地点的信息。
recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &len)
具体文档参见:https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-recvfrom
其返回值代表接收到的字节数,如果连接正常关闭,则返回值为零。
while(1){if (recvfrom(sockfd, (char *) &msg, sizeof(msg), 0, (struct ClientAddr*)&ClientAddr, &len) < 0){perror("recvfrom err.");return -1;}
}
这里要注意的有两点,第一个是msg作为一种内存结构体,在recvfrom函数中由内存首位指针(&msg)和内存长度(sizeof(msg))决定。
recvfrom的IP地址(函数的第五个变量)是对接收端socket的ip地址的描述,需要提前声明这个变量后,函数将对这个变量完成赋值。
具体的函数功能需要查看文档中的in、out描述来确定,而接收到的数据呢,则是一个char *类型的(你也可以使用转换函数编程任何你想要的东西)。
另外,如果你想要正确的显示你的IP地址,显示你的端口号,显示你的实际接收内容,那么你需要调换字节序并可能使用到下列函数:
//int af指的是AF_INET和AF_INET6,后两者是写入变量
int inet_pton(int family, const char * src, void * dst);
//返回值其实是一个PCSTR类型(标准类型的IP格式和字符串表现格式,
//详情见另一篇专门讲这个的博客或者去查找C++ANSI编码与Unicode编码之间的关系。)
PCSTR WSAAPI inet_ntop(int family, const char * pAddr, PSTR pStringBuf, size_t StringBufSize);
//这里的PCSTR是win系统下的一种单独def的指针,可以通过(char * )来转换,其返回的就直接是地址的string指针。
(char *) inet_ntop;
为了方便的使用messge传递信息,我们最好定义一个新的结构体:
typedef struct msg_t
{int type;char text[100];
} MSG_t;
注意将结构体的指针转化成对应的char *指针输入到对应的read函数中去就好了。
好了,写到这里,差不多已经可以检验一下我们的socket成色如何了。
使用网络调试助手或者wireshark可以轻松的生成指定IP地址和端口的数据(但受限于windows API 往往不能同时调用同样的IP作为主机,但你可以以发送端IP去发送是没有问题的),让我们来实际检验一下吧。
检验代码:
//初始化云端的ip地址变量sockaddr_in ClientAddr;int Clientlen = sizeof(ClientAddr);//初始化消息结构体sMSG_t msg;char buf[32] = { 0 };//循环接收来自云端的数据while (1){printf("UDP server is ready to get information!\n");printf("ServerAddr is %s\n", (char*)inet_ntop(AF_INET, &ServerAddr.sin_addr, buf, sizeof(buf)));printf("ServerAddr is %d\n", ntohs(ServerAddr.sin_port));if (recvfrom(sockfd, (char *) & msg, sizeof(msg), 0, (struct sockaddr*)&ClientAddr, &Clientlen) < 0){perror("recvfrom err \n");printf("WSAS errocode is %d\n", WSAGetLastError());return -1;}printf("ClientAddr is %s\n", (char*)inet_ntop(AF_INET, &ClientAddr.sin_addr, buf, sizeof(buf)));printf("ClientAddr is %d\n", ntohs(ClientAddr.sin_port));printf("msg typr is %d\n",msg.type);printf("msg text is %s\n", msg.text);}
这里提前定义好服务器主机的IP地址,注意,服务器主机的IP地址固定只有那几个,可以上CMD用-arp -a指令调出来。
调出本地可用的IP地址后,打开网络调试助手(没有就自己百度下一个去)。调整发送IP地址和端口为你的服务器IP地址和端口(注意由于Windows API的限制,一个IP主机地址一次只能由一个进程调用,所以网络调试的本地主机地址不能跟服务器主机地址在一个局域网中完全一样)。
这里发送了一个UDP数据包:http://www.bilibili.com这一串字符串,通过我们的msg.type定义可以知道,UDP数据包的前一个int(也就是4个字节)数据给到了type实体里,后面的数据给到了text里。
实际编码过程中使用的是ASCII编码,http这四个最前面的字符就变成了68 74 74 70这四个数字(16进制)。其二进制码表现为:
字母 | ASCII (16进制) | 二进制 |
---|---|---|
h | 68 | 01101000 |
t | 70 | 01110000 |
t | 70 | 01110000 |
p | 74 | 01110100 |
再看实际转换为int后的type数据为:
1886680168(十进制)
1110000011101000111010001101000(二进制)
1110000 01110100 01110100 01101000 (二进制)
整理成下面这个样子就是:
二进制:
01110000 p
01110100 t
01110100 t
01101000 h
刚好就是http,低位在前的意思。
所以,想要给int type赋值为1,前四个字符串应该为
00000000
00000000
00000000
00000001
转换成UDP字节序就是,你需要发送01 00 00 00 到UDP就可以让你的int值刚好为1了。
或者你也可以用一个简单的办法就是转换成char,单字节的顺序和UDP就是完全相同的了。
最后还有一个问题是,为什么text里面会出现那么多的“烫”。
这是因为,编译器会自动给msg里面填充检错码0xCC对应到ASCII里面就是汉字烫。
如何这些修复BUG?
第一个看上去是BUG的东西是msg.text的最后还加上了一串IP地址。不要慌,我们查阅recvfrom文档看看怎么肥是:https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-recvfrom。结果发现没有啊,不应该啊,应该就是那么长啊,怎么回事呢?
后来发现主要的原因是,如果你的UDP包里面没有ASCII里面的NULL符号的话,C++的char[]是不知道应该在哪里停的,所以就会一直读下去,解决的办法就是在ASCII码的最后加上00。如下图:
第一次没有加最后红圈的00,所以出现了char数组的读取错误,第二次加上之后就正常了。00的ASCII码为\意味右划线,可以加到char数组的最后一个。
第二个可能的bug是,如果你使用了wireshark这一类软件进行监控的话,那么你的C语言进程很可能就收不到信号了,因为wireshark已经调用了这个IP地址。
服务端代码总览
#include<iostream>
#include<WinSock2.h>
#include<WS2tcpip.h>
#include<stdlib.h>
#pragma comment(lib,"ws2_32.lib")typedef struct msg_t
{int type;char text[100];
} MSG_t;int main(int argc, char* argv[])
{//初始化WSAWSADATA wsaDATA;WORD wVersion = MAKEWORD(2, 2);if (WSAStartup(wVersion, &wsaDATA) != 0){printf("WSAS errocode is %d\n", WSAGetLastError());return -1;}//初始化SOCKETint sockfd;sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err is \n");printf("WSAS errocode is %d\n", WSAGetLastError());return -1;}//建立socket_addrsockaddr_in ServerAddr;ServerAddr.sin_family = AF_INET;inet_pton(AF_INET, "192.168.1.102", &ServerAddr.sin_addr);ServerAddr.sin_port = htons(1234);//bind绑定socket和ip地址、端口if (bind(sockfd, (struct sockaddr*)&ServerAddr, sizeof(ServerAddr)) != 0){perror("bind err is \n");printf("WSAS errocode is %d\n", WSAGetLastError());return -1;}//初始化云端的ip地址变量sockaddr_in ClientAddr;int Clientlen = sizeof(ClientAddr);//初始化消息结构体sMSG_t msg;char buf[32] = { 0 };//循环接收来自云端的数据while (1){printf("UDP server is ready to get information!\n");printf("ServerAddr is %s\n", (char*)inet_ntop(AF_INET, &ServerAddr.sin_addr, buf, sizeof(buf)));printf("ServerAddr is %d\n", ntohs(ServerAddr.sin_port));printf("msg text len is %d\n", sizeof(msg));if (recvfrom(sockfd, (char *) & msg, sizeof(msg), 0, (struct sockaddr*)&ClientAddr, &Clientlen) < 0){perror("recvfrom err \n");printf("WSAS errocode is %d\n", WSAGetLastError());return -1;}printf("ClientAddr is %s\n", (char*)inet_ntop(AF_INET, &ClientAddr.sin_addr, buf, sizeof(buf)));printf("ClientAddr is %d\n", ntohs(ClientAddr.sin_port));printf("msg typr is %d\n",msg.type);printf("msg text is %s\n", msg.text);printf("msg text len is %d\n", sizeof(msg.text));printf("msg text len is %d\n", sizeof(msg));}closesocket(sockfd);return 0;
}
打怪升级之UDP数据接收实验的服务端代码相关推荐
- Esp8266学习之旅⑧ 你要找的8266作为UDP、TCP客户端或服务端的角色通讯,都在这了。(带Demo)
本系列博客学习由非官方人员 半颗心脏 潜心所力所写,不做开发板.仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 序号 SDK版本 内容 链接 1 nonos2.0 搭建 ...
- Udp数据接收和发送代码
每一台电脑都有自己的ip地址,向指定的ip地址发数据,数据就发送到了指定的电脑.UDP通信只是一种通信方式而已,有了ip地址数据就能发送到指定的电脑了,但是呢!我把数据发送到电脑了,电脑中的哪个程序接 ...
- python交互式编程客户端_【python】UDP网络编程:实现服务端与客户端的交互、简单的AI智能模式...
关于UDP网络编程 UDP(user datagram protocol)的中文叫用户数据报协议,属于传输层.UDP是面向非连接的协议,它不与对方建立连接,而是直接把要发的数据发给对方. [UDP网络 ...
- 解决udp socket报socketException使得服务端不再正常工作
在写服务端udp socket接受数据时,曾遇到一个不容易察觉的问题: ------由于客户端的不正常操作/关闭(频繁打开.关闭,就会出现这个不容易察觉的问题),导致服务端遇到异常:SocketExc ...
- 分区数据导出功能(服务端实现)
服务端实现 第一步:查询所有的分区数据 第二步:使用POI将数据写到Excel文件中 第三步:使用输出流进行文件下载 package com.learn.bos.web.action;import j ...
- FPGA----ZCU106与RTDs的udp数据收发实验
1.硕士即将毕业,准备继续读博喽,欢迎大家报考NEEPU. 2.实验目标:依据RTDs设备的GTNET SKT网络协议,完成ZCU106 PS侧与RTDs的HIL实验. 3.实验任务:①TRDs发送数 ...
- php ini 长连接秒数,php使用webSocket实现Echarts长连接自动刷新的解决方案(2):后端服务端代码返回json数据...
$address = "127.0.0.1"; $port = 9090; //调试的时候,可以多换端口来测试程序! set_time_limit(0); $sock = sock ...
- Nginx测试实验--rest服务端负载均衡应用
2019独角兽企业重金招聘Python工程师标准>>> Nginx版本如何查看(windows系统下) 使用命令 nginx -v 试验准备--试验环境为单位局域网 0.准备两个服务 ...
- 服务端mysql数据存储设计_服务端开发指南与最佳实战 | 数据存储技术 | MySQL(03) 如何设计索引...
改善性能最好的方式,就是通过数据库中合理地使用索引,换句话说,索引是提高 MySQL 数据库查询性能的主要手段.在下面的章节中,介绍了索引类型.强制索引.全文索引. 基本索引类型 MySQL 索引可以 ...
最新文章
- WebService入门教程_HTTP协议学习
- butter fly graph
- python yield用法举例说明
- 关于document.referrer的使用需要注意
- 客户端获取游客IP,获取客户地理信息,展示地图
- vscode 支持 markdown 流程图
- 02 理解==与Equals()的区别及用法 1214
- Spring MVC Interceptor Handler InterceptorAdapter HandlerInterceptor示例
- BP神经网络(手写数字识别)
- 关于单行和多行文本溢出显示省略号的解决方案
- 美国国防部设立承包商网络漏洞披露计划
- socket-(2)
- [转载] Python杂谈 | (6) numpy中array()和asarray()的区别
- 一入前端深似海,从此红尘是路人系列第四弹之未来前端路该何去何从
- Linux内核安全模块学习-导言
- 使用easypoi导出excel设置表头样式
- Windwos磁盘管理工具diskpart
- sketch中制作蒙版及通道蒙版
- 智慧城市构建的核心问题:数据信息的安全性与隐私性
- 理想电压源和理想电流源