客户端和服务端通过以下方式连接和通信,虽然它们各自获取的SocketChannel对象不同,但它们通过网络连接形成了一个双向通信链路:
客户端部分
selector = Selector.open();
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
这段代码中,客户端创建了一个SocketChannel实例,并尝试连接到指定服务器地址和端口。之后,这个SocketChannel被设置为非阻塞模式,并注册到Selector上,关注OP_READ事件,表明客户端准备好了接收数据。这里的SocketChannel代表了客户端一侧的连接通道。
服务端部分
if(key.isReadable()) { //发生 OP_READ
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer buffer = (ByteBuffer)key.attachment();
channel.read(buffer);
System.out.println("form 客户端 " + new String(buffer.array()));
}
在服务端,当检测到SelectionKey的OP_READ事件就绪时,通过key.channel()反向获取到的SocketChannel对象,实际上是与某个已连接客户端对应的通道。这意味着,虽然服务端可能通过这种方式处理来自多个客户端的连接,每次处理时通过key拿到的SocketChannel是特定于当前正在处理的客户端的。
它们之间的连接与通信
连接建立:当客户端的SocketChannel通过open()和connect()方法成功连接到服务端时,服务端的ServerSocketChannel会接收到新的连接请求,并通过accept()方法创建一个新的SocketChannel来专门处理这个连接。此时,客户端和服务端各自持有一个SocketChannel,它们通过底层的TCP/IP协议栈连接在一起,形成了一条物理通信链路。
数据交换:一旦连接建立,双方就可以通过各自的SocketChannel读写数据。服务端通过上面的代码片段监听到OP_READ事件,意味着有数据可以从客户端的SocketChannel读取;客户端也可以通过类似的逻辑监听读事件来接收服务端的数据,或者直接写入数据到其SocketChannel发送给服务端。
综上所述,虽然客户端和服务端获取的SocketChannel对象在内存中是不同的实例,但它们通过网络连接相互关联,共同构成了客户端与服务端之间数据传输的双向通道。
缓冲区在通信中的桥梁作用
数据封装:缓冲区作为数据的容器,帮助封装待发送的数据,确保数据以合适的块大小进行传输,减少网络操作的次数,提高效率。
边界管理:通过缓冲区的capacity(容量)、position(当前位置)、limit(上界)等属性,可以有效管理数据的读写边界,防止数据溢出或未完全读取。
字节操作:无论是文本还是二进制数据,都可以通过缓冲区进行统一处理,便于进行字节级别的操作和转换。
总之,缓冲区位于客户端和服务器的SocketChannel之间,是数据传输的必要组件,它使得数据在实际网络传输前后有一个暂存和处理的空间,保证了数据的可靠传递和高效处理。
数据流向关系图解述
客户端数据准备:
info.getBytes(): 首先,将要发送的字符串info转换为字节数组。这一步是将人类可读的文本转换为计算机可以直接处理的二进制数据。
缓冲区分配与填充:
ByteBuffer.wrap(info.getBytes()): 然后,使用ByteBuffer.wrap()方法创建一个ByteBuffer实例,并将转换后的字节数组包装进去。这一步骤为数据传输准备了缓冲区,使得数据可以被SocketChannel识别并发送。
数据写入SocketChannel:
socketChannel.write(ByteBuffer): 最后,通过SocketChannel的write方法,将缓冲区中的数据写入到网络中。这个过程实际上是由操作系统完成的,数据从应用层经过传输层(通常是TCP/IP协议栈)封装成网络包,然后通过物理网络发送到服务端。
+------------+ +----------------+ +------------------+ +-------------+
| Client App| --> |ByteBuffer (Wrap)| --> |SocketChannel (Write)| --> | Network |
| (info Text)| | (info Bytes) | | | | (Data Packet)|
+------------+ +----------------+ +------------------+ +-------------+
理解了客户端如何使用ByteBuffer和SocketChannel发送数据后,让我们扩展到服务端,看看整个数据流动的完整过程。服务端的工作主要是接收数据、处理(如果需要),然后可能向客户端响应数据。下面是结合服务端的简化数据流向关系图解:
客户端到服务端数据流向
客户端准备并发送数据:
客户端应用层将信息转换为字节,封装到ByteBuffer,通过SocketChannel写入网络。
网络传输:
封装好的数据包在网络中从客户端传输到服务端。
服务端接收数据:
服务端SocketChannel监听网络,接收到数据包。
ByteBuffer分配与读取:服务端为接收数据分配一个ByteBuffer,通过SocketChannel.read(ByteBuffer)方法从网络读取数据到缓冲区中。
服务端处理与响应(假设服务端简单回显)
数据处理:
服务端解析缓冲区中的数据,可能进行一些逻辑处理或直接准备回显。
服务端响应客户端:
响应数据封装:将响应信息转换为字节数组,然后用ByteBuffer包裹。
通过SocketChannel写回:使用服务端的SocketChannel.write(ByteBuffer)方法将响应数据写回客户端。
完整关系图解述
+------------+ +----------------+ +------------------+ +-------------+ +----------------+ +-------------+
| Client App| --> |ByteBuffer (Wrap)| --> |SocketChannel (Write)| --> | Network | --> |SocketChannel (Read)| --> |ByteBuffer (Read)| --> | Server App |
| (info Text)| | (info Bytes) | | | | (Data Packet)| | | | (Received Info)| | (Process/Response)|
+------------+ +----------------+ +------------------+ +-------------+ +----------------+ +-------------+
+------------+ +----------------+ +------------------+ +-------------+
| Server App | --> |ByteBuffer (Wrap)| --> |SocketChannel (Write)| --> | Network |
| (Response) | | (Resp Bytes) | | | | (Resp Packet)|
+------------+ +----------------+ +------------------+ +-------------+
这个流程展示了客户端如何通过SocketChannel和ByteBuffer发送数据到服务端,服务端如何接收并处理这些数据,以及如何使用相同的机制响应客户端。整个过程中,ByteBuffer作为数据临时存储的媒介,有效地促进了客户端与服务端之间的数据交换