UDP套接字编程详解

news/2024/5/18 14:41:17 标签: udp, 网络, 网络协议, c++

    UDP 是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议。

    UDP协议与TCP协议一样用于处理数据包。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但即使在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。

    当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择。在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP也是一个好的选择。 [3] 

UDP的一般实现流程图:

UDP服务端 

初始化套接字库

//初始化套接字库
WORD wVersion;
WSADATA wsaData;
int err;

wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
//判断执行结果
if (err != 0) {
	return err;
}
//判断初始化结果是否与选择的版本库一致
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
	//清理套接字库
	WSACleanup();
	return -1;
}

    在开之前需要先初始化套接字库,告诉操作系统我们选择的套接字库的版本号。这里我们选择的是 1.1版本的套接字库 。

    初始化完成后需要判断程序的执行结果,一个是函数本身的运行情况,判断返回结果是否为0;另一个是我们初始化的套接字库的版本号是否和我们指定的相一致。

创建Socket 

//创建Socket
//指定协议族为IPV4,socket通信类型为支持UDP连接
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);

//准本一个处理网络通信的地址
SOCKADDR_IN addrSrv;
//指定要绑定的IP地址为本机上的任意IP地址
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
//协议族要与创建给的Socket一致
addrSrv.sin_family = AF_INET;
//指明绑定的端口号
addrSrv.sin_port = htons(6001);

创建一个服务器的Socket, 并指明协议族为IPV4,通信类型为支持UDP协议。

还需要准本一个用于处理网络通信的结构体,并指绑定的IP地址、端口号和协议族(协议族要与创建Socket时的协议族保持一致)。

绑定到本机

//绑定套接字
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

bind函数会将端口和IP地址绑定到我们的socket描述符上,返回0时表示绑定成功 。

等待发送与接收数据

//保存发送数据的地址
SOCKADDR_IN addrCli;

int len = sizeof(SOCKADDR_IN);
char recvBuf[100];
char sendBuf[100];
while (true) {
	//接收数据
	recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
	//将接收数据打印下来
	std::cout << recvBuf << std::endl;
	sprintf_s(sendBuf, 100, "ACK%s", recvBuf);
    //发送数据
	sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrCli, len);
}

UDP没有TCP的监听和请求步骤,而是直接去接收客户端发来的信息,并将客户端的socket信息保存下来,在发送数据的时候使用。 

recvfrom函数用于从(已连接)套接口上接收数据,并捕获数据发送源的地址。

recvfrom函数原型为

int recvfrom(
	SOCKET   s,
	char* buf,
	int      len,
	int      flags,
	sockaddr * from,
	int* fromlen
);
  •  s:标识绑定套接字的描述符
  • buf:传入数据的缓冲区
  • len:指向缓冲区的长度
  • flags:可一般默认为0
  • from:保存数据的发送地址
  • fromlen:发送数据的源地址长度指针

sendto是一个向指定目的地发送数据的函数,将recvfrom捕获的客户端地址传入sendto里,就可以向该客户端发送数据 

sendto函数原型

int sendto(
    int s, 
    const void * msg, 
    int len, 
    unsigned int flags,
    const struct sockaddr * to, 
    int tolen
);

s:标识绑定的套接字描述符

msg:发送数据的缓冲区

len:指向发送缓冲区的长度

flags:可一般默认为0

to:向指定地址发送数据的地址长度

完整代码

#include<iostream>
#include<WinSock2.h>

int main() {


	//初始化套接字库
	WORD wVersion;
	WSADATA wsaData;
	int err;

	wVersion = MAKEWORD(1, 1);
	err = WSAStartup(wVersion, &wsaData);
	//判断执行结果
	if (err != 0) {
		return err;
	}
	//判断初始化结果是否与选择的版本库一致
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
		//清理套接字库
		WSACleanup();
		return -1;
	}

	//创建Socket
	//指定协议族为IPV4,socket通信类型为支持UDP连接
	SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
	//准本一个处理网络通信的地址
	SOCKADDR_IN addrSrv;
	//指定要绑定的IP地址为本机上的任意IP地址
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	//协议族要与创建给的Socket一致
	addrSrv.sin_family = AF_INET;
	//指明绑定的端口号
	addrSrv.sin_port = htons(6001);
	//绑定套接字
	bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	//保存发送数据的地址
	SOCKADDR_IN addrCli;

	int len = sizeof(SOCKADDR_IN);

	char recvBuf[100];
	char sendBuf[100];
	while (true) {
		//接收数据
		recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
		//将接收数据打印下来
		std::cout << recvBuf << std::endl;
		sprintf_s(sendBuf, 100, "ACK%s", recvBuf);
        //发送数据
		sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrCli, len);
	}


	//关闭套接字
	closesocket(sockSrv);
	//清理套接字库
	WSACleanup();

	system("pause");
	return 0;
}

UDP客户端 

初始化套接字库

//初始化套接字库
WORD wVersion;
WSADATA wsaData;
int err;

wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
//判断执行结果
if (err != 0) {
	return err;
}
//判断初始化结果是否与选择的版本库一致
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
	//清理套接字库
	WSACleanup();
	return -1;
}

客户端的实现也需要先初始化套接字库,注意库的版本和服务器的版本相一致。

创建套接字 

//创建Socket
//指定协议族为IPV4,socket通信类型为支持UDP连接
SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);

//准本一个处理网络通信的地址
SOCKADDR_IN addrSrv;
//指定要连接的IP地址
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//协议族要与创建给的Socket一致
addrSrv.sin_family = AF_INET;
//指明绑定的端口号
addrSrv.sin_port = htons(6001);

创建一个套接字,套接字的协议族与通信类型要和服务器的保持一致。之后准备一个处理网络通信的地址,也就是需要连接服务器的地址,并指明连接的IP地址和端口号。 

发送与接收数据

//保存发送数据的地址
SOCKADDR_IN addrCli;

int len = sizeof(SOCKADDR_IN);
char recvBuf[100];
char sendBuf[100] = "UDP Cli come to connect";
//发送数据
sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len);
//接收数据,这里也可也不保存地址
recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
//将发送数据打印下来
std::cout << recvBuf << std::endl;

其实这里我们可以不写保存地址的SOCKADDR_IN结构体,因为我们已经知道要发送到的地址了

完整代码 

#include<iostream>
#include<WinSock2.h>

#pragma comment(lib,"ws2_32.lib")

int main()
{
	//初始化套接字库
	WORD wVersion;
	WSADATA wsaData;
	int err;

	wVersion = MAKEWORD(1, 1);
	err = WSAStartup(wVersion, &wsaData);
	//判断执行结果
	if (err != 0) {
		return err;
	}
	//判断初始化结果是否与选择的版本库一致
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
		//清理套接字库
		WSACleanup();
		return -1;
	}

	//创建Socket
	//指定协议族为IPV4,socket通信类型为支持UDP连接
	SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
	//准本一个处理网络通信的地址
	SOCKADDR_IN addrSrv;
	//指定要连接的IP地址
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	//协议族要与创建给的Socket一致
	addrSrv.sin_family = AF_INET;
	//指明绑定的端口号
	addrSrv.sin_port = htons(6001);

	//保存发送数据的地址
	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR_IN);
	char recvBuf[100];
	char sendBuf[100] = "UDP Cli come to connect";
	//发送数据
	sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len);
	//接收数据
	recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
	//将发送数据打印下来
	std::cout << recvBuf << std::endl;

	system("pause");
	return 0;
}

http://www.niftyadmin.cn/n/1662862.html

相关文章

Python中不可变的字典(映射类型)。

不可变映射类型 在有些场景中&#xff0c;比如不能用用户错误的修改某个映射&#xff0c;所以需要一种不可变的字典。 在Python3.3开始&#xff0c;types模块中新增了一个封装类MappingProxyType。 给这个类一个映射&#xff0c;他会返回一个只读的映射视图。虽然这个视图是…

多线程+socket 实现群聊服务器

通过多线程Socket&#xff0c;实现群聊服务器。 服务端&#xff1a; 每当有一个连接时&#xff0c;服务端起一个线程去维护&#xff1b;.将收到的信息转发给所有的客户端&#xff1b;当某个客户端断开连接时需要处理断开连接 客户端&#xff1a; 接收与发送信息断开连接自定…

钩针纺织资料

兔子爸爸妈妈主体勾法视频&#xff1a; http://v.youku.com/v_show/id_XMzM0MDE5NDk4MA.html?spma2hzp.8244740.0.0 缝合视频 http://v.youku.com/v_show/id_XMzM3MzQxODkwOA.html?spma2hzp.8253869.0.0 爸爸背带裤视频&#xff1a; http://v.youku.com/v_show/id_XMzM0MzQxN…

量子通信技术、量子加密技术

量子是什么&#xff1f; 所谓量子&#xff0c;是构成物质的最基本单元&#xff0c;是能量&#xff0c;动量等物理量最小单位&#xff0c;不可分割。像电子、光子等构成物质的基本粒子&#xff0c;统称为量子。 除了不可分割性&#xff0c;量子还具有不可克隆&#xff08;复制&a…

Python的集合set简介

11. 集合 set和它的不可变类型frozenset直到python2.6版本中才成为内置类型&#xff0c;是比较年轻的。 集合的本质是许多唯一对象的聚集。 集合的所有元素必须是可散列的。set类型本身是不可散列的&#xff0c;因为可以增删元素。但是不可变集合frozenset是可散列的。所以可…

[Poi2012]A Horrible Poem BZOJ2795

分析&#xff1a; 这是今天下午的考试题&#xff0c;推了2个小时&#xff0c;考试中A掉了 首先&#xff0c;循环串通过字符串hash可以O(1)判断&#xff1a;get_hash(l,r-len)get_hash(llen,r);显然可证。 我们其次可以发现&#xff0c;循环串的长度是所求串的长度的约数 之后我…

线程同步之信号量

什么是信号量 信号量&#xff08;semaphore&#xff09;是操作系统用来解决并发中的互斥和同步问题的一种方法。与互斥量不同的地方是&#xff0c;它允许多个线程在同一时刻访问同一资源&#xff0c;但是需要限制在同一时刻访问此资源的最大线程数目。 信号量的工作原理以一个…

深入理解内核对象与函数句柄

1. 内核对象 Windows中每个内核对象都只是一个内存块&#xff0c;它由操作系统内核分配&#xff0c;并只能由操作系统内核进行访问&#xff0c;应用程序不能在内存中定位这些数据结构并直接更改其内容。这个内存块是一个数据结构&#xff0c;其成员维护着与对象相关的信息。少…