java io分为BIO和NIO及NIO升级版AIO,这里主要介绍BIO及NIO的网络socket简单示例
首先区分概念(以下内容来自引用):
区分同步或异步(synchronous/asynchronous)。简单来说,同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系。
区分阻塞与非阻塞(blocking/non-blocking)。在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如 ServerSocket 新连接建立完毕,或数据读取、写入操作完成;而非阻塞则是不管 IO 操作是否结束,直接返回,相应操作在后台继续处理。
NIO主要概念(以下内容来自引用):
- Buffer,高效的数据容器,除了布尔类型,所有原始数据类型都有相应的 Buffer 实现。
- Channel,类似在 Linux 之类操作系统上看到的文件描述符,是 NIO 中被用来支持批量式 IO 操作的一种抽象。File 或者 Socket,通常被认为是比较高层次的抽象,而 Channel 则是更加操作系统底层的一种抽象,这也使得 NIO 得以充分利用现代操作系统底层机制,获得特定场景的性能优化,例如,DMA(Direct Memory Access)等。不同层次的抽象是相互关联的,我们可以通过 Socket 获取 Channel,反之亦然。
- Selector,是 NIO 实现多路复用的基础,它提供了一种高效的机制,可以检测到注册在 Selector 上的多个 Channel 中,是否有 Channel 处于就绪状态,进而实现了单线程对多 Channel 的高效管理。
bio实现socket交互演示
每来一个socket连接创建一个线程处理socket
class RequestHandler extends Thread{
private Socket socket;
public RequestHandler(Socket socket) {
super();
this.socket = socket;
}
@Override
public void run() {
try (PrintWriter printWriter = new PrintWriter(socket.getOutputStream())) {
printWriter.println("hello world!");
printWriter.println("this is server ");
printWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
bio 基本演示
*/
@Test
public void socket1() throws Exception{
Thread sThread = new Thread(new Runnable() {
@Override
public void run() {
try {
// serverSocket 绑定 ip port
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
// serverSocket 等待连接
Socket socket = serverSocket.accept();
//获取socket 后开启线程处理
new RequestHandler(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
// 服务器启动
sThread.start();
// Socket 客户端(接收信息并打印)
try (Socket cSocket = new Socket(InetAddress.getLocalHost(), 8888)) {
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(cSocket.getInputStream()));
//读取数据并打印
bufferedReader.lines().forEach(s -> System.out.println("客户端:" + s));
} catch (IOException e) {
e.printStackTrace();
}
}
bio增加线程池演示
池化线程提升效率
/*
bio 基本演示 + 连接池
*/
@Test
public void socket2() throws Exception{
Executor executor = Executors.newFixedThreadPool(8);
Thread sThread = new Thread(new Runnable() {
@Override
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
Socket socket = serverSocket.accept();
executor.execute(new RequestHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
sThread.start();
try (Socket cSocket = new Socket(InetAddress.getLocalHost(), 8888)) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
bufferedReader.lines().forEach(s -> System.out.println("客户端:" + s));
} catch (IOException e) {
e.printStackTrace();
}
}
nio实现socket交互演示
/*
nio socket 基本演示
*/
@Test
public void socket3() throws Exception{
Thread sThread = new Thread(new Runnable() {
@Override
public void run() {
// 创建 server 端
// 1.创建Selector
// 2.创建 ServerSocketChannel
// 3.绑定 ip port
// 4.设置为 非阻塞,注册到 Selector 中
// 5.selector.select() 等待 就绪的 channel
// 6. 有就绪的 channel 将它取出 并 accept 拿到 socketChannel 进行 交互
try (Selector selector = Selector.open();
ServerSocketChannel serverSocket = ServerSocketChannel.open();) {// 创建 Selector 和 Channel
serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));
//阻塞模式下,注册操作是不允许的
serverSocket.configureBlocking(false);
// 注册到 Selector,并说明关注点
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();// 阻塞等待就绪的 Channel,这是关键点之一
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
// 生产系统中一般会额外进行就绪状态检查
sendData((ServerSocketChannel) key.channel());
iter.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
sThread.start();
Thread.sleep(200);
//创建 client端
//1.创建 SocketChannel
//2.连接到 ip port
// 3.和服务端交互 read or write
// Socket 客户端(接收信息并打印)
try (SocketChannel cSocket = SocketChannel.open()) {
cSocket.connect(new InetSocketAddress(InetAddress.getLocalHost(),8888));
Message<String> objectMessage = receiveData(cSocket);
System.out.println("client receive:");
System.out.println(objectMessage.getClass());
System.out.println(objectMessage.getData().getClass());
System.out.println("client receive: " + new ObjectMapper().writeValueAsString(objectMessage));
} catch (IOException e) {
e.printStackTrace();
}
}
Message交互对象
class Message<T>{
private Integer code;
private T data;
private String message;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
工具方法toBytes/toObject(使用Jackson进行对象和数组的转换)
public <T> T toObject (byte[] bytes, Class<T> clazz) {
try {
return new ObjectMapper().readValue(bytes, clazz);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public byte[] toBytes(Object object){
try {
return new ObjectMapper().writeValueAsBytes(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
工具方法sendData/receiveData
private void sendData(ServerSocketChannel serverSocketChannel) throws IOException {
try (SocketChannel sSocket = serverSocketChannel.accept();) {
Message<String> stringMessage = new Message<>();
stringMessage.setCode(1);
stringMessage.setData("Hello World!");
stringMessage.setMessage("你好");
System.out.println("server:");
System.out.println("server send: "+ new ObjectMapper().writeValueAsString(stringMessage));
sSocket.write(ByteBuffer.wrap(toBytes(stringMessage)));
}
}
private <T> Message<T> receiveData(SocketChannel channel){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read=0;
try {
while((read = channel.read(buffer)) != -1){
buffer.flip();//将 limit = pos; pos = 0 目的是 让 get 方法 从 开头读取
byte[] bytes = new byte[read];
buffer.get(bytes);
baos.write(bytes);// 读到的 bytes 写入 目标数组
buffer.clear();//将 pos = 0; limit = capacity; 准备下次 读取 新数据
}
return toObject(baos.toByteArray(), Message.class);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
总结:
nio主要是三个对象buffer/channel/selector的操作
服务端创建要经历:1、创建selector 2、创建ServerSocketChannel并绑定到指定地址 3、向selector注册 4、等待准备就绪的channel
客户端连接要经历:1、创建SocketChannel 2、创建SocketChannel并绑定到指定地址 3、连接完成后就可以接收或发送数据