14. “已连接”的UDP是不是存在?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了14. “已连接”的UDP是不是存在?相关的知识,希望对你有一定的参考价值。
参考技术A在前面的基础篇中,我们已经接触到了 UDP 数据报协议相关的知识,在我们的脑海里,已经深深印上了“UDP 等于无连接协议”的特性。那么看到这一讲的题目,你是不是觉得有点困惑?没关系,和我一起进入”已连接“的 UDP 的世界,回头再看这个标题,相信你就会恍然大悟。
我们先从一个客户端例子开始,在这个例子中,客户端在 UDP 套接字上调用 connect 函数,之后将标准输入的字符串发送到服务器端,并从服务器端接收处理后的报文。当然,和服务器端发送和接收报文是通过调用函数 sendto 和 recvfrom 来完成的。
我对这个程序做一个简单的解释:
在没有开启服务端的情况下,我们运行一下这个程序:
看到这里你会不会觉得很奇怪?不是说好 UDP 是“无连接”的协议吗?不是说好 UDP 客户端只会阻塞在 recvfrom 这样的调用上吗?怎么这里冒出一个“Connection refused”的错误呢?
别着急,下面就跟着我的思路慢慢去解开这个谜团。
从前面的例子中,你会发现,我们可以对 UDP 套接字调用 connect 函数,但是和 TCP connect 调用引起 TCP 三次握手,建立 TCP 有效连接不同,UDP connect 函数的调用,并不会引起和服务器目标端的网络交互,也就是说,并不会触发所谓的”握手“报文发送和应答。
那么对 UDP 套接字进行 connect 操作到底有什么意义呢?
其实上面的例子已经给出了答案,这主要是为了 让应用程序能够接收”异步错误“的信息 。
如果我们回想一下第 6 篇不调用 connect 操作的客户端程序,在服务器端不开启的情况下,客户端程序是不会报错的,程序只会阻塞在 recvfrom 上,等待返回(或者超时)。
在这里,我们通过对 UDP 套接字进行 connect 操作,将 UDP 套接字建立了”上下文“,该套接字和服务器端的地址和端口产生了联系,正是这种绑定关系给了操作系统内核必要的信息,能够将操作系统内核收到的信息和对应的套接字进行关联。
我们可以展开讨论一下。
事实上,当我们调用 sendto 或者 send 操作函数时,应用程序报文被发送,我们的应用程序返回,操作系统内核接管了该报文,之后操作系统开始尝试往对应的地址和端口发送,因为对应的地址和端口不可达,一个 ICMP 报文会返回给操作系统内核,该 ICMP 报文含有目的地址和端口等信息。
如果我们不进行 connect 操作,建立(UDP 套接字——目的地址 + 端口)之间的映射关系,操作系统内核就没有办法把 ICMP 不可达的信息和 UDP 套接字进行关联,也就没有办法将 ICMP 信息通知给应用程序。
如果我们进行了 connect 操作,帮助操作系统内核从容建立了(UDP 套接字——目的地址 + 端口)之间的映射关系,当收到一个 ICMP 不可达报文时,操作系统内核可以从映射表中找出是哪个 UDP 套接字拥有该目的地址和端口,别忘了 套接字在操作系统内部是全局唯一的 ,当我们在该套接字上再次调用 recvfrom 或 recv 方法时,就可以收到操作系统内核返回的”Connection Refused“的信息。
在对 UDP 进行 connect 之后,关于收发函数的使用,很多书籍是这样推荐的:
其实不同的 UNIX 实现对此表现出来的行为不尽相同。
在我的 Linux 4.4.0 环境中,使用 sendto 和 recvfrom,系统会自动忽略 to 和 from 信息。在我的 macOS 10.13 中,确实需要遵守这样的规定,使用 sendto 或 recvfrom 会得到一些奇怪的结果,切回 send 和 recv 后正常。
考虑到兼容性,我们也推荐这些常规做法。所以在接下来的程序中,我会使用这样的做法来实现。
一般来说,服务器端不会主动发起 connect 操作,因为一旦如此,服务器端就只能响应一个客户端了。不过,有时候也不排除这样的情形,一旦一个客户端和服务器端发送 UDP 报文之后,该服务器端就要服务于这个唯一的客户端。
一个类似的服务器端程序如下:
我对这个程序做下解释:
注意这里所有收发函数都使用了 send 和 recv。
接下来我们实现一个 connect 的客户端程序:
我对这个客户端程序做一下解读:
注意这里所有收发函数也都使用了 send 和 recv。
接下来,我们先启动服务器端程序,然后依次开启两个客户端,分别是客户端 1、客户端 2,并且让客户端 1 先发送 UDP 报文。
我们看到,客户端 1 先发送报文,服务端随之通过 connect 和客户端 1 进行了“绑定”,这样,客户端 2 从操作系统内核得到了 ICMP 的错误,该错误在 recv 函数中返回,显示了“Connection refused”的错误信息。
一般来说,客户端通过 connect 绑定服务端的地址和端口,对 UDP 而言,可以有一定程度的性能提升。
这是为什么呢?
因为如果不使用 connect 方式,每次发送报文都会需要这样的过程:
连接套接字→发送报文→断开套接字→连接套接字→发送报文→断开套接字 →………
而如果使用 connect 方式,就会变成下面这样:
连接套接字→发送报文→发送报文→……→最后断开套接字
我们知道,连接套接字是需要一定开销的,比如需要查找路由表信息。所以,UDP 客户端程序通过 connect 可以获得一定的性能提升。
在今天的内容里,我对 UDP 套接字调用 connect 方法进行了深入的分析。之所以对 UDP 使用 connect,绑定本地地址和端口,是为了让我们的程序可以快速获取异步错误信息的通知,同时也可以获得一定性能上的提升。
可以对一个 UDP 套接字进行多次 connect 操作吗? 你不妨动手试试,看看结果。
UDP协议经过connect之后,在通过sendto来发送数据报时不需要指定目的地址、端口,如果指定了目的地址、端口,那么会返回错误。通过UDP协议可以给同一个套接字指定多次connect操作,而TCP协议不可以,TCP只能指定一次connect操作。UDP协议指定第二次connect操作之后会先断口第一次的连接,然后建立第二次的连接。
如果想使用多播或广播,我们应该怎么去使用 connect 呢?
参考: https://blog.51cto.com/a1liujin/1699540
对于recvfrom函数,我们可以看成是TCP中accept函数和read函数的结合,前三个参数是read的参数,后两个参数是accept的参数。对于sendto函数,则可以看成是TCP中connect函数和send函数的结合,前三个参数是send的参数,后两个参数则是connect的参数。所以udp在发送和接收数据的过程中都会建立套接字连接,只不过每次调用sendto发送完数据后,内核都会将临时保存的对端地址数据删除掉,也就是断开套接字,从而就会出现循环。
Android 蓝牙:是不是已连接?
【中文标题】Android 蓝牙:是不是已连接?【英文标题】:Android Bluetooth: IsConnected?Android 蓝牙:是否已连接? 【发布时间】:2010-08-17 21:23:54 【问题描述】:有没有办法确定您是否连接到蓝牙设备?
我的应用程序连接、发送/接收都很好。但是我需要一种方法来查看我是否仍然连接说..如果我走出范围并回到范围内。
我注意到蓝牙套接字中没有 isConnected 函数,就像在 TCP 东西中一样...有没有办法查看您是否已连接,或者与您应该连接的设备通信?
【问题讨论】:
【参考方案1】:我能够解决此问题的唯一方法是每秒发送一次“心跳”消息。如果它没有通过,那么我认为蓝牙已断开连接。
【讨论】:
【参考方案2】:发送尽可能少的数据,看看是否得到响应。如果你没有,那么你没有连接。
【讨论】:
【参考方案3】:当任何 BT 设备断开连接时,以下广播接收器值会告诉您:
intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); // API 5
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); // API 5
如果您对特定设备感兴趣,您可能应该实现一个 BluetoothProfile.ServiceListener 代理侦听器:
private class MyBluetoothHeadsetListener //
implements BluetoothProfile.ServiceListener
@Override
public void onServiceDisconnected(int profile)
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy)
if (profile == BluetoothProfile.A2DP)
BluetoothA2dp bluetoothA2dp = (BluetoothA2dp) proxy;
mDevicesA2dp = bluetoothA2dp.getConnectedDevices();
for (BluetoothDevice deviceA2dp : mDevicesA2dp)
boolean isA2dpPlaying = bluetoothA2dp.isA2dpPlaying(deviceA2dp);
return;
if (profile == BluetoothProfile.HEADSET)
BluetoothHeadset bluetoothHeadset = (BluetoothHeadset) proxy;
mDevicesNonA2dp = bluetoothHeadset.getConnectedDevices();
if (mDevicesNonA2dp.size() > 0)
for (BluetoothDevice deviceNonA2dp : mDevicesNonA2dp)
BluetoothClass bluetoothClass = deviceNonA2dp.getBluetoothClass();
String bluetoothDeviceClass = bluetoothClassToString(bluetoothClass);
boolean isAudioConnected = bluetoothHeadset.isAudioConnected(deviceNonA2dp);
return;
...
private MyBluetoothHeadsetListener mProfileListener = new MyBluetoothHeadsetListener();
...
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
adapter.getProfileProxy(mApp, mProfileListener, BluetoothProfile.HEADSET);
adapter.getProfileProxy(mApp, mProfileListener, BluetoothProfile.A2DP);
【讨论】:
以上是关于14. “已连接”的UDP是不是存在?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 PHP 和 fsockopen 检查 UDP 地址是不是存在?
使用已存在且已连接的 OAuth 的 API 连接的逻辑应用 ARM 部署失败
io.on('connection')事件是否需要将数据发送到预先存在且已连接的套接字?