UDP 是User Datagram Protocol的简称,UDP进行通信,客户端与服务端不进行连接,只是使用数据报进行通信。
一个程序打开一个UDP端口,可以给任何远程主机的UDP端口发送数据报,也可以接收任何发送到本端口的数据报(前提是别人知道你的UDP端口)。
数据报最大可以是65536字节。
DatagramChannel 可以打开并监听一个UDP端口,接收发送到该端口的任何数据报(可能来源于任何一台主机),也可以发送数据报给任何一台主机。(一个DatagramChannel 对应一个UDP端口)
API:
|
|
static DatagramChannel |
open() 打开数据报通道。 |
abstract SocketAddress |
receive(ByteBuffer dst) 通过此通道接收数据报。 |
abstract int |
send(ByteBuffer src, SocketAddress target) 通过此通道发送数据报。 |
abstract DatagramSocket |
socket() 获取与此通道关联的数据报套接字。 |
int |
事件类型由一个int值表示: 第0位表示可读,第2位表示可写,第3位表示有连接请求,第4位表示可接收连接请求 |
实例的产生:需要调用静态的open方法,获取DatagramChannel实例。
阻塞状态: DatagramChannel 打开(open)之后,默认为阻塞状态。
当调用read方法时,如果没有数据报发来,则一至阻塞直至接收到数据报才返回。
当调用write方法时,由于底层缓冲区没有足够空间容纳要发送的数据,则阻塞,直至缓冲区够大才发送。
非阻塞状态: DatagramChannel 打开(open)之后,调用configureBlocking(false)方法,将数据报通道 配置成为非阻塞模式。
非阻塞模式需要与Selector(选择器)一起使用,将DatagramChannel(数据报通道)在Selector注册了感兴趣的事件(read/write),调用Selector.select()方法时,该方法会阻塞直至发生了感兴趣的事件,如接收到数据报(read)或可以发送数据报(write,底层缓冲区可用时),select()方法结果并返回SelectionKey,通过SelectionKey的方法 isReadable()或
isWritable()来判断是哪一种操作准备好了,
SelectionKey.channel()返回是哪条数据报通道准备好了( Selector 上可以注册多个可选择通道如多个 DatagramChannel 或SocketChannel),再调用
该DatagramChannel的接收或发送方法。
非阻塞模式下,需要一条线程在无限循环中不断的调用 Selector.select() 以监控数据报通道的可读或可写。在selector只监控一条 DatagramChannel 情况下,与阻塞模式类似:需要一条线程调用receive方法,该方法阻塞直至有数据报发来,调用send方法时,可能由于底层缓冲区不够而阻塞直至缓冲区可用。
随机端口:默认为随机端口
非随机端口:使用 DatagramChannel.socket().bind(new InetSocketAddress(端口号))绑定到指定端口
与任意地址通信:
SocketAddress receive(ByteBuffer dst):该方法接收任意地址发来的数据报
该方法在非阻塞模式下会被阻塞直至接收到数据报,在非阻塞模式下得到可读事件时再调用不会阻塞。
如果数据报中的字节数大于给定缓冲区中的剩余空间,则丢弃余下的数据报,数据报最大为65536字节,因此针对实际情况设置一个合适的缓冲区大小以防止数据丢弃。 可以创建一个足够大的 ByteBuffer 接收数据报,每次接收都使用这个ByteBuffer(只有一个),接收完成后,按实际数据报大小再生成一个ByteBuffer,这个ByteBuffer会小得多,由业务逻辑代码对这个ByteBuffer进行处理,节省了内存又防止多线程同时对一个ByteBuffer处理引用线程安全问题。
接收数据固定要使用一条线程,那么对接收到的数据报处理是在接收线程中或使用另外的一条或几条线程,取决于接收数据报的频率与业务逻辑处理时间的长短。最好接收一条线程,业务处理一条线程,数据少时只是多占用了一条线程,数据报频繁或业务逻辑处理时间长时,也可以方便扩充业务逻辑线程个数,对程序的改动少。
返回值: SocketAddress ,指定数据报的来源,可以根所这个 SocketAddress 给对端发送数据报。
int send(ByteBuffer src, SocketAddress target) :该方法发送数据报到指定的目标地址。
将 ByteBuffer 中的可用数据(position至limit)打成单个数据报发给给目标地址。
该方法在非阻塞模式下可能会被阻塞(可能是底层数据缓冲区不够大时)直至能把可用数据全部发送出去(一个数据报),返回全部数据的字节数。
该方法在非阻塞模式下,可能因为底层数据缓冲区不够大,不能够容纳 ByteBuffer 中的可用数据(不能生成一个完整数据报)而返回0,即不发送任何字节。 策略是要么全部发送要么不发送,因为发送出的数据要打成单个数据报发送给目标地址。这与socket通道不同:socket通道是连接的,在通道上能够发送几个字节就发送几个字节,并返回发送字节个数,因此socket通道的发送方法需要在无限循环中,直至所有的字节发送完成才退出循环,即完成发送。
一般非阻塞模式下,在Selector(选择器)中只注册读取事件,当调用send方法(写操作)返回0时,表明底层数据缓冲区不够大,那么什么时候缓冲区够大呢?不知道,那么不断的发送呢:
while((sendNum = this.datagramChannel.send(src, target))==0){};
这段代码在缓冲区足够大之前,一直空跑,占用cpu。
解决方式一:
当 send方法返回0时,在Selector(选择器)上注册可写事件,当发生可写事件时,再调用send方法发送。
这种方式可能会造成同一通道上的send的数据先后顺序改变:
当选择器发现可以写入前,可能另一线程调用同一通道的send方法,这时刚好可以写入,之后选择器才发现可写入事件并写入数据,这就造成了,后调用send方法的数据比先调用方法的数据优先写入了。对于数据流顺序不影响业务时,可以这么做。
解决方式二:
当 send方法返回0时,打开一个新的选择器,并注册监控该通道上的写入事件,调用该选择器上的select()方法,这时阻塞,直到可以写入了,再send.
//返回值 sendNum==length ,说明数据全被发送出去了,返回值sendNum=0,说明底层输出缓冲区中没有足够的空间供数据报,则未发送任何字节数据 //与socketChannel 不同,socketChannel在底层输出缓冲区中没有足够的空间时,能发送几个字节就发几个字节。返回值为发送字节个数。 if(sendNum ==0){ SendData sendData = new SendData(); sendData.setByteBuffer(allByteBuffer); sendData.setTarget(target); this.sendDataQueue.add(sendData);//加入待发队列 SelectionKey key = datagramChannel.keyFor(this.selector);//获取注册的key key.interestOps(SelectionKey.OP_READ |SelectionKey.OP_WRITE);//加入监控可写事件 this.selector.wakeup();//唤醒选择器,使其在下一周期select()时监控可写事件,当前正执行的select()方法不会监控新加入的可写入事件 }
while(true){ selector.select();//阻塞直至收到数据包,返回值可能为零,但有事件发生,因此不以返回值判断事件数 if(this.stop){//退出标志 break; } Set<SelectionKey> keys = selector.selectedKeys();//获取发生读取事件的注册键 Iterator<SelectionKey> iterator = keys.iterator(); while(iterator.hasNext()){//遍历 SelectionKey key=iterator.next(); iterator.remove();//需要手工移除注册键,否则下次selectedKeys里仍然包括它(虽然该selectionKey对应的通道上没有事件) // DatagramChannel dc = (DatagramChannel)key.channel();//获取接收数据通道==datagramChannel if(key.isWritable()){//通道可以写入了 if(!sendDataQueue.isEmpty()){//重发队列不为空 SendData sendData = null; while((sendData = sendDataQueue.peek()) != null){ if(!this.send(sendData.getTarget(), sendData.getByteBuffer())){//重发失败 break; }else{ sendDataQueue.poll();//重发成功 } } }else{//重发队列空了,不再监控可写入事件,只监控接收数据事件 key.interestOps(SelectionKey.OP_READ); } }else if(key.isReadable()){//接收数据事件,有数据被接收了 byteBuffer.clear(); final SocketAddress from = this.receive(byteBuffer);//接收数据包,返回数据来源 //处理业务逻辑 } }
与固定地址通信:
首先调用connect 方法连接指定远程地址,之后就可以调用不带有下面的read 或write方法与之通信。
在调用disconnect ()断开连接以前,不能与其它地址通信。
read方法类似于 receive方法。
write方法类似于send方法。
abstract DatagramChannel |
connect(SocketAddress remote) 连接此通道的套接字。 |
abstract DatagramChannel |
disconnect() 断开此通道套接字的连接。 |
abstract boolean |
isConnected() 判断是否已连接此通道的套接字。 |
abstract int |
read(ByteBuffer dst) 从此通道读取数据报。 |
long |
read(ByteBuffer[] dsts) 从此通道读取数据报。 |
abstract long |
read(ByteBuffer[] dsts, int offset, int length) 从此通道读取数据报。 |
abstract int |
write(ByteBuffer src) 将数据报写入此通道。 |
long |
write(ByteBuffer[] srcs) 将数据报写入此通道。 |
abstract long |
write(ByteBuffer[] srcs, int offset, int length) 将数据报写入此通道。 |
DatagramChannel 使用方法:
datagramChannel = DatagramChannel.open();//打开通道 datagramChannel.socket().bind(new InetSocketAddress(this.localPort));//绑定本地端口 datagramChannel.configureBlocking(false);//配置成非阻塞模式 selector = Selector.open();//打开选择器 datagramChannel.register(selector, SelectionKey.OP_READ);//注册监听可读取事件 while(true){ selector.select();//阻塞直至收到数据报,返回值可能为零,但有事件发生,因此不以返回值判断事件数 Set<SelectionKey> keys = selector.selectedKeys();//获取发生读取事件的注册键 Iterator<SelectionKey> iterator = keys.iterator(); while(iterator.hasNext()){//遍历 SelectionKey key=iterator.next(); iterator.remove();//需要手工移除注册键,否则下次selectedKeys里仍然包括它(虽然该selectionKey对应的通道上没有事件) // DatagramChannel dc = (DatagramChannel)key.channel();//获取接收数据通道==datagramChannel if(key.isWritable()){//通道可以写入了 }else if(key.isReadable()){//通道可以读取了 } } }
相关推荐
描述中的"DatagramChannel UDP 非阻塞socket"提到了两个关键概念:DatagramChannel和非阻塞Socket。在Java中,`DatagramChannel`是NIO(Non-blocking Input/Output)的一部分,允许程序员进行低级别的UDP通信。它...
`DatagramChannel`是Java NIO中的一个重要组件,主要用于处理UDP(User Datagram Protocol)数据包的发送和接收。`DatagramChannelImpl`则是`DatagramChannel`的默认实现类,它实现了相关的底层操作。 `...
在客户端,我们需要创建一个`UdpClient`,同样使用`Bootstrap`,但这次是`DatagramChannel`,因为UDP是无连接的。客户端可以向多个服务器发送数据,不需要维持连接状态。 ```java public class UdpClient { public...
在 Netty 中,我们可以使用 `NioDatagramChannel` 类来处理 UDP 通信。 创建 UDP 接收服务的关键步骤如下: 1. **初始化 Bootstrap**:首先,我们需要创建一个 `Bootstrap` 实例,这是 Netty 中启动服务器的基本...
在上述代码中,我们使用了NIO(非阻塞I/O)事件循环组来管理线程,`NioDatagramChannel`作为UDP通信的通道,`UdpServerHandler`和`UdpClientHandler`作为业务处理逻辑。通过`writeAndFlush`方法,我们可以将数据发送...
`NioDatagramChannel`是`DatagramChannel`的具体实现,基于Java NIO。 在实际编程中,你可以通过`DatagramPacket`的`content()`方法获取消息内容,`sender()`获取发送者地址,`recipient()`获取接收者地址。UDP的单...
`DatagramChannel`接口扩展了Netty的`Channel`抽象,用于发送和接收`AddressedEnvelope`消息,实现UDP多播组的管理。`NioDatagramChannel`是具体的实现类,基于Java NIO(非阻塞I/O)。 在UDP单播中,数据包被发送...
4. **ChannelHandlerContext 和 ChannelPipeline**: 当数据通过 DatagramChannel 时,它会经过 ChannelPipeline,这个管道包含了多个 ChannelHandler,每个 Handler 都可以处理或转换数据。 5. **多线程模型**: ...
DatagramChannel channel = DatagramChannel.open(); InetSocketAddress remoteAddress = new InetSocketAddress("localhost", 9999); ByteBuffer buf = ByteBuffer.wrap("Hello".getBytes()); channel.send(buf...
Java NIO系列教程(一) Java NIO 概述 Java NIO系列教程(二) Channel ...Java NIO系列教程(十) Java NIO DatagramChannel Java NIO系列教程(十一) Pipe Java NIO系列教程(十二) Java NIO与IO
FileChannel用于文件的读写,SocketChannel用于TCP网络通信,而DatagramChannel则用于UDP协议的无连接通信。 1. **FileChannel**:它是从文件读取数据或向文件写入数据的通道。支持随机访问,可以通过position()...
10-Java NIO-Channel-DatagramChannel.mp4 11-Java NIO-Channel-分散和聚集.mp4 12-Java NIO-Buffer-概述.mp4 13-Java NIO-Buffer-基本使用.mp4 14-Java NIO-Buffer-三个属性和类型.mp4 17-Java NIO-Buffer-缓冲区分...
常见的通道有FileChannel、SocketChannel、ServerSocketChannel和DatagramChannel等。 2. **缓冲区(Buffer)**:缓冲区是NIO中数据存储的主要对象,它是内存块的抽象,用于在通道和应用程序之间传输数据。Java NIO...
·00. 尚硅谷__NIO__源码、课件 ·01. 尚硅谷_NIO_NIO 与 IO 区别 ·02. 尚硅谷_NIO_缓冲区(Buffer)的数据存取 ·03. 尚硅谷_NIO_直接缓冲区与非直接缓冲区... 尚硅谷_NIO_DatagramChannel ·12. 尚硅谷_NIO_Pipe 管道
FileChannel用于文件的读写操作,而SocketChannel和ServerSocketChannel用于TCP网络通信,DatagramChannel则用于UDP网络通信。 选择器(Selectors):Java NIO的选择器用于实现单线程管理多个网络连接。通过选择器...
Java NIO提供了多种类型的通道,如FileChannel、SocketChannel、ServerSocketChannel和DatagramChannel等。 - FileChannel主要用于文件操作,SocketChannel和ServerSocketChannel用于TCP网络通信,而...
- 通道的实例包括FileChannel、DatagramChannel、SocketChannel和ServerSocketChannel。 - FileChannel用于文件IO操作,允许读写文件内容。 - DatagramChannel支持UDP协议的网络数据读写。 - SocketChannel和...
通过`NioDatagramChannel`,我们可以监听UDP端口并处理接收到的数据。 四、HTTP支持 HTTP(超文本传输协议)是互联网上应用最广泛的一种网络协议,对于手机游戏服务器,可能需要提供Web接口供玩家查询或更新游戏...
Java NIO提供了多种通道,如FileChannel、SocketChannel、ServerSocketChannel和DatagramChannel。 2. **文件通道**:FileChannel主要用于与文件进行交互,可以通过RandomAccessFile、FileInputStream或...
在UDP(用户数据报协议)方面,Netty的实现基于DatagramChannel,UDP是无连接的、不可靠的传输协议,适合于对实时性要求较高的应用场景。通过Netty的DatagramChannelBootstrap,开发者可以创建一个服务器来接收和...