UDP和TCP处于同一层网络模型中,也就是运输层,基于二者之上的应用有很多,常见的基于TCP的有HTTP、Telnet等,基于UDP有DNS、NFS、SNMP等。UDP是无连接,不可靠的数据协议服务,而TCP提供面向流、提供可靠数据服务。注意,UDP和TCP没有好坏之分,只是二者的适用场景不同罢了。

  典型的UDP套接字编程模型是客户端不予服务端建立连接,而只是调用sendto函数来向服务端发送数据,其中必须要指定服务端的信息,包括IP和端口等;服务端不接收来自客户端的连接,而只是调用recvfrom函数,来等待某个客户端的数据到达。

UDP编程模型

  在UDP套接字中,有2个函数最常用,也就是sendto和recvfrom,二者的声明如下:

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, void *buff, size_t nbytes, int flags,
const struct sockaddr *to, socklen_t addrlen);

  recvfrom和snedto的前3个参数和read/write的前3个参数一样。flags表示设置的标志值,简单的UDP程序可以直接设置为0,最后两个参数表示服务端地址(对于sendto来说)或者是对端地址(对于recvfrom来说)。如果不关心对端的地址,则设置为NULL,此时addrlen也可以设置为NULL了。

  注意:recvfrom和sendto也可以应用于TCP编程,不过一般不这样用。

简单的echo UDP服务器和客户端程序

/**
* UDP epollc测试 server端
*/
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <cstring>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h> using namespace std; int main(int argc, char **argv)
{
int listenFd = -1;
int connFd = -1;
int epollFd = -1;
struct sockaddr_in servAddr;
struct epoll_event epollEvent;
struct epoll_event epollEvents[16];
int nEvent = 0; listenFd = socket(AF_INET, SOCK_DGRAM, 0); memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(6060);
servAddr.sin_addr.s_addr = INADDR_ANY;
bind(listenFd, (struct sockaddr *)&servAddr, sizeof(servAddr)); listen(listenFd, 5); epollFd = epoll_create(5);
memset(&epollEvent, 0, sizeof(epollEvent));
epollEvent.data.fd = listenFd;
epollEvent.events = EPOLLIN; epoll_ctl(epollFd, EPOLL_CTL_ADD, listenFd, &epollEvent);
for (; ;) {
nEvent = epoll_wait(epollFd, epollEvents, 16, -1);
if (nEvent <= 0) {
continue;
} for (int i = 0; i < nEvent; i++) {
int fd = epollEvents[i].data.fd;
int event = epollEvents[i].events; if (event & EPOLLIN) {
struct sockaddr_in clientAddr;
socklen_t clientLen = sizeof(clientAddr);
int recvLen = 0;
char buff[256]; recvLen = recvfrom(fd, buff, sizeof(buff), 0, (struct sockaddr *)&clientAddr, &clientLen);
buff[recvLen] = '\0'; cout << "-------------------------" << endl;
cout << ntohs(clientAddr.sin_port) << endl;
cout << inet_ntoa(clientAddr.sin_addr) << endl;
cout << buff << endl;
cout << "-------------------------" << endl; sendto(fd, buff, strlen(buff), 0, (struct sockaddr *)&clientAddr, clientLen);
} if (event & (EPOLLHUP | EPOLLERR)) {
epoll_ctl(epollFd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
cout << "client shutdown" << endl;
}
}
} return 0;
}
/**
* UDP 客户端
*/
#include <iostream> #include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> using namespace std; int main(int argc, char **argv)
{
int connFd = -1;
struct sockaddr_in servAddr;
char buff[64] = "hi, udp server";
char buffRecv[64]; connFd = socket(AF_INET, SOCK_DGRAM, 0); memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(6060);
servAddr.sin_addr.s_addr = inet_addr("192.168.1.100"); for (int i = 0; i < 2; i++) { sendto(connFd, buff, sizeof(buff), 0, (struct sockaddr *)&servAddr, sizeof(servAddr)); int recvLen = recvfrom(connFd, buffRecv, sizeof(buffRecv), 0, NULL, NULL);
buffRecv[recvLen] = '\0'; cout << buffRecv << endl;
}
close(connFd); return 0;
}

  注意:代码中为了方便没有处理函数的返回值 :(

  UDP编程会有数据包的丢失问题,因为UDP是不可靠的,如果一个客户的数据包丢失,客户端将永远阻塞在recvfrom函数调用;类似的,如果客户数据到达了服务端,然后响应数据包丢失了,则客户永远阻塞在recvfrom调用。为了防止这样的问题出现,一般可以给recvfrom设置一个超时时间。

一个有意思的小问题

  当UDP服务端未运行时,UDP客户端发送给服务端数据包后就阻塞在了recvfrom调用了,等待一个永远也不可能的服务端应答。但是通过抓包分析,服务器主机响应了一个ICMP端口不可达的消息,但是这个ICMP错误不返回给客户进程。

  这里测试用的是Socket Tool工具,在192.168.1.100(window主机)上往192.168.1.150(linux主机)上发送UDP包的测试结果。

  这个ICMP错误是异步错误,该错误是由sendto引起,但是sendto本身成功返回,从UDP输出操作成功仅仅表示数据包已加入到数据链路层的输出队列了。而该ICMP报文是后来才接收来的。因此,对于一个UDP套接字来说,由它引发的异步错误并不返回给它,除非它已经建立连接。那么,这个问题如何解决呢,请搬个小板凳,拿着鼠标,继续翻滚 :)

UDP可以使用connect函数吗

  UDP是可以调用connect函数的,但是UDP的connect函数和TCP的connect函数调用确是大相径庭的,这里没有三次握手过程。内核只是检查是否存在立即可知的错误(比如目的地址不可达),记录对端的IP和端口号,然后立即返回调用进程。

  使用了connect的UDP编程就可不必使用sendto函数了,直接使用write/read即可。以下代码使用了connect函数,然后往未运行UDP服务的主机发送数据:

#include <iostream>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h> using namespace std; int main(int argc, char **argv) {
int connFd = -1;
struct sockaddr_in servAddr;
char buff[64] = "hi, udp server";
char buffRecv[64]; connFd = socket(AF_INET, SOCK_DGRAM, 0); memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(60);
servAddr.sin_addr.s_addr = inet_addr("192.168.1.150"); connect(connFd, (struct sockaddr *) &servAddr, sizeof(servAddr)); write(connFd, buff, sizeof(buff)); int recvLen = read(connFd, buffRecv, sizeof(buffRecv));
if (recvLen < 0) {
perror("read error");
} close(connFd); return 0;
}

输出结果为:

  这里把测试代码中的read函数替换成以下代码输出结果也是一样的。

int recvLen = recvfrom(connFd, buffRecv, sizeof(buffRecv), 0, NULL, NULL);

UDP缺乏流量控制

  每个TCP套接口都有一个发送缓冲区,我们可以用SO_SNDBUF套接口选项来改变这个缓冲区的大小。当应用程序调用write时,内核从应用进程的缓冲区中拷贝所有数据到套接口的发送缓冲区。如果套接口发送缓冲区容不下应用程序所有的程序(或者应用程序的缓冲区大于套接口发送缓冲区,或者是套接口发送缓冲区还有其他数据),应用进程将被挂起,这里假设write是阻塞的。内核将不从write系统调用返回,直到将应用进程缓冲区的所有数据都拷贝到套接口发送缓冲区。因此从写一个TCP套接口的write调用成功返回仅仅代表发送的数据到达应用进程的缓冲区。它并不告诉我们对端TCP或者应用进程已经接收到数据。

  UDP套接口有发送缓冲区大小(SO_SNDBUF修改),不过它仅仅是写到套接口的UDP数据报的大小上限,注意,实际上UDP发送缓冲区并不存在。如果一个应用程序写一个大于套接口发送缓冲区大小的数据报,内核将返回EMSGSIZE错误。既然UDP不可靠,它不必保存应用程序的数据拷贝,因此无需真正的发送缓冲区(应用进程的数据在沿协议栈往下传递,以某种形式拷贝到内核缓冲区,然而数据链路层在送出数据之后将丢弃该拷贝)。

  从UDP套接口write成功返回仅仅表示用户写入的数据报或者所有片段已经加入到数据链路层的输出队列。如果该队列没有足够的空间存放该数据包或者它的某个片段,内核通常返回给应用进程一个ENOBUFS错误。

  TCP和UDP都拥有套接口接收缓冲区。TCP套接口接收缓冲区不可能溢出,因为TCP具有流量控制,然而对于UCP来说,当接收到的数据报装不进套接口接收缓冲区时,该数据报就丢弃。UDP是没有流量控制的:较快的发送端可以很容易淹没较慢的接收端,导致接收端的UDP丢弃数据报。

  注意,正是由于UDP没有流量控制,所以其接收缓冲区满后就开始丢弃新到来的数据包,测试如下,UDP服务端程序实时打印出接收到的数据包个数:

  

  服务端显示收到了94个UDP数据包,也就是说丢失了6个,如果一次发送的UDP数据量更大的话,丢包现象更严重。

参考资料

  1、《Unix网络编程 卷一 套接字编程》UDP章节

  2、UDP接收端缓冲区和丢包问题

转:http://www.cnblogs.com/luoxn28/p/5811727.html

【转】 探索UDP套接字编程的更多相关文章

  1. 探索UDP套接字编程

    UDP和TCP处于同一层网络模型中,也就是运输层,基于二者之上的应用有很多,常见的基于TCP的有HTTP.Telnet等,基于UDP有DNS.NFS.SNMP等.UDP是无连接,不可靠的数据协议服务, ...

  2. 【Python网络编程】利用Python进行TCP、UDP套接字编程

    之前实现了Java版本的TCP和UDP套接字编程的例子,于是决定结合Python的学习做一个Python版本的套接字编程实验. 流程如下: 1.一台客户机从其标准输入(键盘)读入一行字符,并通过其套接 ...

  3. JavaTCP和UDP套接字编程

    在我们刚开始入门Java后端的时候可能你会觉得有点复杂,包含了很多杂七杂八的知识,例如文件上传下载,监听器,JDBC,请求重定向,请求转发等等(当然也没有很多),但是我们自己真正的去开发一个小型网站( ...

  4. 计算机网络实验 UDP套接字编程

    这是个傻瓜式操作教程 西科大计算机网络实验 UDP套接字编程 我用自己的Ubuntu16.04来举例,实验室的是虚拟机,差不多 只针对第三个题目,修改服务器来通过响应客户端发送的GetTime并发送给 ...

  5. UDP套接字编程 返回系统时间

    计算机网络实验 简单UDP套接字编程 这是学校老师自己改进了一点的题目.我预习了好久才搞明白,同学来问的时候,一大堆简单问题实在是不想回答...所以,这时候我觉得博客是个好东西! 我的任务是做客户端和 ...

  6. 【Unix网络编程】chapter8基本UDP套接字编程

    chapter8基本UDP套接字编程 8.1 概述 典型的UDP客户端/服务端的函数调用 8.2 recvfrom和sendto函数 #include <sys/socket.h> ssi ...

  7. TCP和UDP套接字编程 (java实现)

    在了解网络编程之前,我们先了解一下什么叫套接字 套接字即指同一台主机内应用层和运输层之间的接口 由于这个套接字是建立在网络上建立网络应用的可编程接口 因此也将套接字称为应用程序和网络之间的应用程序编程 ...

  8. 《Unix 网络编程》08:基本UDP套接字编程

    基本UDP套接字编程 系列文章导航:<Unix 网络编程>笔记 UDP 概述 流程图 recvfrom 和 sendto #include <sys/socket.h> ssi ...

  9. java基础----->TCP和UDP套接字编程

    这里简单的总结一下TCP和UDP编程的写法,另外涉及到HttpUrlConnection的用法 . TCP套接字 一.项目的流程如下说明: .客户输入一行字符,通过其套接字发送到服务器. .服务器从其 ...

随机推荐

  1. syslog及syslog-ng详解 日志服务器

    服务器的日志对系统工程师来说是至关重要的,一旦服务器出现故障或被入侵,我们需要查看日志来定位问题的关键所在,所以说对于线上跑的服务器而言日志应该合理的处理及管理.下面来   服务器的日志对系统工程师来 ...

  2. 使用js实现移动设备访问跳转到指定目录

    最近最项目的时候总会同时做pc站点跟手机站点,当手机访问的时候默认是看到pc站点的,需要在url上加上/mobile才能正常访问,这段代码是我同事分享给我的,还是蛮实用的. CODE function ...

  3. Cheatsheet: 2014 09.01 ~ 09.30

    Mobile Testing Mobile: Emulators, Simulators And Remote Debugging iOS 8 and iPhone 6 for Web Develop ...

  4. jquery之clone()方法详解

    clone()函数用于克隆当前匹配元素集合的一个副本,并以jQuery对象的形式返回.你也可以简单地理解为:克隆当前jQuery对象. 你还可以指定是否复制这些匹配元素(甚至它们的子元素)的附加数据( ...

  5. 各种编码中汉字所占字节数;中文字符集编码Unicode ,gb2312 , cp936 ,GBK,GB18030

    vim settings set fileencodings=utf-8,ucs-bom,gb18030,gbk,gb2312,cp936,latin1set termencoding=utf-8se ...

  6. tophat cufflinks cuffcompare cuffmerge 的使用

    Cole Trapnell said: there are three strategies: 1) merge bams and assemble in a single run of Cuffli ...

  7. Maven 3.3.9在Windows上的安装

    开始学Maven了,可是我一个项目都木有做过.听过Maven 的大名,用来构建项目的. 下面记录下我安装Maven的过程 1.确认电脑上安装了JDK 在cmd下执行下列命令: java –versio ...

  8. [SAP ABAP开发技术总结]权限对象检查

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  9. [Java解惑]异常

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  10. CUBRID学习笔记 37 ADO.NET Schema Provider

    通常需要添加以下引用:   1 2 3 using System.Data; using System.Data.Common; using CUBRID.Data.CUBRIDClient; 定义连 ...