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. shell 求模

    $ expr % $ let i=% $ echo $i $ echo % | bc $ ((i=%)) $ echo $i

  2. password_hash加密

    每次执行 password_hash('123456', PASSWORD_BCRYPT) 语句后,得到哈希值都不一样! 给密码做哈希之前,会先加入一个随机子串,因为加入的随机子串每次是不一样的,所以 ...

  3. 30分钟带你了解Docker

    最近一直在忙项目,不知不觉2个多月没有更新博客了.正好自学了几天docker就干脆总结一下,也顺带增加一篇<30分钟入门系列>.网上能够查到的对于docker的定义我就不再重复了,说说我自 ...

  4. oracle创建/删除表空间、创建/删除用户并赋予权限

    创建表空间 分开执行如下sql语句 --创建临时表空间 CREATE SMALLFILE TEMPORARY TABLESPACE "TEMP11" TEMPFILE 'E:\ap ...

  5. python模块——socket (实现简单的C/S架构端通信操作CMD)

    # 服务端代码#!/usr/bin/env python # -*- coding:utf-8 -*- __author__ = "loki" import socket impo ...

  6. 关于"架构"

    杨光辉说,在构架系统的早期可能不会更多地考虑架构,主要是在做技术选型,首先是编程语言的选择.对于编程语言选择,当前主流编程语言有很多,有面向对象语言.传统式语言等.做这个选择主要根据人员知识储备,包括 ...

  7. Python的第二次作业

    羊车门问题 1.我认为 会 增加选中汽车的机会,原因如下: 不换的情况:对于参赛者而言无论选哪一扇门都有1/3的几率能获得车子. 换的情况  :对于参赛者而言,有两种情况「1.参赛者第一次就选择到了正 ...

  8. Java IO流中的flush()

    通过BufferedOutputStream或BufferedWriter 链接到底层流上来实现.因此,在写 完数据时,flush就显得尤为重要. 例如: 上图中WEB服务器通过输出流向客户端响应了一 ...

  9. Bellman-Ford模板

    转载链接:http://blog.csdn.net/niushuai666/article/details/6791765 Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情 ...

  10. 第一章连通性问题-----algorithm in C 读书笔记

    首先不得不吐槽一下翻译的质量,霍红卫....你给我站出来,不打死你,只想问你一下,你当年四级过了吗? 问题描述 输入两个整数,代表两个节点,如果这两个整数没有建立连接(这包括直接连接和通过其他节点连接 ...