前言:
SocketChannel作为网络套接字的通道,与之前我们学习到的FileChannel有很多不同之处(就是两个大类别的通道)。
没有SocketChannel之前,我们创建网络连接一般都是通过Socket和ServerSocket,这些都是BIO类别,性能的扩展会受到影响。
借助NIO相关实现SocketChannel和ServerSocketChannel,我们可以管理大量连接并且实现更小的性能损失。
本文就来介绍下SocketChannel的相关使用。
我们来给定一个需求:就是创建一个简易的对话框,使客户端和服务端可以接收到彼此的对话,并予以响应。(本篇专注于client端,也就是Socket和SocketChannel,下一篇会继续将server端的补上)。
1.基于Socket的客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69public class BIOClientSocket { private String address; private int port; public BIOClientSocket(String address, int port) { this.address = address; this.port = port; } public void connectToServer() { Socket socket = new Socket(); try { socket.connect(new InetSocketAddress(address, port)); // 写数据 new ClientWriteThread(socket).start(); // 读数据 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String msg = ""; while ((msg = bufferedReader.readLine()) != null) { System.out.println("receive msg: " + msg); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { String address = "localhost"; int port = 9999; BIOClientSocket bioClientSocket = new BIOClientSocket(address, port); bioClientSocket.connectToServer(); } } /** * 客户端发送请求线程 */ class ClientWriteThread extends Thread { private Socket socket; private PrintWriter writer; private Scanner scanner; public ClientWriteThread(Socket socket) throws IOException { this.socket = socket; this.scanner = new Scanner(System.in); this.writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); } @Override public void run() { String msg = ""; try { // 通过获取对话框里的消息,不断发送到server端 while ((msg = scanner.nextLine()) != null) { if (msg.equals("bye")) { break; } writer.println(msg); } } catch (Exception e) { e.printStackTrace(); } } }
以上就是标准的Socket客户端与服务端交互的代码,也比较简单,笔者不再详述
2.基于SocketChannel的客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88public class NIOClientSocket { private String address; private int port; private Selector selector; private ByteBuffer readBuffer = ByteBuffer.allocate(1024); private ByteBuffer writeBuffer = ByteBuffer.allocate(1024); private Scanner scanner = new Scanner(System.in); public NIOClientSocket(String address, int port) throws IOException { this.address = address; this.port = port; this.selector = Selector.open(); } public void connectToServer() { try { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_CONNECT); socketChannel.connect(new InetSocketAddress(address, port)); connect(); } catch (IOException e) { e.printStackTrace(); } } private void connect() { while (true) { try { selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); for (SelectionKey key : selectionKeys) { if (key.isConnectable()) { SocketChannel clientChannel = (SocketChannel) key.channel(); if (clientChannel.isConnectionPending()) { clientChannel.finishConnect(); System.out.println("client connect success..."); } clientChannel.register(selector, SelectionKey.OP_WRITE); } else if (key.isReadable()) { SocketChannel clientChannel = (SocketChannel) key.channel(); StringBuffer sb = new StringBuffer("receive msg: "); readBuffer.clear(); while (clientChannel.read(readBuffer) > 0) { readBuffer.flip(); sb.append(new String(readBuffer.array(), 0, readBuffer.limit())); } System.out.println(sb.toString()); clientChannel.register(selector, SelectionKey.OP_WRITE); } else if (key.isWritable()) { SocketChannel clientChannel = (SocketChannel) key.channel(); String msg = scanner.nextLine(); writeBuffer.clear(); writeBuffer.put(msg.getBytes()); writeBuffer.flip(); clientChannel.write(writeBuffer); clientChannel.register(selector, SelectionKey.OP_READ); } } selectionKeys.clear(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { String address = "localhost"; int port = 9999; try { new NIOClientSocket(address, port).connectToServer(); } catch (IOException e) { e.printStackTrace(); } } }
借助Selector,我们将想要监听的事件注册到Selector上。
client端默认先进行写,故在连接建立完成之后,直接注册了写事件;
写的事件会阻塞到Scanner上,等待用户输入,输入后传输给Server端,然后注册读事件;
通过这样的读写事件来回注册,就可以实现类似对话框的效果。(当然,必须是一问一答)。
3.SocketChannel API
我们先来看下其类结构图
3.1 非阻塞模式
1
2
3
4
5
6
7
8
9
10
11
12// SocketChannel直接建立连接,当前进程并没有阻塞 socketChannel.connect(new InetSocketAddress(address, port)); // 后续通过注册的Selector来获取连接状态 // 当selector检测到SocketChannel已经完成连接或连接报错,则会添加OP_CONNECT到key的就绪列表中 if (key.isConnectable()) { SocketChannel clientChannel = (SocketChannel) key.channel(); // 此时需要判断连接是否成功 if (clientChannel.isConnectionPending()) { clientChannel.finishConnect(); System.out.println("client connect success..."); }
3.2 NetworkChannel(网络连接相关方法)
SocketChannel实现了NetworkChannel接口的相关方法,来完成ip:port的绑定,socket属性的设置。
1
2
3
4
5// 使当前channel绑定到具体地址 NetworkChannel bind(SocketAddress local) throws IOException; // 设置socket属性 <T> NetworkChannel setOption(SocketOption<T> name, T value) throws IOException;
3.3 AbstractSelectableChannel(绑定Selector相关方法)
SocketChannel继承了AbstractSelectableChannel抽象类,来完成Selector的注册,多路复用功能。
1
2
3
4
5
6// 将当前通道注册到Selector上 public abstract SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException; // 获取当前selector上可执行的操作(OP_READ OP_WRITE...) public final SelectionKey keyFor(Selector sel)
3.4 ByteChannel(数据的读写)
SocketChannel实现ByteChannel接口,这个接口我们之前了解过,ByteChannel接口继承了ReadableByteChannel和WritableByteChannel,实现了对数据的读写。
上文中的示例里,clientChannel.read()和clientChannel.write()方法就是对其的使用。
4.Socket与SocketChannel
通过以上的介绍,我们会使用了SocketChannel,也会使用Socket来创建对服务端的连接。那么这两者之间有什么关系吗?
1
2
3
4
5
6
7
8
9
10// A socket is an endpoint for communication between two machines public class Socket implements java.io.Closeable { /** A socket will have a channel if, and only if, the channel itself was * created via the{@link java.nio.channels.SocketChannel#open * SocketChannel.open} or {@link * java.nio.channels.ServerSocketChannel#accept ServerSocketChannel.accept} */ public SocketChannel getChannel() { return null; } }
根据其类上面的注释,我们可以看到,Socket是一个端点,用于连接两个机器。
而直接使用socket.getChannel方法来获取其对应的通道时,则返回了null,同时给出提示:我们只能通过SocketChannel.open或者ServerSocketChannel.accept方法来获取通道。
1
2
3
4
5
6
7
8// A selectable channel for stream-oriented connecting sockets public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel { // Retrieves a socket associated with this channel. public abstract Socket socket(); }
同样看注释,SocketChannel被描述为一个可选择(注册到Selector上)的通道,用来连接socket(client-server)。
而SocketChannel.socket方法,则返回通道对应的Socket。
总结:虽然每个SocketChannel通道都有一个关联的Socket对象,但并非所有的socket都有一个关联的SocketChannel。
如果我们使用传统的方式来new Socket,那么其不会有关联的SocketChannel
参考:
非阻塞式socket_一个菜鸟的博客-CSDN博客_非阻塞socket
SocketChannel---各种注意点_billluffy的博客-CSDN博客_socketchannel NIO相关的坑,大家可以借鉴下
最后
以上就是眼睛大康乃馨最近收集整理的关于NIO源码解析-SocketChannel的全部内容,更多相关NIO源码解析-SocketChannel内容请搜索靠谱客的其他文章。
发表评论 取消回复