数据在网络传输过程中,因为数据报长度有限,所以数据会被拆分成多个包,在目的地拆包重新组装,这个过程可能会出现丢包,包乱序,包错误等传输问题,Socket封装了这些过程,将网络抽象为数据流,程序员利用Socket编程屏蔽了网络底层的细节
利用Socket实现主机之间的通信通常需要进行以下的操作
1.连接远程机器 2.发送数据 3.接收数据 4.关闭连接
如果是服务端还需要进行以下操作
5.绑定端口 6.监听入站数据 7.在绑定端口上接收来自远程机器的连接
例子
在这个例子中构造一个Socket,与time.nist.gov通信获取时间(美国时间)
try (Socket socket = new Socket("time.nist.gov", 13)) {
InputStream in = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in, "ASCII"));
//如果socket在15毫秒内没有响应会抛出ConnectExceiont
//如果一个有问题的服务器接收了请求但是没有主动关闭连接,该参数并不会起作用
socket.setSoTimeout(15000);
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
System.out.println(sb.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
在某些情况下,我们发完请求数据并不在使用输出流的时候我们可以选择关闭输出(输入)流
socket.shutdownOutput();
socket.shutdownInput();
这种选择性的关闭流我们称之为半关闭socket,半关闭socket只会释放socket流,并不会关闭socket,如果读取一个已经关闭的InputStream会返回-1,写入一个已经关闭的OutputStream会抛出IO异常
address
在Socket编程的时候常会遇到两个Address类,一个是InetAddress,另一个是InetSocketAddress,这两个address的区别在于
InetAddress是Java对IP地址的封装,表示互联网的IP地址
InetAddress对象的获取只能通过静态方法,比如根据主机名获取主机的IP地址封装对象
InetAddress address = InetAddress.getByName("www.baidu.com");
InetSocketAddress是SocketAddress的实现子类也是SocketAddress的唯一子类与InetAddress相比多了端口号,此类实现IP套接字地址(IP 地址 + 端口号)不依赖任何协议。一半用于Socket编程中
Socket使用InetSocketAddress作为连接参数
final Socket socket = new Socket();
SocketAddress address = new InetSocketAddress("www.fortify.net", 443);
try {
socket.connect(address);
} catch (IOException e) {
e.printStackTrace();
}
Proxy代理
在初始化socket的时候可以为socket设置代理,但是设置的代理是应用于系统中所有的socket,也可以为参数传入Proxy.NO_PROXY完全绕过所有的代理服务器而直接连接
SocketAddress proxyAddress = new InetSocketAddress("www.example.com", 1234);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, proxyAddress);
Socket socket = new Socket(proxy);
SocketAddress remote = new InetSocketAddress("www.baidu.com", 443);
socket.connect(remote);
上面设置代理的时候用到了Proxy.Type,Type有三种类型,分别是SOCKS,HTTP,DIRECT,其中SOCKS是java理解的唯一一种底层的代理协议,HTTP协议是应用层协议,以及一个表示无代理连接的DIRECT