socket之UDP通讯

该文章简单的讲解linux中socket UDP通讯

UDP(user datagram protocol)的中文叫用户数据报协议,属于传输层。UDP是面向非连接的协议,它不与对方建立连接,而是直接把我要发的数据报发给对方。所以UDP适用于一次传输数据量很少、对可靠性要求不高的或对实时性要求高的应用场景。正因为UDP无需建立类如三次握手的连接,而使得通信效率很高。

1.UDP通讯模型
服务端:socket(创建套接字)–>bind(绑定套接字)–>recvfrom/sendto(对客服端进行读写操作)–>close(关闭套接字)

客户端:socket(创建套接字)—>connect(连接服务器)(可选项)–>–>recvfrom/sendto(对服务器进行读写)–>close(关闭套接字)

2、socket函数原型:

int socket(int domain,int type, int protocol)

domain:指明了协议族/域,通常AF_INET、AF_INET6、AF_LOCAL等;
type:是套接口类型,主要SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW(原始socket);
protocol:一般取为0。成功时,返回一个小的非负整数值,与文件描述符类似
返回值:非负描述符 – 成功,-1 - 出错

3、bind函数原型:

int bind(int sockfd, const struct sockaddr * my_addr, socklen_t addrlen);

函数功能:把一个本地协议地址赋予一个套接字。对于网际协议,协议地址是32位的IPv4地址或是128位的IPv6地址与16位的TCP或UDP端口号的组合。 对于IPv4来说,通配地址通常由INADDR_ANY来指定,其值一般为0。它告知内核去选择IP地址。
sockfd: 套接字编号
my_addr: 是一个指向sockaddr结构体类型的指针
addrlen: 表示my_addr结构的长度,可以用sizeof操作符获得。
返回值: 返回值:成功返回0,失败返回-1

4、connect函数原型:

int connect(int soctfd, const struct sockaddr * addr, int addrlen);

函数功能: 用于建立与指定socket的连接。
soctfd:标识一个未连接socket
addr:指向要连接套接字的sockaddr结构体的指针
addrlen:sockaddr结构体的字节长度
返回值: 成功0;失败-1
a. 硬错:端口号错误,服务器进程未开启,收到RST,立刻返回ECONNREFUSED;
b. 软错:IP不可达,协议ICMP,比如no route to host,通常是发送arp请求无响应

5.sendto函数原型

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

sockfd:正在监听端口的套接口文件描述符,通过socket获得
buf:发送缓冲区,往往是使用者定义的数组,该数组装有要发送的数据
len:发送缓冲区的大小,单位是字节
flags:填0即可
dest_addr:指向接收数据的主机地址信息的结构体,也就是该参数指定数据要发送到哪个主机哪个进程
addrlen:表示第五个参数所指向内容的长度
返回值:成功:返回发送成功的数据长度; 失败: -1

6.recvfrom函数原型

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);

sockfd:正在监听端口的套接口文件描述符,通过socket获得
buf:接收缓冲区,往往是使用者定义的数组,该数组装有接收到的数据
len:接收缓冲区的大小,单位是字节
flags:填0即可
src_addr:指向发送数据的主机地址信息的结构体,也就是我们可以从该参数获取到数据是谁发出的
addrlen:表示第五个参数src_addr所指向内存的长度
返回值:成功:返回接收成功的数据长度; 失败: -1

7.相关头文件

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

8.UDP通讯之server端代码

#define BUF_SIZE 1024

void ServerDataHandle_recvform_sendto(int fd)
{
	int byte = 0, cnt = 0;
	char buf[BUF_SIZE] = {0};
	socklen_t len = sizeof(struct sockaddr_in);
	struct sockaddr_in clientaddr;
	
	if(fd <= 0) 
	{
		perror("socket fd value err");
		return ;
	}
	
	while(1)
	{
		memset(buf, 0, BUF_SIZE );
		byte = recvfrom(fd, buf, BUF_SIZE, 0, (struct sockaddr *)&clientaddr, &len);	//读取client数据,有数据更新才读取,否则阻塞
		if(byte == 0)							//客户端关闭时,读取数据个数为0
		{
			printf("sockfd:%d read over\n", fd);
			break;
		}
		if(byte < 0)
		{
			perror("read failed");	
			break;
		}
		printf("client IP:%s, port:%d, datalen:%d, info:%s\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port, byte, buf );
		
		memset(buf, 0, BUF_SIZE );
		sprintf(buf, "server send cnt:%d\n", ++cnt);
		sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
		//sleep(5);
	}
	close(fd);
}

int main(int argc, void *argv[] )
{
	int listenfd, opt = 1;
	struct sockaddr_in serveraddr;
	
	listenfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(listenfd < 0)
	{
		perror("Create socket fail.");
		return -1;
	}	
	
    memset( (void*)&serveraddr,0,sizeof(struct sockaddr_in) );
    serveraddr.sin_family 		= AF_INET;
	serveraddr.sin_port			= htons(12345);
	//serveraddr.sin_addr.s_addr 	= inet_addr("127.0.0.1");
	serveraddr.sin_addr.s_addr 	= htonl(INADDR_ANY);	//自动生成自身IP做服务器IP给客户端连接
	
	if(bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))<0)	//绑定
	{
		perror("bind error.");
		return -1;
	}
	
	ServerDataHandle_recvform_sendto(listenfd);
	
	return 0;
}

9.UDP通讯之client端代码

#define BUF_SIZE 1024
#define SERVER_IP "127.0.0.1"	//IP号之间不能有空格

void ClientDataHandle_recvfrom_sendto(int socketfd, struct sockaddr_in *addr)
{
	int byte = 0, cnt = 0;
	unsigned char data[BUF_SIZE];
	socklen_t len = sizeof(struct sockaddr_in);
	struct sockaddr_in serveraddr;
	
	while(1)
	{
		memset(data, 0, BUF_SIZE );
		sprintf(data, "client send data cnt:%d\n", ++cnt);
		sendto(socketfd, data, strlen(data), 0, (struct sockaddr *)addr, sizeof(*addr));
		
		memset(data, 0, BUF_SIZE );
		//读取server数据,有数据更新才读取,否则阻塞
		byte = recvfrom(socketfd, data, BUF_SIZE, 0, (struct sockaddr *)&serveraddr, &len);
		if(byte == 0)
		{
			perror("read over");
			break;
		}
		if(byte < 0)
		{
			perror("read failed");
			break;
		}
		printf("server-->client datelen:%d info:%s\r\n",byte, data);	
		sleep(5);
	}	
	return;
}

int main(int argc, void *argv[] )
{
	int socketfd = -1;
	struct sockaddr_in servaddr;
	
	//socket函数类似于open函数,打开并创建一个socket,AF_INET使用IPV4协议族,SOCK_DGRAM为UDP套接口
	if( (socketfd = socket(AF_INET, SOCK_DGRAM, 0) ) ==  -1)	
	{
		perror("socket create failed!");
		return -1;
	}
	
	memset(&servaddr, 0, sizeof(servaddr) );
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
	servaddr.sin_port = htons(12345);

	ClientDataHandle_recvfrom_sendto(socketfd, &servaddr);
	return 0;	
}

上面的代码基于socket UDP通讯方式实现了server端与client的数据交互

10.client端使用connect函数通讯
在通讯过程中,如果确定对方地址又不会再更改地址信息,可以使用connect函数进行连接,连接后调用recvfrom/sendto函数时无需再填入IP信息内容。也可以使用read/write代替recvfrom/sendto函数进行通讯,下面举例UDP通讯中client端使用connect函数例子。

#define SERVER_IP "127.0.0.1"	//IP号之间不能有空格

void ClientDataHandle_connect_read_write(int socketfd)
{
	int byte, cnt = 0;
	unsigned char data[1024];
	
	while(1)
	{
		memset(data, 0, 1024);
		sprintf(data, "client send data cnt:%d", ++cnt);
		write(socketfd, data, strlen(data));
		printf("client write to server seccessful\n");
		
		memset(data, 0, 1024);
		byte = read(socketfd, data, 1024);	//读取server数据,有数据更新才读取,否则阻塞
		if(byte == 0)
		{
			perror("read over");
			break;
		}
		if(byte < 0)
		{
			perror("read failed");
			break;
		}
		printf("server-->client datelen:%d info:%s\r\n",byte, data);	
		sleep(5);		
	}	
	return;
}

void ClientDataHandle_connect_recvfrom_sendto(int socketfd)
{
	int cnt = 0, byte = 0;
	unsigned char data[1024];
	
	while(1)
	{
		memset(data, 0, 1024);
		sprintf(data, "client send data cnt:%d\n", ++cnt);
		sendto(socketfd, data, strlen(data), 0, NULL, 0);
		memset(data, 0, 1024);
		//读取server数据,有数据更新才读取,否则阻塞
		byte = recvfrom(socketfd, data, 1024, 0, NULL, NULL);
		if(byte == 0)
		{
			perror("read over");
			break;
		}
		if(byte < 0)
		{
			perror("read failed");
			break;
		}
		printf("server-->client datelen:%d info:%s\r\n",byte, data);	
		sleep(5);	
	}	
	
}

int main(int argc, void *argv[] )
{
	int socketfd = -1;
	struct sockaddr_in servaddr;
	
	if( (socketfd = socket(AF_INET, SOCK_DGRAM, 0) ) ==  -1)	//socket函数类似于open函数,打开并创建一个socket,AF_INET使用IPV4协议族,SOCK_STREAM为TCP套接口
	{
		perror("socket create failed!");
		return -1;
	}
	
	memset(&servaddr, 0, sizeof(servaddr) );
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
	servaddr.sin_port = htons(12345);

	if(connect(socketfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0 )
	{
		perror("socket connect failed!");
		return -1;
	}

	//ClientDataHandle_connect_read_write(socketfd);//效果与下方函数一样
	ClientDataHandle_connect_recvfrom_sendto(socketfd);
	
	return 0;	
}

11.UDP流量通知问题

总所周知,TCP有滑动窗口进行流量控制和拥塞控制,反观UDP因为其特点无法做到。UDP接收数据时直接将数据放进缓冲区内,如果用户没有及时将缓冲区的内容复制出来放好的话,后面的到来的数据会接着往缓冲区放,当缓冲区满时,后来的到的数据就会覆盖先来的数据而造成数据丢失(因为内核使用的UDP缓冲区是环形缓冲区)。因此,一旦发送方在某个时间点爆发性发送消息,接收方将因为来不及接收而发生信息丢失。

解决方法一般采用增大UDP缓冲区,使得接收方的接收能力大于发送方的发送能力。

int n = 220 * 1024; //220kB
setsocketopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

这样我们就把接收方的接收队列扩大了,从而尽量避免丢失数据的发生。

### 回答1: MFC中的Socket UDP通讯是一种基于用户数据报协议(UDP)的通信方式。UDP是一种无连接的协议,它将数据以数据包(Packet)的形式发送给对方,不需要建立持久的连接。 在MFC中实现UDP通讯可以通过CSocket类来实现。CSocket类是一个功能强大的封装了套接字API的类,可以方便地进行网络通信。 首先,在MFC应用程序中创建一个CSocket派生类的对象,并调用它的Create函数创建Socket对象。 然后,通过Socket对象的Bind函数将Socket和本地地址进行绑定。这里的本地地址可以是指定的IP地址和端口号。 接着,可以通过Socket对象的SendTo函数向指定的目标IP地址和端口号发送数据,也可以通过RecvFrom函数接收来自其他Socket的数据。 Socket对象的Close函数可以关闭Socket连接。 需要注意的是,UDP是一种不可靠的协议,因此在发送和接收数据时需要自己处理数据包的丢失、顺序错乱等问题。 通过以上步骤,就可以在MFC中实现Socket UDP通讯了。这种通讯方式适用于一些需要快速传输数据,但对数据可靠性和顺序没有严格要求的场景,比如音视频实时传输、广播等。 ### 回答2: MFC(Microsoft Foundation Class)是微软公司提供的一套C++编程库,用于支持Windows操作系统上的图形用户界面(GUI)应用程序开发。在MFC中使用Socket进行UDP通信,可以通过以下步骤进行: 1. 创建一个UDP Socket对象。在MFC中,可以使用CSocket类来创建Socket对象。使用CSocket的Create函数来创建一个UDP Socket,指定协议类型为AF_INET,类别为SOCK_DGRAM(即UDP)。 2. 绑定Socket到本地IP地址和端口号。使用CSocket的Bind函数将Socket绑定到一个本地的IP地址和端口号上,以便接收和发送数据。可以使用INADDR_ANY来表示使用任意可用的本地IP地址。 3. 接收数据。使用CSocket的ReceiveFrom函数来接收UDP数据。该函数会阻塞,直到接收到数据为止。可以指定一个缓冲区来存储接收到的数据,并指定发送方的IP地址和端口号。 4. 发送数据。使用CSocket的SendTo函数来发送UDP数据。可以指定一个缓冲区存储要发送的数据,并指定目标IP地址和端口号。 5. 关闭Socket。在UDP通信完成后,需要使用CSocket的Close函数关闭Socket对象,释放资源。 在MFC中进行UDP通信时,可以结合消息机制来处理接收到的数据。可以在一个单独的线程中循环接收数据,通过PostMessage函数将接收到的数据发送给主界面进行处理和显示。 总结起来,通过在MFC中使用CSocket类来创建UDP Socket对象,可以实现UDP通信的功能。即将Socket绑定到本地IP地址和端口号,然后通过ReceiveFrom接收数据,通过SendTo发送数据,最后使用Close函数关闭Socket。同时,可以结合消息机制来实现数据的处理和显示。 ### 回答3: MFC(Microsoft Foundation Classes)是一套用于开发 Windows 程序的 C++ 类库,它提供了许多GUI(图形用户界面)和非GUI的类,方便开发人员快速构建Windows应用程序。 在MFC中使用Socket进行UDP通信,可以通过以下步骤: 1. 创建UDP Socket:通过调用MFC的CSocket类的Create函数创建一个UDP类型的Socket对象。 2. 绑定Socket:通过调用Socket对象的Bind函数绑定Socket到本地的IP地址和端口号上。 3. 接收数据:使用Socket对象的ReceiveFrom函数接收来自远程主机的UDP数据包。 4. 发送数据:使用Socket对象的SendTo函数向远程主机发送UDP数据包。 5. 关闭Socket:使用Socket对象的Close函数关闭UDP Socket。 下面是一个简单的示例代码: ```cpp // 创建UDP Socket CSocket udpSocket; udpSocket.Create(); // 绑定Socket到本地IP地址和端口号 udpSocket.Bind(1234); // 接收数据 char buffer[1024]; int recvLength; sockaddr_in senderAddr; int senderAddrLength = sizeof(senderAddr); recvLength = udpSocket.ReceiveFrom(buffer, sizeof(buffer), (sockaddr*)&senderAddr, &senderAddrLength); buffer[recvLength] = '\0'; CString receivedData(buffer); // 发送数据 CString sendData = "Hello, UDP!"; udpSocket.SendTo(sendData.GetString(), sendData.GetLength(), senderAddr, senderAddrLength); // 关闭Socket udpSocket.Close(); ``` 在实际应用中,可以根据需要进行数据的处理和解析,以及异常处理和错误检测。同时需要注意IP地址和端口号的设置,以及网络环境的配置和保障。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值