本文主要分析从Java Socket API到Linux Socket API的调用链,从而来探究Java Socket是如何利用Linux提供的系统调用来实现对应功能的。java
首先给出一个利用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).对象