原始的编程模型 Java Socket 示例
服务器端 也就是IO 阻塞式编程的普通写法
package com.xykj.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
服务器端继承Thread
* 可以实现简单的交互
* */
public class Server extends Thread {
// 定义服务器接口ServerSocket
ServerSocket server = null;
// 定义一个服务器,定义端口
public Server(int port) {
try {
server = new ServerSocket(port);
} catch (IOException e) {
e.printStackTrace();
}
}
// 发送消息的线程
@Override
public void run() {
super.run();
try {
System.out.println("服务器在启动中...等待用户的连接");
//一直接收用户的连接,连接之后发送一条短信给用户
while(true){
// 建立socket接口,accept方法是一个阻塞进程,等到有用户连接才往下走
// 定义Socket类
Socket socket = server.accept();
//通过socket对象可以获得输出流,用来写数据
OutputStream os = socket.getOutputStream();
// 向客户端发送消息
os.write("服务器正在向你发送消息!".getBytes());
//在服务器上显示连接的上的电脑、
System.out.println(socket.getInetAddress().getHostAddress()+"连接上了!");
//通过socket对象可以获得输入流,用来读取用户数据
InputStream is=socket.getInputStream();
//读取数据
int len=0;
byte[] buf=new byte[1024];
while ((len=is.read(buf))!=-1) {
//直接把获得的数据打印出来
System.out.println("服务器接收到客户端的数据:"+new String(buf,0,len));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
**缺点: 一个请求对应着一个线程,那么对于大的系统以上面的选择肯定是不显示的 那么 我们来看看NIO 的写法 **
** Selector 概念 : Event 事件 ,Event 是异步编程不可缺少的概念 , 事件 比如说 连接事件,回调事件等, 异步的 **
NIO 的相关概念与 示例
Selector 与Channel的的关系是通过 SelectionKey来进行标识的
//SelectionKey Doc 文档
<p> A selectable channel's registration with a selector is represented by a
* {@link SelectionKey} object. A selector maintains three sets of selection
* 三种SelectionKey的集合
* keys:
*
* <ul>
* <li>
* SelectionKey 标识的一些事件 与操作 是否链接等等 ,是否有数据 ,已链接, 链接断开 等都是事件
* SelectionKey 标识的一些事件的集合
* <p> The <i>key set</i> contains the keys representing the current
* channel registrations of this selector. This set is returned by the
* {@link #keys() keys} method. </p></li>
*
*
* 感兴趣的集合
* <li><p> The <i>selected-key set</i> is the set of keys such that each
* key's channel was detected to be ready for at least one of the operations
* identified in the key's interest set during a prior selection operation.
* This set is returned by the {@link #selectedKeys() selectedKeys} method.
* The selected-key set is always a subset of the key set. </p></li>
*
原来敢兴趣现在不感兴趣的集合 (不在关注某些动作)
* <li><p> The <i>cancelled-key</i> set is the set of keys that have been
* cancelled but whose channels have not yet been deregistered. This set is
* not directly accessible. The cancelled-key set is always a subset of the
* key set. </p></li>
所有的出初始化都是为空的
* <p> All three sets are empty in a newly-created selector.
*
* 我们可以通过SelectableChannel#register方法,注册一个channel 到时 一个Key被添加到上面的集合中
* <p> A key is added to a selector's key set as a side effect of registering a
* channel via the channel's {@link SelectableChannel#register(Selector,int)
* register} method.
Cancelled keys are removed from the key set during
* selection operations. The key set itself is not directly modifiable.
*
* selection operation 概念 :
*
* <p> A key is added to its selector's cancelled-key set when it is cancelled,
* whether by closing its channel or by invoking its {@link SelectionKey#cancel
* cancel} method. Cancelling a key will cause its channel to be deregistered
* during the next selection operation, at which time the key will removed from
* all of the selector's key sets.
*
* 移除的两中的方法 ,
* <a name="sks"></a><p> Keys are added to the selected-key set by selection
* operations. A key may be removed directly from the selected-key set by
* invoking the set's {@link java.util.Set#remove(java.lang.Object) remove}
* method or by invoking the {@link java.util.Iterator#remove() remove} method
* of an {@link java.util.Iterator iterator} obtained from the
* set. Keys are never removed from the selected-key set in any other way;
* they are not, in particular, removed as a side effect of selection
* operations. Keys may not be added directly to the selected-key set. </p>
*
*
<h2>Selection</h2>
* //重要的动作都是通过Selection 的动作来 执行的
* <p> During each selection operation, keys may be added to and removed from a
* selector's selected-key set and may be removed from its key and
* cancelled-key sets. Selection is performed by the {@link #select()}, {@link
* #select(long)}, and {@link #selectNow()} methods, and involves three steps:
* </p>
* 三个步骤
* <ol>
* 通道会被取消注册
* <li><p> Each key in the cancelled-key set is removed from each key set of
* which it is a member, and its channel is deregistered. This step leaves
* the cancelled-key set empty. </p></li>
* 查询底层操作系统
* <li><p> The underlying operating system is queried for an update as to the
* readiness of each remaining channel to perform any of the operations
* identified by its key's interest set as of the moment that the selection
* operation began. For a channel that is ready for at least one such
* operation, one of the following two actions is performed: </p>
*
NIO的核心概念 以及组件当中的关系 串联
NIO编程的示例 也就是一个线程处理多个请求
画个简单的脑图
代码 关键要看注释
** 服务器端 **
private static Map<String, SocketChannel> clientMap = new HashMap<>();
public static void main(String[] args) throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(8899));
//打开Selector
Selector selector = Selector.open();
//注册并关注 事件 关注连接事件 serverSocketChannel 注册到 selctor上面
//关注的事件是连接
//进行服务器的监听
//进行注册了
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
try {
//一旦监听到 上面的 OP_ACCEPT 就触发select() 方法 返回关注的数量
selector.select();
// 获取所有的SelectionKey 也就是所有事件的集合 可以获取到Channel对象
Set<SelectionKey> selectionKeys = selector.selectedKeys();
selectionKeys.forEach(selectionKey -> {
final SocketChannel client;
// 判断不同的事件
try {
if (selectionKey.isAcceptable()) {
//是不是 客户端向服务器端 发送 (因为前面注册的事件是ServerSocketChannel这里 转型)
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
//接收连接
client = server.accept();
client.configureBlocking(false);
//接收到REAd的请求 --> 到此为止 我们注册两个channel
client.register(selector, SelectionKey.OP_READ);
//建立好连接了 将客户端的信息 保存到服务器端里面
//定义一个Key
String key = "{" + UUID.randomUUID().toString() + "}";
clientMap.put(key, client);
//客户端注册完成
} else if (selectionKey.isReadable()) {
//判断是否数据来了 可读的也就是
//获取到与之关联的channel对象 之前注册了什么对象就取什么对象
client = (SocketChannel) selectionKey.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int count = client.read(readBuffer);
if (count > 0) {
readBuffer.flip();
//转字符集
Charset charset = Charset.forName("utf-8");
String receivedMessage = String.valueOf(charset.decode(readBuffer).array());
System.out.println(client + ": " + receivedMessage);
//获取到消息 分发给其他的客户端
//根据连接发送对象对应的key
String sendKey = null;
for (Map.Entry<String, SocketChannel> channelEntry : clientMap.entrySet()) {
if (client == channelEntry.getValue()) {
sendKey = channelEntry.getKey();
}
}
for (Map.Entry<String, SocketChannel> entry : clientMap.entrySet()) {
//发送到其他的对象
SocketChannel value = entry.getValue();
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
writeBuffer.put((sendKey + ": " + receivedMessage).getBytes());
writeBuffer.flip();
//写到其他的连接里面
value.write(writeBuffer);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
//返回客户端的
});
//每次处理完之后一定要清空
selectionKeys.clear();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
** 客户端 **
public static void main(String[] args) {
try {
//打开一个socketChannel对象
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
//监听 OP_CONNECT发起连接的事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
//去连接 连接建立
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8899));
while (true) {
//一旦返回事件 阻塞的方法 也就是说等待事件发生
selector.select();
Set<SelectionKey> keySet = selector.selectedKeys();
for (SelectionKey selectionKey : keySet) {
//已经建立好连接
if (selectionKey.isConnectable()) {
//获取到SocketChannel
SocketChannel client = (SocketChannel) selectionKey.channel();
//判断连接是否是 正在连接
if (client.isConnectionPending()) {
//完成连接 连接建立
client.finishConnect();
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
//获取到连接的时间
writeBuffer.put((LocalDateTime.now() + " 连接成功").getBytes());
//反转
writeBuffer.flip();
//写到里面
client.write(writeBuffer);
//建立双向的链接 已经与服务器端
ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
//等待输入的线程
executorService.submit(() -> {
//客户端不断的等待输入 所以是个死循环
while (true) {
try {
writeBuffer.clear();
InputStreamReader input = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(input);
String sendMessage = br.readLine();
writeBuffer.put(sendMessage.getBytes());
writeBuffer.flip();
//读出 信息 写回给服务端
client.write(writeBuffer);
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
}
//注册 服务器端向客户端发送数据的这个时间 把这个对象 注册到Selector上面
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
//进行读取
SocketChannel client = (SocketChannel) selectionKey.channel();
//读取到服务器端发过来的数据
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int count = client.read(readBuffer);
if (count > 0) {
//获取到服务器端发过来的数据
String receivedMessage = new String(readBuffer.array(), 0, count);
System.out.println(receivedMessage);
}
}
}
//清除处理完的SelectorKey
keySet.clear();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
总结
上面的那一套 就是NIO 编程的模型的基本步骤 也就是说写基本的原生 基本步骤都是这样的