基于UDP协议的网络程序
一、下图是典型的UDP客户端/服务器通讯过程
下面依照通信流程,我们来实现一个UDP回射客户/服务器
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
当套接字处于“已连接”的状态时,才可以使用send,当flags = 0 时 send 与 write 一致。
且 send(sockfd, buf, len, flags); 即 sendto(sockfd, buf, len, flags, NULL, 0);
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);
recv 与 recvfrom 的关系与 send 与 sendto 的关系一致。
echoser_udp.c:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
/*************************************************************************
> File Name: echoser_udp.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sun 03 Mar 2013 06:13:55 PM CST ************************************************************************/ #include<stdio.h> #define ERR_EXIT(m) \ void echo_ser(int sock) while (1) peerlen = sizeof(peeraddr); if (errno == EINTR) ERR_EXIT("recvfrom error"); fputs(recvbuf, stdout); int main(void) struct sockaddr_in servaddr; if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) echo_ser(sock); return 0; |
echocli_udp.c:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
/*************************************************************************
> File Name: echocli_udp.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sun 03 Mar 2013 06:13:55 PM CST ************************************************************************/ #include <unistd.h> #define ERR_EXIT(m) \ void echo_cli(int sock) int ret; sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL); fputs(recvbuf, stdout); close(sock); } int main(void) echo_cli(sock); return 0; |
编译运行server,在两个终端里各开一个client与server交互,可以看到server具有并发服务的能力。用Ctrl+C关闭server,然后再运行server,此时client还能和server联系上。和前面TCP程序的运行结果相比较,我们可以体会无连接的含义。udp
协议来说,server与client 的界限更模糊了,只要知道对等方地址(ip和port) 都可以主动发数据。
二、UDP编程注意点
1、UDP报文可能会丢失、重复
2、UDP报文可能会乱序
3、UDP缺乏流量控制
4、UDP协议数据报文截断
5、recvfrom返回0,不代表连接关闭,因为udp是无连接的。
6、ICMP异步错误
7、UDP connect
8、UDP外出接口的确定
9、太大的UDP包可能出现的问题
由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实现,即123点所提到的。比如
如果发送端速度较快,而接收端较慢,很可能会产生 ICMP Source Quench Error,丢弃一些数据包。
对于第4点,可以写个小程序测试一下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
#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) \ int main(void) struct sockaddr_in servaddr; if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) sendto(sock, "ABCD", 4, 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); char recvbuf[1]; |
上述程序是自己发送数据给自己,发送了4个字节,但我们只提供1个字节的缓冲区recvbuf,第一次recvfrom
读取一个字节,但接下去循环却读不到剩下的数据了,因为udp
是报式协议,如果一次性接收的缓冲区小于发来的数据,有可能造成报文截断,反观tcp流式协议,可以一次读取一个数据包的一部分,也可以一次性读取多个数据包,但这也正是其会造成粘包问题的来源,所以也说udp
协议不会有粘包问题,因为一次就接收一个消息。输出如下:
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./trunc
n=1 A
............
接收了一个字符之后,再次recvfrom 就阻塞了。
对于第5点,如果我们使用sendto 发送的数据大小为0,则发送给对方的是只含有各层协议头部的数据帧,recvfrom 会返回0,但并不代表对方关闭连接,因为udp 本身没有连接的概念。
第678点合起来一起讲,可以看到我们的客户端程序现在没有调用connect,不运行服务器程序,直接运行客户端程序,查看现象:
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_udp
dfsaf
................
当我们在键盘敲入几个字符,sendto只是把Buf的数据拷贝到sock对应的缓冲区中,此时服务器未开启,协议栈返回一个ICMP异步错误,但因为前面没有调用connect“建立”一个连接,则recvfrom时不能收到这个错误而一直阻塞。现在我们在while
循环的外面添加一句:connect(sock, (struct sockaddr*)&servaddr,
sizeof(servaddr)); 再次测试一下:
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_udp
dfsaf
recvfrom: Connection refused
此时recvfrom 就能接收到这个错误而返回了,并打印错误提示。
其实connect 并没有真正建立一个连接,即没有3次握手过程,只是维护了一种状态,绑定了远程地址,因为如此在调用sendto
时也可以不指定远程地址了,如 sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);
甚至也可以使用send 函数
send(sock, sendbuf, strlen(sendbuf), 0);
假设现在客户端有多个ip地址,由connect 或 sendto
函数提供的远程地址的参数,系统会选择一个合适的出口,比如远程ip 是192.168.2.10, 而客户端现在的ip 有 192.168.1.32
和 192.168.2.75 那么会自动选择192.168.2.75 这个ip 出去。
关于第9点。假设现在我们发送一个8192B
的IP数据报,必须分片传输,如果此时目的地址arp 并没有缓存,那么每一片都会发起arp 请求,此时会造成 arp flooding(RFC
建议的最大发送速率是每秒一次)。此时每一片都在等待arp reply,系统实现只会将最后一片发送到目的地(ARP input
queue was LIFO
),也就是说,其他片都被丢弃了。对等方的IP层当接收到第一个到来的片时(不一定是偏移为0的片)会启动定时器,如果在30~60s
内的超时时间内没有接收到所有的片,则会丢弃所有接收到的片。但需要注意的是不一定会产生 ICMP "time exceeded
during reassembly" error (ICMP
超时错误类型为11,code为0表示是TTL为0超时,code为1表示对方重组分片超时),只有在已经接收到偏移为0的片,即包含udp头部的片时才会产生此种错误,因为这个时候ICMP报文的接收方通过头部(源端口号,如下ICMP超时报文的payload)才知道是哪个进程发送的这个IP报文被丢弃了。实际上有没有产生ICMP超时报文并不是那么重要,因为系统假设TCP层
或者使用UDP的应用程序最终会timeout 导致重传。
参考:
《Linux C 编程一站式学习》
《TCP/IP详解 卷一》
《UNP》
基于UDP协议的网络程序的更多相关文章
- UNIX网络编程——基于UDP协议的网络程序
一.下图是典型的UDP客户端/服务器通讯过程 下面依照通信流程,我们来实现一个UDP回射客户/服务器: #include <sys/types.h> #include <sys/so ...
- 基于UDP协议的网络编程
UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送.接收数据报的对象. Java使用DatagramSock ...
- 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程
Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...
- 基于UDP协议的控制台聊天程序(c++版)
本博客由Rcchio原创,转载请告知作者 ------------------------------------------------------------------------------- ...
- 网络编程----socket介绍、基于tcp协议的套接字实现、基于udp协议的套接字实现
一.客户端/服务器架构(C/S架构) 即C/S架构,包括: 1.硬件C/S架构(打印机) 2.软件C/S架 ...
- 网络编程(基于udp协议的套接字/socketserver模块/进程简介)
一.基于UDP协议的套接字 TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据.相对TCP,UDP则是面向无连接的协议. 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就 ...
- JAVA基础知识之网络编程——-基于UDP协议的通信例子
UDP是一种不可靠的协议,它在通信两端各建立一个socket,这两个socket不会建立持久的通信连接,只会单方面向对方发送数据,不检查发送结果. java中基于UDP协议的通信使用DatagramS ...
- c++下基于windows socket的服务器客户端程序(基于UDP协议)
前天写了一个基于tcp协议的服务器客户端程序,今天写了一个基于UDP协议的,由于在上一篇使用TCP协议的服务器中注释已经较为详细,且许多api的调用是相同的,故不再另外注释. 使用UDP协议需要注意几 ...
- 网络编程基础:粘包现象、基于UDP协议的套接字
粘包现象: 如上篇博客中最后的示例,客户端有个 phone.recv(2014) , 当服务端发送给客户端的数据大于1024个字节时, 多于1024的数据就会残留在管道中,下次客户端再给服务端发命令时 ...
随机推荐
- [leetcode]Copy List with Random Pointer @ Python
原题地址:https://oj.leetcode.com/problems/copy-list-with-random-pointer/ 题意: A linked list is given such ...
- SCSI contrller的几种类型的区别
在VMware vSphere Web Client中, 可以为虚拟机添加一个新的SCSI controller, 选项中包含如下的类型, 那么他们有什么区别呢? 如何选择呢? BusLogic ...
- 网上收集:跟着 8 张思维导图学习 Javascript【转】
学习的道路就是要不断的总结归纳,好记性不如烂笔头,so,下面将po出8张javascript相关的思维导图. 思维导图小tips:思维导图又叫心智图,是表达发射性思维的有效的图形思维工具 ,它简单却又 ...
- windows10下部署IIS10+Django web服务
下面不是一系列过程. 1.下载软件 Python: 2.78 Django: 1.10.3 wfastcgi-2.2.1 pywin32-219.win32-py2.7.exe 下载并安装以上软件 2 ...
- Java Object Clone
Java Object Clone User user = new User(); user.setName("tom"); User user1 = new User(); us ...
- textarea光标移到末尾兼容ie,ffchrome
function moveEnd(obj){ obj.focus(); var len = obj.value.length; if (document ...
- ZH奶酪:Ubuntu启动/重启/停止apache服务
Start Apache 2 Server /启动apache服务 # /etc/init.d/apache2 start or $ sudo /etc/init.d/apache2 start Re ...
- js 深冻结 与 浅冻结 Object.freeze
1.深冻结 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <t ...
- jersey获取各个参数的总结
service端: @Path("/hello") public class HelloService { @GET @Produces("text/plain" ...
- [Python]项目打包:5步将py文件打包成exe文件 简介
1.下载pyinstaller并解压(可以去官网下载最新版): http://nchc.dl.sourceforge.net/project/pyinstaller/2.0/pyinstaller-2 ...