网络IO过程包含许多层面的内容,如计算机组成、网络通信、操作系统、应用程序API等。
本次只讨论操作系统层面以上的网络IO基础。
从操作系统层面看网络IO
socket
socket,主要包含五个要素, 通信协议、客户端ip、客户端port、服务端 ip、服务端port;可以理解为应用层到传输层的一层抽象,操作系统就为应用程序提供了许多socket相关的系统调用,以方便应用程序进行网络通信。
可以通过netstat
命令查看网络连接
netstat -natp # 查看网络连接
复制代码
如果你进行过网络通信,如之前调用了curl www.baidu.com
,再查看网络连接,将看到socket的五个要素,如下图:
文件描述符
操作系统将socket连接映射成为 -> 文件描述符(file descriptor,简称fd),针对socket的读写转换为对fd的读写,进程的输入输出。说起来抽象,可以通过在linux下创建一个socket连接并通过fd读写更形象的理解:
# 与百度建立socket连接,并将其读写交给文件描述符8
exec 8<> /dev/tcp/www.baidu.com/80 # 8是文件描述符,<>代表输入输出流,由内核建立socket连接
复制代码
# 输出一段文本到上面的socket文件描述符,即发送tcp数据,在应用层向传输层发送了数据,socket是应用层到传输层的一个抽象
echo -e "GET / HTTP/1.1\n" 1>& 8
复制代码
# 输入从文件描述符8处来
cat 0<& 8
复制代码
执行完第三条命令后将得到百度首页的相应内容
服务端与客户端
如下图C1,C2,C3为三个客户端,Server为一个服务端,它们建立连接并进行数据读写的过程如下:
- Server启动,创建了一个socket,绑定地址,得到一个S-fd服务端文件描述符
- 客户端通过Server的socket地址,进行TCP三次握手连接,成功后,客户端生成一个代表socket连接的文件描述符(图中客户端的c1-fd、c2-fd、c3-fd)
- 服务端S-fd读写进行三次握手,连接成功后,在服务端生成一个代表客户端socket连接的文件描述符(图中Server的c1-fd、c2-fd、c3-fd)
- 客户端、服务端通过socket连接(即c1-fd、c2-fd、c3-fd的读写)进行发送接收数据
网络IO的阶段
从CPU工作的角度来看,网络IO的读取过程大概分为两个阶段
- 数据从网卡读取到内核缓冲区;(需要发起IO请求后等待数据就绪)
- 从内核缓冲区拷贝数据到用户空间
类似的,写入数据过程也可能因为图中标红的buffer memory
,内核socket缓冲区被占满而需要等待就绪。
综上所述,网络IO与本地文件IO的不同在于,其读写过程中可能需要一个等待的过程,这有助于理解后面编写网络IO程序的阻塞概念。
从Java网络IO程序到系统调用
操作系统为应用程序提供了一系列系统调用用于实现建立socket连接、读写数据等操作。具体的包括这几类:
socket # 创建socket
bind # 服务端绑定地址
listen # 监听
accept # 服务端接收客户端连接
recvfrom / read # 读取数据
复制代码
下面从几个Java应用程序,结合它们运行时所进行的系统调用理解网络IO的过程。
BIO
BIO,Blocking IO的简称,指的是阻塞IO,下面通过一个java程序的运行分析操作系统层面是如何实现一个BIO的Server的。
程序
// BIOServer.java
ServerSocket serverSocket = new ServerSocket(8081);
while (true){
// 接受连接,阻塞至有连接
final Socket socket = serverSocket.accept();
// new Thread(()->{
try {
InputStream inputStream = socket.getInputStream();
while (true){
byte[] bytes = new byte[1024];
// 从socket连接中读取数据,阻塞至有数据可读
if (inputStream.read(bytes) > 0){
System.out.println(String.format("got message: %s", new String(bytes)));
}
}
} catch (IOException e) {
e.printStackTrace();
}
// }).start();
}
复制代码
启动
上面是一个简单的Java BIO程序,我们可以在执行它时同时使用strace
命令查看程序运行时所进行的系统调用(Linux下)
strace -ff -o out java BIOServer # 查看进程的线程对内核进行了那些调用
复制代码
执行上面命令后,我们会得到几个out
为前缀的文件,这些文