Java Socket与Linux Socket底层调用分析

本文主要分析从Java Socket API到Linux Socket API的调用链,从而来探究Java Socket是如何利用Linux提供的系统调用来实现对应功能的。

Java Socket API示例代码

首先给出一个利用Java Socket API编写的简易的Hello/Hi代码示例。

 //服务端
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来接收客户端的连接请求。

创建

通过源码调试我们可以发现,在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系统调用来进行监听。下图为服务端创建过程的大致调用流程。

连接接收

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)

数据读写

不管是客户端还是服务端都可能会需要进行数据的读写,其实现方式是相同的。

数据读

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来进行数据读入。

数据写

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来进行数据写入。

关闭

当通讯双方通讯结束时,需要关闭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).

猜你喜欢

转载自www.cnblogs.com/fiveFish/p/12005960.html