01
—
分析 BIO 过程
Socket 是什么呢,其实就是一个 有 IP地址+端口,并且持有文件描述符的对象,操作系统通过 ip + port 建立 TCP 连接;通过文件描述符来读取传输的数据。无论操作系统内核,还是JDK,都会定义一个Socket的数据结构。
而通常网络连接,是一个客户端\服务端模型,在 Java世界里,ServerSocket 用来实现服务端接收网络请求,Socket 在客户端用于实现网络连接建立,并发送\接收传输数据,在服务端用于承接每一个连接请求。抽象一点理解,就像两个Socket在实际通信。
ServerSocket 和 Socket 具体行为的实现为 SocketImpl,SocketImpl 封装了服务端bind、listen等内核操作函数,用来建立Socket监听对象;也封装了客户端connect操作函数,用来与服务端创建连接,当然还包括输入、输出的数据流对象;
Server 端
当 socket 调用 accept 时候,实际上会调用操作系统的 accept函数,而 当前 ServerSocket 会阻塞等待,直到操作系统 将 一个客户端连接 封装成 Socket 返回给 ServerSocket;服务端拿到一个Socket对象才跳出阻塞,去执行具体的Socket响应逻辑(即,Handler),通常我们会使用一个线程池去执行它。
注:其实 对于每一个 网络进程 来说,操作系统会用很多 文件描述符(对应的内核地址),用来操作系统读取数据,通常进程的文件描述符 是有上限的(大概 是 1024)。
public class BIOServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端已经启动!");
ExecutorService executor = Executors.newFixedThreadPool(10);
boolean flag = true;
while (flag) {
Socket accept = serverSocket.accept();
executor.execute(new BIOHandler(accept));
}
executor.shutdown();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class BIOHandler implements Runnable {
Socket socket;
BIOHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream inputStream = socket.getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
byte[] buffer = new byte[1024];
int len = 0;
while ((len = bufferedInputStream.read(buffer)) != -1) {
String s = new String(buffer, 0, len);
System.out.println(s);
}
inputStream.close();
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client 端
根据 ip + port,去与目标地址创建连接,经过三次握手,成功创建连接,就可以通过输入、输出流与服务端进行数据交互;
public class BIOClient {
private static final BufferedReader KEYBOARD_INPUT = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 9999);
boolean flag = true;
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
while (flag) {
String str = KEYBOARD_INPUT.readLine();
bufferedOutputStream.write(str.getBytes());
bufferedOutputStream.flush();
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
02
—
分析 NIO 过程
Channel 持有 Socket、fd、SelectionKeys[] ,ServerSocketChannel 和 SocketChannel,不能直接读取数据,需要使用 Buffer 缓冲区技术来获取数据,当然缓冲区可以有很多种,ByteBuffer、或者内存映射(MappedByteBuffer),就是用户线程与内核共用操作系统的内核缓冲区,这样程序可以不拷贝内核数据到用户空间,而直接操作数据,提升效率;
套接字通道设置为阻塞,当调用 channel 的 accept时候,会直接返回结果,而不是阻塞等待对应事件的到达;
channel需要将 selector 注册,并且绑定接收就绪事件;
所有的Channel 需要由 Selector 管理,当由连接就行,可读、可写等IO事件时候,可以通过Selector.select方法检测到,然后通过遍历Selector上的所有就绪事件,进行相应事件的IO操作;
对于 Selector.select 方法,不同操作系统的实现不一样,select、poll和epoll等;
https://blog.csdn.net/u011990285/article/details/88125071?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
https://segmentfault.com/a/1190000003063859/
https://blog.csdn.net/daaikuaichuan/article/details/83475809
sendfile“零拷贝”和mmap内存映射