简略剖析Socket编程实现流程及源码
public class Main {
public static void main(String[] args) {
try {
//1.创建服务端对象
ServerSocket socket = new ServerSocket(8080);
//获取Socket对象
Socket accept = socket.accept();//这是一个十分关键的方法,之后细作分析
System.out.println("是否关闭链接:"+socket.isClosed());
//获取当前请求的流对象
InputStream inputStream = accept.getInputStream();
System.out.println("是否关闭链接:"+socket.isClosed());
//用缓冲流包装输入流,选解码的字符集
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream,StandardCharsets.UTF_8));
//从流中获取信息
String s = bufferedReader.readLine();
socket.close();
System.out.println("是否关闭链接:"+socket.isClosed());
System.out.println(socket);
System.out.println(s);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
一段简单的可以实现接收客户端请求的服务的代码,我们从代码的角度去剖析实现原理。
ServerSocket socket = new ServerSocket(8080);//获取Socket服务对象
SocketServer对象的创建: ServerSocket类有多个不同的构造方法,其中参数最多的一个构造方法拥有三个参数。
1. port: 指定服务运行端口号
2. backlog: 最大请求数量
3. InetAddress: Socket IP网络地址
public ServerSocket(int port) throws IOException {
this(port, 50, null);//使用另一个有参构造器,默认网络地址为null,处理最大请求数目为50
}
这个构造方法做了三件事:
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);//重要的InetSocketAddress对象,下文会分析具体创建逻辑
} catch(SecurityException e) {
close();
throw e;
} catch(IOException e) {
close();
throw e;
}
}
1. 调用setImpl方法
private void setImpl() {
if (factory != null) {//提供了SocketFactory接口,可以使用静态工厂的方式创建SocketImpl类实例
impl = factory.createSocketImpl();
checkOldImpl();
} else {
//临时访问
impl = new SocksSocketImpl();//SocksSocketImpl是SocketImpl的子类
}
if (impl != null)
impl.setServerSocket(this);//将自身注入SocketImpl类中
}
2.判断端口的合理性
3.bind方法:绑定对应的主机地址
bind(new InetSocketAddress(bindAddr, port), backlog);//绑定InetSockAddress:包括 网络号(InetAddress:ipv4 ipv6) 主机号(host) 端口号(port)
//Params:SockerAddress,backlog
public void bind(SocketAddress endpoint, int backlog) throws IOException {
if (isClosed())//判断链接是否是被关闭的
throw new SocketException("Socket is closed");
if (!oldImpl && isBound())//判断链接是否已经被绑定
throw new SocketException("Already bound");
if (endpoint == null)//若未指定SocketAddress
endpoint = new InetSocketAddress(0);//默认创建一个InetSocketAddress对象(*3.1)
if (!(endpoint instanceof InetSocketAddress))//判断是否是InetSocketAddress实例,从而分辨是否是系统所支持的地址格式
throw new IllegalArgumentException("Unsupported address type");//不支持的地址类型
InetSocketAddress epoint = (InetSocketAddress) endpoint;
if (epoint.isUnresolved())
throw new SocketException("Unresolved address");
if (backlog < 1)
backlog = 50;
try {
SecurityManager security = System.getSecurityManager();//获取系统安全实例,个人觉得这个涉及
if (security != null)
security.checkListen(epoint.getPort());//检查该端口是否已经被使用,如果被占用会抛出异常
getImpl().bind(epoint.getAddress(), epoint.getPort());//如果没有为SocketImpl的实例绑定访问地址和访问的端口号
getImpl().listen(backlog);//设定监听的请求的数量
bound = true;//如果执行成功 且无异常发生就将 bound 标识改为 true
} catch(SecurityException e) {
bound = false;//否则绑定失败
throw e;
} catch(IOException e) {
bound = false;
throw e;
}
}
*(3.1):如何默认创建一个InetSocketAddress
//未指定网络地址时,会默认绑定本机
public InetSocketAddress(int port) {
this(InetAddress.anyLocalAddress(), port);//使用InetAddress类方法获取本机系统的本地地址 127.0.0.1
}
本文只是一个简单示例,其中提到的 InetSocketAddress SocketImpl InetAdress 等类,对于本地地址的获取来自System类中的anyLocalAddress方法,之后在文中再详细叙述