Java Socket與Linux Socket底層調用分析

本文主要分析從Java Socket API到Linux Socket API的調用鏈,從而來探究Java Socket是如何利用Linux提供的系統調用來實現對應功能的。java

Java Socket API示例代碼

首先給出一個利用Java Socket API編寫的簡易的Hello/Hi代碼示例。linux

 //服務端
1
ServerSocket server = new ServerSocket(8000); 2 Socket client = server.accept(); 3 InputStream in = client.getInputStream(); 4 byte[] bytes = new byte[1024]; 5 int len = in.read(bytes); 6 String data = new String(bytes, 0 , len); 7 System.out.println("接收客戶端消息:" + data); 8 9 OutputStream out = client.getOutputStream(); 10 out.write("Hi".getBytes()); 11 client.close();
 //客戶端
1
Socket client = new Socket("localhost", 8000); 2 OutputStream out = client.getOutputStream(); 3 String msg = "Hello"; 4 out.write(msg.getBytes()); 5 6 InputStream in = client.getInputStream(); 7 byte[] bytes = new byte[1024]; 8 int len = in.read(bytes); 9 String s = new String(bytes, 0, len); 10 System.out.println("接收服務端響應信息:" + s); 11 client.close();

 

服務端建立及接收鏈接

Socket服務端經過調用ServerSocket構造函數來進行建立並調用accept來接收客戶端的鏈接請求。jvm

建立

經過源碼調試咱們能夠發現,在ServerSocket的構造函數中,調用了ServerSocket的bind方法。在bind方法中獲取了AbstractPlainSocketImpl對象並調用了該對象兩個重要的方法bind和listen。分別在AbstractPlainSocketImpl的bind和listen方法中,調用到了native方法socketBind和socketListen。爲了搞清楚這兩個native方法到底使用了哪些個Linux系統調用來實現功能,咱們繼續查閱了jdk中提供的native方法源碼,在native/java/net/PlainSocketImpl.c中能夠找到方法Java_java_net_PlainSocketImpl_socketBind和Java_java_net_PlainSocketImpl_socketListen。在方法Java_java_net_PlainSocketImpl_socketBind中能夠清楚看到調用了NET_Bind來進行綁定,而NET_Bind實如今native/java/net/net_util_md.c,最終經過系統調用bind來進行綁定。在方法Java_java_net_PlainSocketImpl_socketListen中也能夠看到調用了JVM_Listen來進行端口監聽,而JVM_Listen實如今jvm.c,簡單地調用了listen系統調用來進行監聽。下圖爲服務端建立過程的大體調用流程。socket

 

鏈接接收

serverSocket調用accept來進行等待接收鏈接。其內部調用鏈與建立相似,即在implAccept方法調用過程當中,獲取了AbstractPlainSocketImpl對象並調用了該對象的accept方法,在AbstractPlainSocketImpl的accept方法中,進一步調用了native方法socketAccept。在native/java/net/PlainSocketImpl.c中能夠找到該native方法的具體實現爲Java_java_net_PlainSocketImpl_socketAccept,其中進一步調用了linux_close.c中的NET_Accept方法,並最終調用了系統調用accept來進行等待接收鏈接。函數

 

客戶端建立

客戶端的建立與服務端的建立相比較爲簡單。在Socket的構造函數中,若是咱們指定了本地的InetAdress和localPort則會先調用bind方法來對指定端口進行綁定,具體bind過程與上述服務端bind過程相似。可是一般咱們並不會客戶端的本地地址進行指定,因此並不會執行bind過程,而是直接進行connect過程。在Socket的connect方法中,首先也會獲取AbstractPlainSocketImpl對象並調用該對象的connect,而後調用到native方法socketConnect。按照上述的jdk native分析過程,咱們能夠查到socketConnect的調用鏈(括號外爲方法,括號內爲文件),Java_java_net_PlainSocketImpl_socketConnect(PlainSocketImpl.c)->NET_Select(linux_close.c)->select(sys_call)spa

 

數據讀寫

無論是客戶端仍是服務端均可能會須要進行數據的讀寫,其實現方式是相同的。調試

數據讀

Java Socket直接經過獲取SocketInputStream,調用read方法來進行數據讀入。read方法進而調用了socketRead,在socketRead方法中又調用了native方法socketRead0。native方法socketRead0的實際實現爲native/java/net/SocketInputStream.c中的Java_java_net_SocketInputStream_socketRead0,該方法繼而又調用了linux_close.c中的NET_Read,最終調用了系統調用recv來進行數據讀入。code

數據寫

Java Socket直接經過獲取SocketOutputStream,調用write方法來進行數據寫入。write方法進而調用了socketWrite,在socketWrite方法中又調用了native方法socketWrite0。native方法socketWrite0的實際實現爲native/java/net/SocketOutputStream.c中的Java_java_net_SocketOutputStream_socketWrite0,該方法繼而又調用了linux_close.c中的NET_Send,最終調用了系統調用send來進行數據寫入。server

 

關閉

當通信雙方通信結束時,須要關閉socket。在Java代碼中能夠直接調用Socket的close來進行關閉。Socket的close方法會進一步調用AbstractPlainSocketImpl的close方法,繼而調用native方法socketClose0。可在jdk提供的native源碼中找到該native方法的調用鏈,Java_java_net_PlainSocketImpl_socketClose0(PlainSocketImpl.c)->NET_SocketClose(linux_close.c)->closefd(sys_call).對象

相關文章
相關標籤/搜索