9.1 UDP协议
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协议的更多相关文章
- TODO:Golang语言TCP/UDP协议重用地址端口
TODO:Golang语言TCP/UDP协议重用地址端口 这是一个简单的包来解决重用地址的问题. go net包(据我所知)不允许设置套接字选项. 这在尝试进行TCP NAT时尤其成问题,其需要在同一 ...
- 闲来无事,写个基于UDP协议的Socket通讯Demo
项目一期已经做完,二期需求还没定稿,所以最近比较闲. 上一篇写的是TCP协议,今天写一下UDP协议.TCP是有连接协议,所以发送和接收消息前客户端和服务端需要建立连接:UDP是无连接协议,所以发送消息 ...
- UDP协议开发
UDP是用户数据报协议(User Datagram Protocol,UDP)的简称,其主要作用是将网络数据流量压缩成数据报形式,提供面向事务的简单信息传送服务.与TCP协议不同,UDP协议直接利用I ...
- 基于UDP协议模拟的一个TCP协议传输系统
TCP协议以可靠性出名,这其中包括三次握手建立连接,流控制和拥塞控制等技术.详细介绍如下: 1. TCP协议将需要发送的数据分割成数据块.数据块大小是通过MSS(maximum segment siz ...
- TCP协议与UDP协议的区别
TCP协议与UDP协议的区别(转) 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UDP协议的区别,我觉得这是没有从本质上弄清楚网络通信! ...
- 采用UDP协议的PIC32MZ ethernet bootloader
了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). 经过千辛万苦,今天终于 ...
- 采用UDP协议实现PIC18F97J60 ethernet bootloader
了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). TCP/IP Stac ...
- 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程
Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...
- Linux内核--网络栈实现分析(九)--传输层之UDP协议(下)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7549340 更多请查看专栏,地 ...
- Linux内核--网络栈实现分析(五)--传输层之UDP协议(上)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明出处http://blog.csdn.net/yming0221/article/details/7532512 更多请看专栏, ...
随机推荐
- codeforces 350 div2 C. Cinema map标记
C. Cinema time limit per test 2 seconds memory limit per test 256 megabytes input standard input out ...
- redis教程(The little redis book中文版)
许可证 <The Little Redis Book>是经由Attribution-NonCommercial 3.0 Unported license许可的,你不需要为此书付钱. 你可以 ...
- LookupError: Couldn't find path to unrar library.
LookupError: Couldn't find path to unrar library. 意思是找不到 unrar library的路径,这里我们就需要去下载这个unrar library, ...
- shell 杀掉指定进程的服务
check_results=`ps -ef|grep bp_driver.launch|awk '{print $2}'|sed -n 1p` echo `kill - $check_results` ...
- Python day14迭代器,三元表达式,列表解析以及生成器表达式
1.迭代器 str=['sds','ccc','dw'] lit_1=str.__iter__()#获取迭代器 print(lit_1.__next__())#打印下一个值 # 用while做for的 ...
- 定义c/c++全局变量/常量几种方法的区别
转自:http://www.cnblogs.com/yaozhongxiao/archive/2010/08/08/1795338.html 1. 编译单元(模块): 在ide开发工具大行其道的今天, ...
- Postman安装与简单介绍
Postman简介 Postman是一个 Chrome 扩展,能提供强大的 Web API HTTP 请求调试功能.Postman能够发送任何类型的http请求,支持GET/PUT/POST/DELE ...
- C#使用xpath查找xml节点信息
Xpath是功能很强大的,但是也是相对比较复杂的一门技术,最好还是到博客园上面去专门找一些专业的帖子来看一看,下面是一些简单的Xpath语法和一个实例,提供给你参考一下. xml示例: <?xm ...
- Confluence 6 为空间赋予公共访问
希望为一个 Confluence 空间赋予公共访问权限,你必须为匿名用户赋予下面的权限: 在全站启用 可以使用(can use)权限,如上面描述的的. 相关的 空间权限.如果你希望你的一个空间可以公共 ...
- Confluence 6 自动添加用户到用户组
默认组成员(Default Group Memberships) 选项在 Confluence 3.5 及后续版本和 JIRA 4.3.3 及后续版本中可用.这字段将会在你选择 'Read Only, ...