UDP浅析!!!(客户-服务通信源码)

UDP协议的特点:

1,无连接,也就是说:UDP内部并没有维护端到端的状态,这跟TCP是不一样的。UDP不需要三次握手,

2,基于消息的数据传输服务,传输的是数据报,跟TCP基于字节流是不一样的,不会出现所谓的粘包问题,就是这些

      数据报是有边界的,而TCP是没有边界的

3,不可靠,表现在数据报可能会丢失,也可能会重复,也可能会乱序,缺乏流量控制


所以说:一般情况下,UDP是比TCP更加高效的

有些场合更适合使用UDP,使用UDP编写的一些常见的应用程序有:DNS(域名系统),NFS(网络文件系统)和

SNMP(简单网络管理协议)


基本模型:

也是需要服务端和客户端的

服务端创建socket,bind,接下来调用recvfrom(因为不需要建立连接)一直阻塞,直到收到来自客户的数据

对于客户端而言:创建socket,接下来也是直接调用sendto函数给服务器发送数据报,其中必须指定目的地(即服务

器)的地址作为参数,这时recvfrom才会收到数据,并且对数据进行处理,recvfrom将与所接受的数据报一道返回客

户的协议地址,因此服务器可以把响应发送给正确的客户。服务器这时会调用sendto函数,然后返回,客户端调用

recvfrom接受,最后调用close函数


UDP的简单回射客户/服务器


当然,这里面需要用到recvfrom,sendto函数,我们来看看:

 #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

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

       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

//第一个:套接字socket
//第二个:缓冲区
//第三个:接收的长度
//第四个:flags,我们用0来表示
//第五个,第六个:指定对方的地址信息



UDP服务端:

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

#define ERR_EXIT(m)\
	do\
	{\
		perror(m);\
		exit(EXIT_FAILURE);\
	}while(0)


void echo_srv(int sock)
{
	char recvbuf[1024] = {0};
	struct sockaddr_in peeraddr;
	socklen_t peerlen;
	int n;                      //接受到的字节数

	while(1)
	{
		peerlen = sizeof(peeraddr);
		memset(recvbuf, 0, sizeof(recvbuf));
		n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (struct sockaddr*)&peeraddr, &peerlen);
		
		if(n == -1)
		{
			if(errno == EINTR)
				continue;
			ERR_EXIT("recvfrom");	
		}

		else if(n > 0)
		{
			fputs(recvbuf, stdout);
			sendto(sock, recvbuf, n, 0, (struct sockaddr*)&peeraddr, peerlen);		
		}
	}

	close(sock);
}


int main(void)
{
	//UDP通信的第一步创建一个套接字
	int sock;
	if((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
		ERR_EXIT("socket");
	
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	//INADDR_ANY本机的任意地址

	if(bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
		ERR_EXIT("bind");

	echo_srv(sock);

	return 0;
}

UDP客户端:

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

#define ERR_EXIT(m)\
	do\
	{\
		perror(m);\
		exit(EXIT_FAILURE);\
	}while(0)

void echo_cli(int sock)
{

	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
	{
			sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
			recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
			//表示对等方的地址可以不要了

			fputs(recvbuf, stdout);
			memset(sendbuf, 0, sizeof(sendbuf));
			memset(recvbuf, 0, sizeof(recvbuf));
	}
	close(sock);
}

int main(void)
{
	//UDP通信的第一步创建一个套接字
	int sock;
	if((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
		ERR_EXIT("socket");

	echo_cli(sock);
	
	return 0;
}


那么客户端的地址,是在什么时候绑定的呢???

是在第一次sendto的时候,

因为我们前面已经知道了我客户端要跟谁(服务端)连接,我sendto的时候,会将服务端的信息加入到socket,所以我们调用sendto的时候,也就间接的知道啦跟谁进行连接,,,,

socket的结构体中:是知道客户端/服务端的端口号,IP地址


套接口:

两个属性:本地地址(通过getsockname来获取),远程地址(通过getpeername来获取)

(前提是:这个套接字是一个已连接套接字)

当前我们的套接字是一个未连接的套接字,那么我们就可以通过getsockname来获取本地地址

也就是说:对于客户端,我们的本地地址不是通过bind函数来进行绑定的,我们是通过sendto函数来绑定的,第一次

调用的时候就会绑定,接下来的操作调用是不会再去绑定的,因为我们不会去做多余的操作。很简单



如上,就是我们的UDP通信的过程


注意点:

1,UDP报文可能会丢失,重复,乱序,也就是说:如果我们要确保一个比较可靠的传输的话,我们处理丢失,重复

     这一些问题(针对丢失,那么发送端/接收端就需要超时重传机制,启动定时器)(针对重复,我们应用层要维护

     数据报之间的序号,)

2,UDP缺乏流量控制,UDP也有自己的缓冲区,如果缓冲区满的话,再次接收到的数据不会丢失,而是会覆盖缓冲

     区中的数据,并没有流量控制的机制,不像TCP有滑动窗口协议,如果我们也要实现的话,那么我们可以在应用

     层实现一个模拟的滑动窗口,来简单的控制流量

3,UDP协议数据报文端可能也会截断,因为接收到的数据可能大于缓冲区,多余的数据会被直接丢弃

      我们来看一下这个程序:UDP中,发送的数据大于缓冲区的情况(为了简单方便,我们将客户端和服务端写到

      啦一块)

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

#define ERR_EXIT(m)\
	do\
	{\
		perror(m);\
		exit(EXIT_FAILURE);\
	}while(0)


int main(void)
{
	//UDP通信的第一步创建一个套接字
	int sock;
	if((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
		ERR_EXIT("socket");
	
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	//INADDR_ANY本机的任意地址

	if(bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
		ERR_EXIT("bind");
	
	sendto(sock, "abcd", 4, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));

        //服务端自己给自己发送数据
	char recvbuf[1];
	int i;
	int n;
	for(i = 0; i< 4; i++)
	{
		n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
		if(n == -1)
		{
			if(errno == EINTR)
				continue;
			ERR_EXIT("recvfrom");	
		}
		else if(n > 0)
			printf("n = %d  %c\n", n, recvbuf[i]);
	}	
	return 0;
}

运行结果:

可以看到,数据后面的bcd都被截断了,所以说:为了避免截断现象,我们必须缓冲区大于所要发送的数据


4,recvfrom可以返回0,因为返回0并不代表连接关闭,因为udp是无连接的

      返回为0,表示不发送任何一个数据,类似于这样:

      sendto(sock, "abcd", 0, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
      这时候,实际发送的数据长度就是一个tcp的头部,IP的头部(都是20个字节),那么总共是40个字节,当然,这

      里不算数字链路的头部,那么对方收到的时候,就代表返回为0,并不代表连接的关闭。。。。

5,ICMP的异步错误

      这里,我们可以简单说一下:

      如上的三个程序:

     

          我们先启动客户端echocli,并且这个客户端从键盘接收输入数据(aaa),调用sendto将数据发送出去,接着

      阻塞到啦recvfrom函数中:

     

      那么实际上对等方并没有启动,但是我们的sendto并没有捕捉到这个信息,仍然阻塞到recvfrom函数:

      为什么呢????

      因为,此刻产生了一个异步错误。。。。异步错误是不会返回给套接口的。那么,为什么说产生了异步错误,首

      先:sendto是不会出错的,因为UDP是无连接的,sendto不需要维护状态,sendto仅仅是将应用层的数据拷贝到

      套接口的缓冲区中,仅仅只是拷贝,并不代表数据已经发送给对方了,只要缓冲区中有数据,那么套接口就可以

      拷贝,但是,但是,数据是不会到达对等方的,这时就会产生一个ICMP的错误。也就是说:TCP/IP协议栈会有

      一个报文的应答。。。报文应答给我们的客户端。。。。而这个错误,是在接受的时候才会产生的(调用

      recvfrom)

      我们称之为异步的iCMP错误。。。


      这个错误被延迟到啦recvfrom才会被通知,,,但是,但是,recvfrom也不会被通知,因为TCP/IP规定,这个异

      步错误,是不能发送给未连接的套接字的。。。。


      recvfrom会一直阻塞

      我们来将上面的程序改改(echocli.c):

        int ret;
        
        char sendbuf[1024] = {0};
        char recvbuf[1024] = {0};
        while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
        {
                        sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
                        ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
                        //表示对等方的地址可以不要了

                        if(ret == -1)
                        {
                                if(errno == EINTR)
                                        continue;
                                ERR_EXIT("recvfrom");                           
                        }

//其他程序没有改变 
          可以预测到:如果recvfrom能接受到应答,那么就会提示出错,并且退出程序。。。。

          我们再次运行结果:

         

          程序还是没有退出。。。

          recvfrom继续阻塞。。。


6,如何处理上面这个问题呢???

      UDP也可以调用connect函数,,,

     

        int ret;        

        connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr));
        char sendbuf[1024] = {0};
        char recvbuf[1024] = {0};

        运行结果:

       

        可以发现,这个套接字可以返回给已连接套接字,表示连接被拒绝,,,,

        但是,UDP建立的也不是跟TCP一样的连接啊,,,

        UDP在调用connect的时候,实际上并不做三次握手,,,并没有跟对方在传递数据,只是维护了套接字的一种

        状态,,,通过这个数据能够发送数据给对等方。。

         通过这个数据能够发送数据给对等方,只能发送给这个对等方(相当于:远程地址得到了一个绑定,远程地址只

         能是我们所能连接的地址,这个时候,这个套接字就不能发送数据给其他地址了)


         通过运行(先打开服务端),我们可以知道客户端还是可以正常收发数据的。。。


         上面的意思,也就是sendto的时候,已经指定了对方的地址,所以我们可以不用指定对方的地址了,,,

        

          sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);   //不指定地址
         也能成功,也能正常的收发,,,


         同时,这里的sendto也是可以该为send和write的,,,

        

         send(sock, sendbuf, strlen(sendbuf), 0);
         同时,recvfrom也可以通过recv来替换,,,,


7,UDP的外出接口

      假如客户端有多个IP的时候(192.168.1.23, 192.168.2.31)服务端有(192.168.1.33,192.168.2.33)

      那么当我们的sendto连接192.168.1.33,就会自动的跟192.268.1.23进行通信

      sendto(sock, 192.168.1.33,,,,,,,,),sock自动的连接到啦192.268.1.23


      当然,也可以调用connect连接地址,但是并没有绑定端口,端口的绑定是在sendto

      如:connect(sock, 192.168.1.33);那么数据是从192.168.1.23这张网卡出去的,但是地址可就限定了


     

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Qt是一个跨平台的C++开发框架,提供了丰富的库和工具,可以用于开发各种类型的应用程序,包括UDP通信UDP通信是一种不可靠的传输协议,主要用于在网络上快速发送数据包,但不能确保数据包的可靠传输和顺序。Qt提供了QUdpSocket类,可以用于实现UDP通信。 QUdpSocket是一个用于UDP通信的套接字类。通过它,我们可以创建和管理UDP套接字,发送和接收UDP数据包。QUdpSocket提供了丰富的方法,如bind()用于绑定IP地址和端口号、writeDatagram()用于发送数据包、readyRead信号与hasPendingDatagrams()方法用于接收数据包等。 如果您想使用Qt实现UDP通信的猫源码,可以按照以下步骤进行: 1. 创建一个Qt项目,并添加QUdpSocket类的头文件。 2. 在主函数中创建QUdpSocket对象。 3. 使用bind()方法绑定本地的IP地址和端口号。 4. 使用writeDatagram()方法发送数据包。 5. 使用readyRead信号与hasPendingDatagrams()方法接收数据包,并处理接收到的数据。 在编写源码时,您可以根据需求添加其他功能,如数据包拆分和组装、数据包的校验等。此外,还可以根据实际情况处理数据丢失和顺序错乱的问题。 总之,使用Qt的QUdpSocket类可以很方便地实现UDP通信。通过自定义的猫源码,您可以实现基于UDP的猫通信程序,用于在网络上传输猫的信息。希望对您有所帮助! ### 回答2: Qt是一种跨平台的C++框架,可以用于开发图形化界面和网络通信等应用。而UDP(User Datagram Protocol)是一种无连接的传输协议,无需建立连接就可以发送数据,适用于实时性要求较高的通信场景。 Qt提供了丰富的网络编程支持,其中包括UDP通信。Qt的UDP通信源码是一个示例项目,用于展示如何使用Qt框架进行UDP通信的实现。 在这个源码中,首先需要创建一个QUdpSocket对象,用于发送和接收UDP数据包。通过调用QUdpSocket的bind()函数,可以将socket绑定到指定的IP地址和端口上。 接下来,可以通过调用QUdpSocket的writeDatagram()函数来发送UDP数据包。该函数需要指定目标IP地址和端口,以及待发送的数据。如果发送成功,writeDatagram()函数将返回实际发送的数据字节数。 同时,可以通过重写QUdpSocket的readyRead()信号的槽函数,来实现数据的接收与处理。当有数据到达时,系统将自动发出readyRead()信号,可以通过连接信号与槽函数的方式,将数据接收和处理的逻辑代码写入槽函数中。 该示例项目通常会实现一个简单的UDP聊天室功能,即用户可以发送消息到指定的IP地址和端口,并接收其他用户发送的消息。通过解析接收到的数据包,可以将发送方的IP地址和消息内容进行显示。 总之,Qt UDP通信源码是一个展示如何使用Qt框架进行UDP通信的示例项目,可以通过该源码了解UDP通信的基本原理和代码实现。如果需要在自己的项目中使用UDP进行通信,可以参考该源码进行调整和拓展。 ### 回答3: Qt是一种跨平台的C++框架,可用于开发各种类型的应用程序。UDP(User Datagram Protocol)是一种无连接的网络传输协议,它可以在网络上发送和接收数据包,但不保证数据包的可靠传输。通信源码是指使用Qt开发的一种UDP通信应用程序的源代码。 UDP通信源码可以用于实现基于UDP协议的网络通信功能。在该源码中,通信猫可以作为服务器客户端的角色来进行通信。作为服务器通信猫可以监听指定的端口,接收来自客户端发送的数据包,并对数据进行处理。作为客户端,通信猫可以向指定的服务器IP地址和端口发送数据包。 通信源码通常包含了UDP通信相关的功能和逻辑,例如创建UDP套接字、绑定端口、发送和接收数据包等。该源码还可能包含与界面相关的代码,以便用户能够通过界面与通信猫进行交互。 使用通信源码,开发者可以快速实现UDP通信功能,并根据自己的需求进行定制和扩展。例如,可以基于该源码进行进一步的开发,添加数据校验、加密、压缩等功能,以提高通信的可靠性和安全性。 总之,Qt UDP通信源码提供了一个基于UDP协议的通信解决方案的基础代码,开发者可以在此基础上进行二次开发,以满足自己的特定需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值