# UDP 通信程序设计
【实验名称】
基于 UDP 丢包统计程序设计
【实验目的】
选择一个操作系统(Linux 或者 Windows),编制 UDP/IP 通信程序,完成一定的通信功能。
【实验要求】
在发送 UDP 数据包时做一个循环,连续发送 100 个数据包;在接收端统计丢失的数据包。
实验时,请运行 Wireshark 软件,对通信时的数据包进行跟踪分析。
【实验原理】
![](https://www.writebug.com/myres/static/uploads/2021/11/15/5f4428ed822b2d3380e8ec686bbf6426.writebug)
以上为一般 UDP 网络编程的流程图,在本次实验中仅涉及客户端发送数据和服务器接收数据,因此本次实验的实
验流程图如下:
![](https://www.writebug.com/myres/static/uploads/2021/11/15/733694073ae47342fc33d5b9b730b8de.writebug)
【实验内容】根据流程图开始编程,下面进行代码分析:
客户端代码 UDP_Cli.cpp
## 1.1 /*创建 Socket*/
```c++
SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockCli < 0)
{ cout << "Failed." << endl;
return -1;
}
cout << "Create socket successfully." << endl;
```
调用库函数 socket 创捷套接字,若返回值 <0 则说明创建套接字失败,退出程序。
socket 声明如下:
```c++
WINSOCK_API_LINKAGE SOCKET WSAAPI socket(int af,int type,int protocol);
```
第一个参数指明了协议簇,目前支持 5 种协议簇,最常用的有 AF_INET(IPv4 协议)和 AF_INET6(IPv6 协议);第二个参数指明套接口类型,有三种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和 SOCK_RAW(原始套接口);如果套接口类型不是原始套接口,那么第三个参数就为 0。 在本次实验中使用 AF_INET 协议簇,SOCK_DGRAM 数据报接口,第三个参数为 UDP 的 protocol。
```c++
/*向指定地址和端口收发数据*/
char recvBuf[BUFSIZE]; //接受数据的缓冲区 string sendBu= "Hello server! This is a packet. Data:"; //发送数据的缓冲区 char tmp[BUFSIZE];
SOCKADDR_IN addr_server; //服务器的地址数据结构 addr_server.sin_family = AF_INET;
addr_server.sin_port = htons(6666); //端口号为6666 addr_server.sin_addr.S_un.S_addr=inet_addr("127.0.0.1"); //127.0.0.1为本电脑IP地址 int server_len = sizeof(addr_server); for (int i = 1; i <= 100; i++){
itoa((rand() % 100000), tmp, 10);
string sendBuf = sendBu + tmp;
err = sendto(sockCli, sendBuf.data(), sendBuf.size(), 0, (SOCKADDR *)&addr_server, sizeof(SOCKADDR)); //发送 if (err < 0){ cout << "Sendto failed."<< endl; return -1;
} else {
cout << "Packet " << i << " has been sent." << endl;
}
}
```
使用 sendto 函数向客户端发送 100 个数据包,若发送成功则输出报告,失败则退出程序。每个数据包包括一句固定的问候语和需要发送的数据,在这里为 0~99999 的一个随机数,以字节为单位发送。
```c++
WINSOCK_API_LINKAGE int WSAAPI sendto(SOCKET s,const char *buf,int len,int flags,const str uct sockaddr *to,int tolen);
```
sendto 函数:UDP 使用 sendto()函数发送数据,他类似于标准的 write(),但是在 sendto()函数中要指明目的地址。前三个参数等同于函数 read()的前三个参数,flags 参数是传输控制标志。参数 to 指明数据将发往的协议地址,他的大小由 addrlen 参数来指定。
它返回发送数据的长度大于或等于 0 说明发送成功,失败则返回-1。
```c++
/*关闭套接字*/
closesocket(sockCli);
```
发送完毕后关闭套接字。
服务端代码 UDP_Ser.cpp
## 1.2 /*创建 Socket*/
```c++
int sockSev = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockSev < 0)
{ cout << "Failed to create socket." << endl;
return -1;
}
cout << "Create socket successfully." << endl;
```
过程和客户端大致相同。
## 1.3 /*绑定 socket 和端口号*/
```c++
SOCKADDR_IN addr_server; //服务器的地址数据结构 addr_server.sin_family = AF_INET; addr_server.sin_port = htons(6666); //端口号为6666 addr_server.sin_addr.S_un.S_addr=inet_addr("172.19.1.207"); //172.19.1.207为本电脑IP 地址
if (bind(sockSev, (SOCKADDR *)&addr_server, sizeof(addr_server)) == SOCKET_ERROR)
{ cout<<"Failed to bind."<< endl;
closesocket(sockSev);
WSACleanup();
return 0;
} else
cout << "Bind successfully." << endl;
```
创建服务器的地址数据结构并对其进行协议簇、端口号和 IP 地址的配置,再使用 bind 函数将创建好的 socket 绑定到该地址上。
```c++
WINSOCK_API_LINKAGE int WSAAPI bind(SOCKET s,const struct sockaddr *name,int namelen);
```
- bind 函数描述:把一个地址族中的特定地址赋给 socket 参数解释: s:指的是通过 socket()创建的描述字,唯一标识一个 socket。
- name:一个指针,指向要绑定的协议地址。
- namelen:该地址结构体的长度
```c++
/*向指定地址和端口收发数据*/
char recvBuf[BUFSIZE]; //接受数据的缓冲区
SOCKADDR_IN addr_client; //用于接收用户的ip地址和端口号等信息 int client_len = sizeof(addr_client); int count = 0; while(true){
int last = recvfrom(sockSev, recvBuf, BUFSIZE, 0, (SOCKADDR *)&addr_client, &clien t_len);
if (last <= 0)
{ cout << "Recvfrom Error!" << endl;
continue;
} else {
cout << "Recvfrom:" << setw(7) << recvBuf;
cout << " Count:" << ++count << endl;
}
}
```
使用 recvfrom 函数监听发送来的数据,若接收成功则输出结果。同时使用 count 来累计成功接收到的数据包的个数。
```c++
WINSOCK_API_LINKAGE int WSAAPI recvfrom(SOCKET s,char *buf,int len,int flags,struct socka ddr *from,int *fromlen);
```
参数解释: s:标识一个已连接套接口的描述字。
buf:接收数据缓冲区。 len:缓冲区长度。 flags:调用操作方式。 from:(可选)指针,指向装有源地址的缓冲区。 fromlen:(可选)指针,指向 from 缓冲区长度值。
由于 Windows 系统下使用 socket 需进行注册,注册过程如下:
## 1.4 /*Winsocket 注册过程*/
```c++
WORD wVersionRequested = MAKEWORD( 2, 0 ); // 请求WinSock库,高字节指明副版本,低字节指明主版本
WSADATA wData; // 这结构是用于接收Wjndows Socket的结构信息的(版本信息) int err; err = WSAStartup(wVersionRequested, &wData); //Winsock服务初始化 if ( err != 0 ) { cout << "Initialize failed."<<endl; return -1; // 返回值为零时表示成功WSAStartup
}
```
- 这段代码需要被放在客户端和服务器的代码中。
- 互联网环境
- 使用公网上的 UDP 的 echo 服务,将客户端本机地址与套接字绑定,进入监听,以便能接收到 echo。
```c++
SOCKADDR_IN addr_client; //服务器的地址数据结构 addr_client.sin_family = AF_INET; addr_client.sin_port = htons(6789); //端口号为6789 addr_client.sin_addr.S_un.S_addr=inet_addr("127.0.0.1"); //127.0.0.1为本电脑IP地址 if (bind(sockCli, (SOCKADDR *)&addr_client, sizeof(addr_client)) == SOCKET_ERROR)
{ cout<<"Failed to bind."<< endl;
closesocket(sockCli);
WSACleanup();
return 0;
} else cout << "Bind successfully." << endl;
```
将目标地址设置为公网上的地址
```c++
SOCKADDR_IN addr_server; //服务器的地址数据结构 addr_server.sin_family = AF_INET; addr_server.sin_port = htons(6789); addr_server.sin_addr.S_un.S_addr=inet_addr("8.129.101.161");
```
每次发送后接收 echo
## 1.5 //接收 echo 的数据
```c++
int last = recvfrom(sockCli, recvBuf, BUFSIZE, 0, (SOCKADDR *)&addr_recv, &recv_le n);
if (last <= 0)
{ cout << "Recvfrom Error!" << endl;
continue;
} else {
基于C++实现 UDP 通信程序设计【100010818】
版权申诉
14 浏览量
2023-02-15
17:22:51
上传
评论
收藏 1.91MB ZIP 举报
神仙别闹
- 粉丝: 2674
- 资源: 7640
最新资源
- 基于flask和echarts融合交易策略的bitfinex可视化微服务.zip
- 包含了wvp-assist.tar wvp-talk.tar zlmediakit.tar .
- 3r4efgh53wgrf43tw
- 2024新版Java基础从入门到精通全套视频+资料下载
- Spring AI大模型视频教程+ChatGPT视频教程+OpenAI大模型视频教程(资料+视频教程)
- ABB工业机器人教程PDF版本
- 123321123323211
- yolov8实战第八天-pyqt5-yolov8实现车牌识别系统(论文(约7000字)+数据集+完整部署代码+代码使用说明)
- 三相桥式全桥整流电路MATALB Simulink仿真文件
- ABB机器人操作培训文档
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈