宝剑锋从磨砺出,梅花香自苦寒来。
Java BIO是传统的Java io编程,相关的类和接口在java.io包中
Java BIO:同步阻塞,一个连接为一个线程,连接一个客户端就需要启动一个线程进行处理,如果连接未断开且未做任何事,会造会不必要的开销。可以通过线程池优化。
Java BIO:适用于连接数目较小且相对固定的架构,对服务器的要求比较高,对并发有局限性。JDK1.4以前唯一的选择,简单易理解。
实例要求:
package com.crazy.io.bio;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author shiyi
* @version 1.0
* @date 2020-7-7 23:48
*/
public class BIOServer {
public static void main(String[] args) throws IOException {
/**
* 1.创建一个线程池
* 如果有客户端连接了,就创建一个线程与之通信。
*/
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
// 创建ServerSocket
ServerSocket serverSocket = new ServerSocket(6668);
System.out.println("服务器启动了");
while (true) {
// 监听,等待客户端连接
final Socket socket = serverSocket.accept();
System.out.println("连接到了一个客户端");
newCachedThreadPool.execute(new Runnable() {
@Override
public void run() {
// 可以和客户端通讯
handler(socket);
}
});
}
}
// 编写一个handler方法,与客户端通讯
public static void handler(Socket socket) {
// 通过socket获取输入流
try {
System.out.println("线程信息 id=" + Thread.currentThread().getId() + "线程名字=" + Thread.currentThread().getName());
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
// 循环的读取客户端发送的数据
while (true) {
System.out.println("线程信息 id=" + Thread.currentThread().getId() + "线程名字=" + Thread.currentThread().getName());
int read = inputStream.read(bytes);
if (read != -1) {
// 输出客户端发送的数据
System.out.println(new String(bytes, 0, read));
} else {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭和客户端的连接
try {
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
package com.crazy.io.zerocopy;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;
public class OldIOClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost", 6668);
String fileName = "1.txt";
InputStream inputStream = new FileInputStream(fileName);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
byte[] buffer = new byte[4096];
long readCount;
long total = 0;
long startTime = System.currentTimeMillis();
while ((readCount = inputStream.read(buffer)) >= 0) {
total += readCount;
dataOutputStream.write(buffer);
}
System.out.println("发送总字节数: " + total + ", 耗时: " + (System.currentTimeMillis() - startTime));
dataOutputStream.close();
socket.close();
inputStream.close();
}
}
有小伙伴说,我就只想写服务器,不想写客户端,能不能测试。能,我都给你们准备好了。可以使用windows的命令行telnet命令来测试。
telnet 127.0.0.1 6668
sned HelloWorld
查看服务端收到的消息
完成了简单的以BIO实现的客户端与服务器之间的交互。
缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer。
常用的ByteBuffer是一个抽象类,继承Buffer,实现了Comparable接口。
mark:标记。
Position:位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,为下次读写作准备。
Limit:表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的。
Capacity:容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变。
ByteBuffer中常用的方法:
// 缓冲区创建相关api
public static ByteBuffer allocateDirect(int capacity)//创建直接缓冲区
public static ByteBuffer allocate(int capacity)//设置缓冲区的初始容量
public static ByteBuffer wrap(byte[] array)//把一个数组放到缓冲区中使用
//构造初始化位置offset和上界length的缓冲区
public static ByteBuffer wrap(byte[] array,int offset, int length)
//缓存区存取相关API
public abstract byte get( );//从当前位置position上get,get之后,position会自动+1
public abstract byte get (int index);//从绝对位置get
public abstract ByteBuffer put (byte b);//从当前位置上添加,put之后,position会自动+1
public abstract ByteBuffer put (int index, byte b);//从绝对位置上put
实例要求:
public class NIOFileChannel03 {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("1.txt");
FileChannel inputStreamChannel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
FileChannel outputStreamChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
while (true) {
byteBuffer.clear();
int read = inputStreamChannel.read(byteBuffer);
System.out.println("read =" + read);
if (read == -1) {
break;
}
byteBuffer.flip();
outputStreamChannel.write(byteBuffer);
}
fileInputStream.close();
inputStreamChannel.close();
outputStreamChannel.close();
fileOutputStream.close();
}
}
package com.crazy.io.nio.buffer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* @author shiyi
* @version 1.0
* @date 2020-7-8 22:32
*/
public class NIOServer {
public static void main(String[] args) throws IOException {
// 创建serverSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 创建selector
Selector selector = Selector.open();
// 绑定一个端口6666.在服务器监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
// 设置为非阻塞
serverSocketChannel.configureBlocking(false);
// 把serverSocketChannel注册到selector 关心事件为OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 循环等待客户端连接
while (true) {
// 一秒没有事件发生,没有事件发生
if (selector.select(1000) == 0) {
System.out.println("等待了一秒,无连接");
continue;
}
// 如果返回的大于0,拿到selectionkey集合
// 如果大于0,表示已回去到关注的事件
// 通过selectionkeys反向获取通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 根据key对应的通道发生的事件做相应处理
// 有新的客户端来连接了
if (key.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
// 将socketChannel设置为非阻塞
socketChannel.configureBlocking(false);
System.out.println("客户端连接成功======");
// 注册到selector上, 关注事件为读,给socketChannel关联一个Buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (key.isReadable()) {
// 通过key 反向获取对应的channel
SocketChannel channel = (SocketChannel) key.channel();
// 获取到管理的Buffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
channel.read(buffer);
System.out.println("form客户端" + new String(buffer.array()));
}
keyIterator.remove();
}
}
}
}
package com.crazy.io.nio.buffer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @author shiyi
* @version 1.0
* @date 2020-7-8 23:00
*/
public class NIOClient {
public static void main(String[] args) throws IOException {
// 等到通道
SocketChannel socketChannel = SocketChannel.open();
// 设置非阻塞
socketChannel.configureBlocking(false);
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
// 连接服务器
if (!socketChannel.connect(inetSocketAddress)) {
while (!socketChannel.finishConnect()) {
System.out.println("没有连接上,可以做其它事情");
}
}
// 连接成功
String hello = "失忆老幺";
ByteBuffer buffer = ByteBuffer.wrap(hello.getBytes());
// 发送数据,将buffer数据写到channel
socketChannel.write(buffer);
System.in.read();
}
}
文章中有任何问题您可以在留言中指出。如果你喜欢这篇文章别忘了三连。微信公众号搜索失忆老幺,除了技术还有生活分享,快来关注吧。