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

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"Tips:"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"Linux底層通過文件的方式實現IO"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"Java等高級語言是通過syscall對Linux系統函數進行調用來實現網絡通信"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"知識準備"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Linux中一切類型都被抽象成文件,如:普通文件、目錄、字符設備、塊設備、套接字等"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"內存被劃分爲內核態和用戶態,數據在用戶態和內核態之間拷貝,內核態可以訪問用戶態數據,反之不可以"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"只有內核可以操作硬件資源(網卡、磁盤等),內核提供syscall函數"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"文件描述符"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"文件描述符是內核創建的方便管理已打開文件的索引,指代被打開的文件。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ec/eccb4aabfae092056f856a5b4365e049.png","alt":null,"title":"文件描述符映射關係","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"https://zh.wikipedia.org/wiki/%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6","fromPaste":false,"pastePass":false}},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"所有執行I/O操作的系統調用都通過文件描述符"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Linux系統中,ssh方式登錄後查看/proc下信息,可以看到系統爲每一個進程默認創建0,1,2 三個fd"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/63/630a705a3251efc66b468b179a88d241.jpeg","alt":null,"title":"當前進程打開的文件描述符","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"用戶態和內核態"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"內存被劃分爲內核態和用戶態,數據在用戶態和內核態之間拷貝,內核態可以訪問用戶態數據,反之不可以"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"用戶態無法直接訪問磁盤、網卡等設備,必須通過系統提供的syscall方式調用系統函數"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fd/fd79a5af8b98f0439492dd3fbe328d01.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"系統調用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們來執行如下Java代碼,看看系統會發生什麼:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import java.io.IOException;\nimport java.net.ServerSocket;\n\npublic class BIOServer {\n public static void main(String[] args) throws IOException {\n ServerSocket server = new ServerSocket(8080);\n server.accept();\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"利用strace獲取系統函數調用棧:"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"strace -ff -o out java BIOServer"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"查看生成的文件,找到關鍵信息行:"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 5\nbind(5, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr(\"0.0.0.0\")}, 16) = 0\nlisten(5, 50)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏出現了3個系統函數,socket,bind,listen。我們分別查看Linux手冊:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"socket"}]}]}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"NAME\n socket - create an endpoint for communication\n \nDESCRIPTION\n socket() creates an endpoint for communication and returns a descriptor.\n\nRETURN VALUE\n On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropri-\n ately."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"socket() 爲通信提供一個終點並且返回一個"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"文件描述符fd"},{"type":"text","text":",否則返回 -1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"bind"}]}]}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"NAME\n bind - bind a name to a socket\n \nSYNOPSIS\n #include /* See NOTES */\n #include \n\n int bind(int sockfd, const struct sockaddr *addr,\n socklen_t addrlen);\n\nDESCRIPTION\n When a socket is created with socket(2), it exists in a name space (address family) but has no address assigned to it.\n bind() assigns the address specified by addr to the socket referred to by the file descriptor sockfd. addrlen speci-\n fies the size, in bytes, of the address structure pointed to by addr. Traditionally, this operation is called \"assign-\n ing a name to a socket\".\n \nRETURN VALUE\n On success, zero is returned. On error, -1 is returned, and errno is set appropriately."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"bind(),接受三個參數("},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"socket返回的文件描述符"},{"type":"text","text":",socket地址結構體,socket地址長度),成功返回0"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"listen"}]}]}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"NAME\n listen - listen for connections on a socket\n\nSYNOPSIS\n #include /* See NOTES */\n #include \n\n int listen(int sockfd, int backlog);\n \nDESCRIPTION\n listen() marks the socket referred to by sockfd as a passive socket, that is, as a socket that will be used to accept\n incoming connection requests using accept(2).\n\n The sockfd argument is a file descriptor that refers to a socket of type SOCK_STREAM or SOCK_SEQPACKET.\n\n The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a\n connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED\n or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connec-\n tion succeeds.\n\nRETURN VALUE\n On success, zero is returned. On error, -1 is returned, and errno is set appropriately."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"listen(),接受兩個參數("},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"socket返回的文件描述符"},{"type":"text","text":",接受socket隊列的大小),成功返回0,失敗返回-1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重新來看三個函數調用的關係,應該是:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d3/d386f32bde57adaa5df29d75fcc2091c.png","alt":null,"title":"系統函數調用關係","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"查看Java進程下的文件描述符:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a8/a8c6002e979c9b1959ac03d364dc370c.png","alt":null,"title":"5號fd指向一個socket","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以得出以下結論:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、Java中通過對系統的調用來實現網絡IO"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、ServerSocket server = new ServerSocket(8080); 一行Java代碼的背後,經過了多個系統函數調用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"實現網絡IO,不是Java的能力,是操作系統內核提供的能力"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ac/ac3de7f43f45372c7279227931aee986.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"備忘錄"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}},{"type":"strong"}],"text":"Linux中都是文件描述符"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}},{"type":"strong"}],"text":"用戶空間的程序,通過調用系統函數來訪問操作系統軟硬件資源"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}},{"type":"strong"}],"text":"提供網絡IO能力的不是Java/Python高級語言而是Linux Kernel"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"下一節,不同JDK版本下的BIO實現"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章