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 地址是不是存在?

对已存在的应用程序进行 UDP 打孔

使用已存在且已连接的 OAuth 的 API 连接的逻辑应用 ARM 部署失败

io.on('connection')事件是否需要将数据发送到预先存在且已连接的套接字?

JavaLearn #(14)网络及分类TCPUDP协议IPSocketTCP编程UDP编程

内存中是不是存在空索引