上一篇文章中介绍了C语言的Socket编程,其主要目的是说明Socket实现是基于网络层IP协议和传输层TCP协议或UDP协议的,网络层、传输层的协议又是操作系统来实现的,所以在C语言中不同操作系统对应着不同的函数库调用,而Java语言具有平台无关性,也就是说不会因操作系统不同而依赖不同的类库,这篇文章将介绍基本的Socket编程及Java层面的实现原理。
一、Java语言Socket编程
服务端:
package edu.haye.socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class SocketServer {
private final static int PORT = 6666;//指定监听端口
public static void main(String[] args) {
try {
//创建ServerSocket对象实例
ServerSocket serverSocket = new ServerSocket(PORT);
//调用accept()方法进入监听状态
Socket socket = serverSocket.accept();
//获取输入流
InputStream is = socket.getInputStream();
//获取输出流
OutputStream os=socket.getOutputStream();
PrintWriter pw=new PrintWriter(os);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br=new BufferedReader(isr);
Scanner sc=new Scanner(System.in);
pw.println("连接成功!你先发言");
pw.flush();
while (true){
System.out.println("来自客户端的消息:"+br.readLine());
pw.println(sc.nextLine());
pw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端:
package edu.haye.socket;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class SocketClient {
private final static String HOST="127.0.0.1";
private final static int PORT=6666;
public static void main(String[] args) {
try {
//创建Socket实例对象
Socket socket = new Socket(HOST,PORT);
//获取输出流
OutputStream os = socket.getOutputStream();
//获取输入流
InputStream is=socket.getInputStream();
OutputStreamWriter osr=new OutputStreamWriter(os);
InputStreamReader isr=new InputStreamReader(is);
BufferedReader br=new BufferedReader(isr);
PrintWriter pw=new PrintWriter(osr);
Scanner sc=new Scanner(System.in);
while (true){
System.out.println("来自服务端的消息:"+br.readLine());
pw.println(sc.nextLine());
pw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
得益于Java面向对象的加持,Socket在Java上的编码相比于C更加简单易懂,服务端、客户端大致可简单归纳为以下几个步骤;
1、服务端:
1)创建SocketServer对象,构造器传入参数为监听端口号;
2)调用SocketServer对象的accpet()方法,获取Socket对象,此时程序进入阻塞状态直到链接出现;
3)通过Socket对象的getInputStream/getOutputStream方法获取输入/输出流;
4)依靠字节流进行数据的传输;
2、客户端:
1)创建Socket对象,构造器参数传入服务端IP及端口号;
2)通过Socket对象的getInputStream/getOutputStream方法获取输入/输出流;
3)依靠字节流进行数据的传输;
二、Java层面实现原理
以SocketServer对象的构建为例
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
setImpl();
if (port < 0 || port > 0xFFFF)
throw new IllegalArgumentException(
"Port value out of range: " + port);
if (backlog < 1)
backlog = 50;
try {
bind(new InetSocketAddress(bindAddr, port), backlog);
} catch(SecurityException e) {
close();
throw e;
} catch(IOException e) {
close();
throw e;
}
}
构造方法中除了对端口号范围的检查还调用了两个私有方法setImpl()以及bind(),从函数名中可以简单推断两个方法的作用,setImpl()用来实现实例,bind()用来绑定IP以及端口,我们重点看一下setImpl()方法:
setImpl()
private void setImpl() {
if (factory != null) {
impl = factory.createSocketImpl();
checkOldImpl();
} else {
// No need to do a checkOldImpl() here, we know it's an up to date
// SocketImpl!
impl = new SocksSocketImpl();
}
if (impl != null)
impl.setServerSocket(this);
}
方法首先判断了工厂实例是否存在,如果存在则使用工厂方法创建SocksSocketImpl实例对象,工厂可以通过ServerSocket类的静态方法setSocketFactory配置,且该配置方法仅可以调用一次,多次调用将抛出异常,我们的代码中并没有指定工厂对象,所以我们关注JDK提供的默认 SocksSocketImpl对象。
从上图中我们可以看到SocksSocketImpl是PlainScoketImpl的直接子类,而在PlainScoketImpl中定义了大量的native方法:
native void socketCreate(boolean isServer) throws IOException;
native void socketConnect(InetAddress address, int port, int timeout)
throws IOException;
native void socketBind(InetAddress address, int port)
throws IOException;
native void socketListen(int count) throws IOException;
native void socketAccept(SocketImpl s) throws IOException;
native int socketAvailable() throws IOException;
native void socketClose0(boolean useDeferredClose) throws IOException;
native void socketShutdown(int howto) throws IOException;
static native void initProto();
native void socketSetOption0(int cmd, boolean on, Object value)
throws SocketException;
native int socketGetOption(int opt, Object iaContainerObj) throws SocketException;
native void socketSendUrgentData(int data) throws IOException;
这些方法完成了Socket从创建到监听/链接、关闭的所有步骤,但是他们并非由Java代码实现,native调用将由JVM实现。JVM是由C/C++编写而成,前面已经说过了,C语言的Socket编程需要根据操作系统的不同而引入不同的函数库,所以JVM并不是通用的,不同的操作系统有着不同的JVM环境,他会根据不同的操作系统作出不同的实现,而暴露出相同的接口供Java代码调用。