TCP 协议是面向连接的基于流的,可靠的传输服务。UDP是无连接的,基于数据报的,不可靠的传输服务,UDP没有粘包,但是会产生丢包。

UDP模型如下:

可以看到,服务器端不用listen,也不用accept。而客户端,也不用connect。

总结UDP的特点如下:

1、无连接

2、基于消息的数据传输服务

3、不可靠

4、一般情况下UDP更加高效

注意点:

1、UDP报文可能会丢失重复

2、UDP报文可能会乱序

3、UDP缺乏流量控制

4、UDP缓冲区写满后,没有流量控制机制,会覆盖缓冲区

5、UDP协议数据报文截断

  如果接收到的数据报,大于缓冲区,报文可以被截断,后面的部分会丢失

6、recvfrom返回0,不代表连接关闭,因为UDP是无连接的

  例如:sendto可以发送数据0包,只包含UDP头部,这时候recvfrem就会返回0

7、ICMP异步错误

  观察现象:

    关闭UDP服务端,如启动UDP客户端,从键盘接收数据后,再发送数据。UDP客户端会阻塞在recvfrom位置(因为没有对端给本机发),sendto是将数据写到 

    套接字缓冲区,UDP协议栈会选择时机发送。

  说明:

    1、UDP发送报文时,只把数据copy到数据缓冲区,在服务器没有起来的情况下可以发送成功。

    2、所谓ICMP异步错误是指:发送报文的时候,没有错误,recvfrom接收报文的时候,会收到ICMP应答。

    3、异步错误,是无法返回未连接的套接字,UDP也可以调用connect。

8、UDP connect

  UDP调用connect,并没有三次握手,只是维护了一个状态信息(和对等方的)

  一旦调用connect,就可以使用send函数

简单的UDP回射服务器程序如下:

服务器:

 #include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> void echo_srv(int sock)
{
char recvbuf[] = {};
struct sockaddr_in peeraddr;
socklen_t peerlen;
int n; while()
{
peerlen = sizeof(peeraddr);
memset(recvbuf, , sizeof(recvbuf)); n = recvfrom(sock, recvbuf, sizeof(recvbuf), , (struct sockaddr*)&peeraddr,
&peerlen); if(n == -)
{
if(errno == EINTR)
continue;
else
{
perror("recvfrom error");
exit();
}
}
else if(n > )
{
int ret = ;
fputs(recvbuf, stdout);
ret = sendto(sock, recvbuf, n, , (struct sockaddr*)&peeraddr, peerlen);
}
} close(sock);
} int main()
{
int sock; sock = socket(AF_INET, SOCK_DGRAM, ); if(sock < )
{
perror("socket error");
exit();
} struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr)); servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
{
perror("bind error");
exit();
} echo_srv(sock);
return ;
}

客户端:

 #include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> void echo_cli(int sock)
{
struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr)); servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); int ret = ;
char sendbuf[] = {};
char recvbuf[] = {}; while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
sendto(sock, sendbuf, strlen(sendbuf), , (struct sockaddr*)&servaddr,
sizeof(servaddr)
);
ret = recvfrom(sock, recvbuf, sizeof(recvbuf), , NULL, NULL); if(ret == -)
{
if(errno == EINTR)
continue;
else
{
perror("recvfrom error");
exit();
}
} fputs(recvbuf, stdout);
memset(sendbuf, , sizeof(sendbuf));
memset(recvbuf, , sizeof(recvbuf)); } close(sock);
} int main()
{
int sock;
sock = socket(AF_INET, SOCK_DGRAM, ); if(sock < )
{
perror("socket error");
exit();
} echo_cli(sock); return ;
}

运行结果如下:

用netstat - na看网络状态如下:

UDP和TCP不一样,不存在11种状态,因此,我们只能看到一个服务器端的套接字,服务器端执行了bind,所以会显示这个套接字。

报文截断:

如果接收到的数据报,大于缓冲区,报文可以被截断,后面的部分会丢失

实验程序如下:

 #include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, )) < )
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
ERR_EXIT("bind"); sendto(sock, "ABCD", , , (struct sockaddr*)&servaddr, sizeof(servaddr)); //数据报方式。。。。不是字节流
//如果接受数据时,指定的缓冲区的大小,较小;
//剩余部分将要截断,扔掉
char recvbuf[];
int n;
int i;
for (i=; i<; i++)
{
n = recvfrom(sock, recvbuf, sizeof(recvbuf), , NULL, NULL);
if (n == -)
{
if (errno == EINTR)
continue;
ERR_EXIT("recvfrom");
}
else if(n > )
printf("n=%d %c\n", n, recvbuf[]);
}
return ;
}

这是一个自己发自己收的UDP程序,第38行我们定义的缓冲区为1字节大小,而33行发送的大小是4字节,recvfrom接收时会一次取出4字节,但是只放一字节到recvbuf中,其他的三字节被丢弃。 我们想看到的现象是recvfrem一个字节一个字节的接收,但是UDP是数据报协议,recvfrom一次接收一个数据报。跟TCP不一样。

下面做一个只启动客户端,不启动服务器的实验,程序如下:

 #include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> void echo_cli(int sock)
{
struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr)); servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); int ret = ;
char sendbuf[] = {};
char recvbuf[] = {}; while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
ret = sendto(sock, sendbuf, strlen(sendbuf), , (struct sockaddr*)&servaddr,
sizeof(servaddr)
);
printf("sendto %d bytes\n", ret);
printf("send success\n"); ret = recvfrom(sock, recvbuf, sizeof(recvbuf), , NULL, NULL); if(ret == -)
{
if(errno == EINTR)
continue;
else
{
perror("recvfrom error");
exit();
}
} fputs(recvbuf, stdout);
memset(sendbuf, , sizeof(sendbuf));
memset(recvbuf, , sizeof(recvbuf)); } close(sock);
} int main()
{
int sock;
sock = socket(AF_INET, SOCK_DGRAM, ); if(sock < )
{
perror("socket error");
exit();
} echo_cli(sock); return ;
}

只启动客户端运行,结果如下:

UDP也可以调用connect,但是并没有三次握手,只是维护了一个状态信息(和对等方的),实验程序如下:

 #include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() void echo_cli(int sock)
{
struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //3 udp 也可以 调用connet
//udp调用connet,并没有三次握手,只是维护了一个状态信息(和对等方的)。。。
connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)); int ret;
char sendbuf[] = {};
char recvbuf[] = {};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
//2如果 connect 已经指定了对方的地址。
//send可以这样写 sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0); //1sendto第一次发送的时候,会绑定地址
sendto(sock, sendbuf, strlen(sendbuf), , (struct sockaddr*)&servaddr, sizeof(servaddr));
/*sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);*/ //一但调用connect,就可以使用send函数
//send(sock, sendbuf, strlen(sendbuf), 0);
ret = recvfrom(sock, recvbuf, sizeof(recvbuf), , NULL, NULL);
if (ret == -)
{
if (errno == EINTR)
continue;
ERR_EXIT("recvfrom");
} fputs(recvbuf, stdout);
memset(sendbuf, , sizeof(sendbuf));
memset(recvbuf, , sizeof(recvbuf));
} close(sock); } int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, )) < )
ERR_EXIT("socket"); echo_cli(sock); return ;
}

一旦调用了connect,就可以使用send函数发送数据了,不在必须使用sendto。使用send发送数据时,目标地址是connect中绑定的地址。只启动客户端,执行结果如下:

上述程序我们只是在28行加上了connect,如果不加这个函数,客户端会阻塞在recvfrem处。调用了connect后,情况就有点不一样了,sendto还是正常发送数据,但是执行到recvfrom处接收到了ICMP报文,UDP接收到这个异常报文后,给recvfrom返回错误,显示连接拒绝,直接退出客户端。

9.1 UDP协议的更多相关文章

  1. TODO:Golang语言TCP/UDP协议重用地址端口

    TODO:Golang语言TCP/UDP协议重用地址端口 这是一个简单的包来解决重用地址的问题. go net包(据我所知)不允许设置套接字选项. 这在尝试进行TCP NAT时尤其成问题,其需要在同一 ...

  2. 闲来无事,写个基于UDP协议的Socket通讯Demo

    项目一期已经做完,二期需求还没定稿,所以最近比较闲. 上一篇写的是TCP协议,今天写一下UDP协议.TCP是有连接协议,所以发送和接收消息前客户端和服务端需要建立连接:UDP是无连接协议,所以发送消息 ...

  3. UDP协议开发

    UDP是用户数据报协议(User Datagram Protocol,UDP)的简称,其主要作用是将网络数据流量压缩成数据报形式,提供面向事务的简单信息传送服务.与TCP协议不同,UDP协议直接利用I ...

  4. 基于UDP协议模拟的一个TCP协议传输系统

    TCP协议以可靠性出名,这其中包括三次握手建立连接,流控制和拥塞控制等技术.详细介绍如下: 1. TCP协议将需要发送的数据分割成数据块.数据块大小是通过MSS(maximum segment siz ...

  5. TCP协议与UDP协议的区别

    TCP协议与UDP协议的区别(转) 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UDP协议的区别,我觉得这是没有从本质上弄清楚网络通信! ...

  6. 采用UDP协议的PIC32MZ ethernet bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). 经过千辛万苦,今天终于 ...

  7. 采用UDP协议实现PIC18F97J60 ethernet bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). TCP/IP Stac ...

  8. 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程

    Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...

  9. Linux内核--网络栈实现分析(九)--传输层之UDP协议(下)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7549340 更多请查看专栏,地 ...

  10. Linux内核--网络栈实现分析(五)--传输层之UDP协议(上)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明出处http://blog.csdn.net/yming0221/article/details/7532512 更多请看专栏, ...

随机推荐

  1. hdu 5666 Segment 俄罗斯乘法或者套大数板子

    Segment Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Problem ...

  2. Cocos2d-x学习笔记(八)精灵对象的创建

    精灵类即是Sprite,它实际上就是一张二维图. 它首先直接继承了Node类,因此,它具有节点的特征,同时,它也直接继承了TextureProtocol类,因此,它也具有纹理的基本特征. 这里,有必要 ...

  3. Cocos2d-x学习笔记(六)Label字体控制

    BMFont使用链接--->>  http://blog.csdn.net/qiurisuixiang/article/details/8984288 这里要注意.fnt文件可通过BMFo ...

  4. servlet生命周期深入理解

    什么是Servlet Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中 ...

  5. MongoDB(课时15 数据排序)

    3.4.2.10 数据排序 在MongoDB里数据排序操作使用“sort()”函数,在进行排序的时候可以有两个顺序:升序(1),降序(-1). 范例:排序 db.students.find().sor ...

  6. MongoDB(课时11 嵌套集合)

    3.4.2.6 嵌套集合运算 MongoDB数据库里每个集合数据可以继续保存其它的集合数据.例如:有些学生信息中需要保存家长信息. 范例: 增加数据 db.students.insert({" ...

  7. Beta 冲刺 第三天

    第三天 2018.6.26 今日完成任务情况. 妥志福.牛瑞鑫: 完成任务:让用户接触系统,指出系统中存在的问题,并统计修改的问题,提出修改的方案. 王胜海.马中林: 完成任务:对文档中存在的问题修改 ...

  8. 《剑指offer》第十九题(正则表达式匹配)

    // 面试题19:正则表达式匹配 // 题目:请实现一个函数用来匹配包含'.'和'*'的正则表达式.模式中的字符'.' // 表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次).在本题 ...

  9. Python 运算符重载

    https://www.cnblogs.com/hotbaby/p/4913363.html

  10. 1月4日编程基础hash

    早上git加星了webapp的教程 > h = Hash.new('Go Fishing')       => {}  // 创建一个空hash,设定"Go Fishing&qu ...