通信编程:UDP 套接字和广播通信

UDP 套接字#

工作流程#

UDP 用户数据报协议提供的是无连接的、不可靠的数据传输服务,但是不需要像 TCP 那样建立连接,所以传输效率高。

数据收发#

UDP 使用的发送数据的函数是 sendto(),比较不同的是发送数据时需要填写接收方的地址。

Copy Highlighter-hljs
int WSAAPI sendto( _In_ SOCKET s, _In_reads_bytes_(len) const char FAR * buf, _In_ int len, _In_ int flags, _In_reads_bytes_(tolen) const struct sockaddr FAR * to, _In_ int tolen );

接收数据的函数是 recvfrom(),接收数据时需要把发送方的地址存出来。

Copy Highlighter-hljs
int WSAAPI recvfrom( _In_ SOCKET s, _Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf, _In_ int len, _In_ int flags, _Out_writes_bytes_to_opt_(*fromlen, *fromlen) struct sockaddr FAR * from, _Inout_opt_ int FAR * fromlen );

sendto() 和 recvfrom() 的参数相似:

参数 说明
s 用来发送数据的套接字
buf 指向发送数据的缓冲区
len 要发送数据的长度
flags 一般指定为 0
to/from 指向一个包含目标地址和端口号的 sockaddr in 结构
tolen/fromlen sockaddr in 结构的大小

程序编写#

功能设计#

模拟实现 TCP 协议通信过程,要求编程实现服务器端与客户端之间双向数据传递。也就是在一条 TCP 连接中,客户端和服务器相互发送一条数据即可。

initsock.h#

Copy Highlighter-hljs
#include <winsock2.h> #pragma comment(lib, "WS2_32") // 链接到 WS2_32.lib class CInitSock { public: /*CInitSock 的构造器*/ CInitSock(BYTE minorVer = 2, BYTE majorVer = 2) { // 初始化WS2_32.dll WSADATA wsaData; WORD sockVersion = MAKEWORD(minorVer, majorVer); if (::WSAStartup(sockVersion, &wsaData) != 0) { exit(0); } } /*CInitSock 的析构器*/ ~CInitSock() { ::WSACleanup(); } };

服务器#

Copy Highlighter-hljs
#include "initsock.h" #include <iostream> using namespace std; CInitSock initSock; // 初始化Winsock库 int main() { // 创建套接字 SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (s == INVALID_SOCKET) { cout << "Failed socket()" << endl; return 0; } // 填充sockaddr_in结构 sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(4567); sin.sin_addr.S_un.S_addr = INADDR_ANY; // 绑定这个套接字到一个本地地址 if (::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { cout << "Failed bind()" << endl; return 0; } cout << "服务端已启动!\n" << endl; // 进入循环 char buff[1024]; sockaddr_in addr; int nLen = sizeof(addr); while (TRUE) { //接收数据 int nRecv = ::recvfrom(s, buff, 1024, 0, (sockaddr*)&addr, &nLen); if (nRecv > 0) { buff[nRecv] = '\0'; cout << " 接收到" << ::inet_ntoa(addr.sin_addr) << "的数据:" << buff << endl; } // 发送数据 char szText[] = "你好,客户端!"; ::sendto(s, szText, strlen(szText), 0, (sockaddr*)&addr, sizeof(addr)); } ::closesocket(s); }

客户端#

Copy Highlighter-hljs
#include "initsock.h" #include <iostream> using namespace std; CInitSock initSock; // 初始化Winsock库 int main() { // 创建套接字 SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (s == INVALID_SOCKET) { printf("Failed socket() %d \n", ::WSAGetLastError()); return 0; } // 也可以在这里调用bind函数绑定一个本地地址 // 否则系统将会自动安排 // 填写远程地址信息 sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(4567); //填写服务器程序所在机器的IP地址 addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 发送数据 char szText[] = "你好,服务器!"; ::sendto(s, szText, strlen(szText), 0, (sockaddr*)&addr, sizeof(addr)); char buff[1024]; sockaddr_in client_addr; int nLen = sizeof(client_addr); int nRecv = ::recvfrom(s, buff, 1024, 0, (sockaddr*)&client_addr, &nLen); if (nRecv > 0) { buff[nRecv] = '\0'; cout << " 接收到" << ::inet_ntoa(client_addr.sin_addr) << "的数据:" << buff << endl; } ::closesocket(s); return 0; }

运行效果#

广播通信#

广播#

利用广播(broadcast)可以将数据发送给本地子网上的每个机器,同时需要有一些线程在机器上监听到来的数据。广播的缺点是如果多个进程都发送广播数据,网络就会阻塞,网络性能便会受到影响。

广播模式设置#

套接字创建之后,可以使用套接字选项和 ioctl 命令操作它的属性,以改变套接字的默认行为。I/O 控制命令缩写为 ioctl,它也影响套接字的行为,获取和设置套接字选项的函数分别是 getsockopt 和 setsockopt,函数调用出错时返回 SOCKET ERROR。

Copy Highlighter-hljs
int WSAAPI setsockopt( _In_ SOCKET s, _In_ int level, _In_ int optname, _In_reads_bytes_opt_(optlen) const char FAR * optval, _In_ int optlen ); int WSAAPI getsockopt(_In_ SOCKET s, _In_ int level, _In_ int optname, _In_reads_bytes_opt_(optlen) const char FAR * optval, _In_ int optlen);
参数 说明
s 套接字句柄
level 指定此选项被定义在哪个级别,如 SOL SOCKET、IPPROTO_TCP、IPPROTO_IP 等
optname 套接字选项名称,如 SO ACCEPTCONN
optval 指定一个缓冲区,所请求的选项的值将会被返回到这里
optlen 指定上面 optval 所指缓冲区的大小,返回所需的大小

SOBROADCAST 选项设置套接字传输和接收广播消息,如果给定套接字已经被设置为接收或者发送广播数据,查询此套接字选项将返回TRUE,此选项对于那些不是SOCK_STREAM类型的套接字有效。

Copy Highlighter-hljs
::setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof(BOOL));

代码编写#

功能设计#

实现服务器端与客户端之间广播数据传递,服务器端向所有客户端发送“今天是个好日子!”广播消息,客户端收到并在本地显示。

initsock.h#

Copy Highlighter-hljs
#include <winsock2.h> #pragma comment(lib, "WS2_32") // 链接到 WS2_32.lib class CInitSock { public: /*CInitSock 的构造器*/ CInitSock(BYTE minorVer = 2, BYTE majorVer = 2) { // 初始化WS2_32.dll WSADATA wsaData; WORD sockVersion = MAKEWORD(minorVer, majorVer); if (::WSAStartup(sockVersion, &wsaData) != 0) { exit(0); } } /*CInitSock 的析构器*/ ~CInitSock() { ::WSACleanup(); } };

Sender#

对于 UDP 来说存在一个特定的广播地址 255.255.255.255,广播数据都应该发送到这里。发送方程序在创建套接字后使用 setsockopt 函数打开 SO_BROADCAST选项,然后设置广播地址 255.255.255.255,使用 sendto 函数向端口号 54321 不断发送广播数据。

Copy Highlighter-hljs
#include "initsock.h" #include <iostream> #include <windows.h> using namespace std; CInitSock theSock; int main() { SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0); // 有效SO_BROADCAST选项 BOOL bBroadcast = TRUE; ::setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof(BOOL)); // 设置广播地址和广播端口号 SOCKADDR_IN bcast; bcast.sin_family = AF_INET; bcast.sin_port = htons(54321); bcast.sin_addr.s_addr = INADDR_BROADCAST; // 发送广播 cout << " 开始向端口发送广播数据... \n" << endl; char sz[] = "今天是个好日子! \r\n"; while (TRUE) { time_t now = time(0); // 基于当前系统的当前日期/时间 char* dt = ctime(&now); ::sendto(s, sz, strlen(sz), 0, (sockaddr*)&bcast, sizeof(bcast)); cout << dt << "发送广播:" << sz << endl; ::Sleep(5000); } return 0; }

Recver#

服务器需要使用 recvfrom 函数,向端口号 54321 接收广播数据。

Copy Highlighter-hljs
#include "initsock.h" #include <iostream> #include <windows.h> using namespace std; CInitSock theSock; int main() { SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0); // 首先要绑定一个本地地址,指明广播端口号 SOCKADDR_IN sin; sin.sin_family = AF_INET; sin.sin_port = htons(54321); sin.sin_addr.S_un.S_addr = INADDR_ANY; if (::bind(s, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR) { cout << " bind() failed!" << endl; return 0; } // 接收广播 cout << " 开始在端口接收广播数据... \n" << endl; SOCKADDR_IN addrRemote; int nLen = sizeof(addrRemote); char sz[256]; while (TRUE) { int nRet = ::recvfrom(s, sz, 256, 0, (sockaddr*)&addrRemote, &nLen); if (nRet > 0) { sz[nRet] = '\0'; time_t now = time(0); // 基于当前系统的当前日期/时间 char* dt = ctime(&now); cout << dt << "接收到广播:" << sz << endl; } } return 0; }

运行效果#


参考资料#

《Windows 网络与通信编程》,陈香凝 王烨阳 陈婷婷 张铮 编著,人民邮电出版社

posted @   乌漆WhiteMoon  阅读(1209)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2020-11-18 sqli-labs 通关指南:Less 50 ~ 53
点击右上角即可分享
微信分享提示
CONTENTS