Categories
程式開發

NIO看破也說破(一)—— Linux/IO基礎


Tips:Linux底層通過文件的方式實現IOJava等高級語言是通過syscall對Linux系統函數進行調用來實現網絡通信

知識準備

Linux中一切類型都被抽象成文件,如:普通文件、目錄、字符設備、塊設備、套接字等內存被劃分為內核態和用戶態,數據在用戶態和內核態之間拷貝,內核態可以訪問用戶態數據,反之不可以只有內核可以操作硬件資源(網卡、磁盤等),內核提供syscall函數

文件描述符

文件描述符是內核創建的方便管理已打開文件的索引,指代被打開的文件。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。

NIO看破也說破(一)—— Linux/IO基礎 1

所有執行I/O操作的系統調用都通過文件描述符

在Linux系統中,ssh方式登錄後查看/proc下信息,可以看到系統為每一個進程默認創建0,1,2 三個fd

NIO看破也說破(一)—— Linux/IO基礎 2

用戶態和內核態

內存被劃分為內核態和用戶態,數據在用戶態和內核態之間拷貝,內核態可以訪問用戶態數據,反之不可以用戶態無法直接訪問磁盤、網卡等設備,必須通過系統提供的syscall方式調用系統函數

NIO看破也說破(一)—— Linux/IO基礎 3

系統調用

我們來執行如下Java代碼,看看系統會發生什麼:

import java.io.IOException;
import java.net.ServerSocket;

public class BIOServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8080);
server.accept();
}
}

利用strace獲取系統函數調用棧:

strace -ff -o out java BIOServer

查看生成的文件,找到關鍵信息行:

socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 5
bind(5, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(5, 50)

這裡出現了3個系統函數,socket,bind,listen。我們分別查看Linux手冊:

socket

NAME
socket - create an endpoint for communication

DESCRIPTION
socket() creates an endpoint for communication and returns a descriptor.

RETURN VALUE
On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropri-
ately.

socket() 為通信提供一個終點並且返回一個文件描述符fd,否則返回 -1

bind

NAME
bind - bind a name to a socket

SYNOPSIS
#include /* See NOTES */
#include

int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

DESCRIPTION
When a socket is created with socket(2), it exists in a name space (address family) but has no address assigned to it.
bind() assigns the address specified by addr to the socket referred to by the file descriptor sockfd. addrlen speci-
fies the size, in bytes, of the address structure pointed to by addr. Traditionally, this operation is called "assign-
ing a name to a socket".

RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

bind(),接受三個參數(socket返回的文件描述符,socket地址結構體,socket地址長度),成功返回0

listen

NAME
listen - listen for connections on a socket

SYNOPSIS
#include /* See NOTES */
#include

int listen(int sockfd, int backlog);

DESCRIPTION
listen() marks the socket referred to by sockfd as a passive socket, that is, as a socket that will be used to accept
incoming connection requests using accept(2).

The sockfd argument is a file descriptor that refers to a socket of type SOCK_STREAM or SOCK_SEQPACKET.

The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a
connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED
or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connec-
tion succeeds.

RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

listen(),接受兩個參數(socket返回的文件描述符,接受socket隊列的大小),成功返回0,失敗返回-1

重新來看三個函數調用的關係,應該是:

NIO看破也說破(一)—— Linux/IO基礎 4

查看Java進程下的文件描述符:

NIO看破也說破(一)—— Linux/IO基礎 5

可以得出以下結論:

1、Java中通過對系統的調用來實現網絡IO

2、ServerSocket server = new ServerSocket(8080); 一行Java代碼的背後,經過了多個系統函數調用

3、實現網絡IO,不是Java的能力,是操作系統內核提供的能力

NIO看破也說破(一)—— Linux/IO基礎 6

備忘錄

Linux中都是文件描述符用戶空間的程序,通過調用系統函數來訪問操作系統軟硬件資源提供網絡IO能力的不是Java/Python高級語言而是Linux Kernel

下一節,不同JDK版本下的BIO實現