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实现"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章