Socket

2019-01-05  本文已影响0人  Samuel_Tom

Socket的作用

在Internet上,数据按有限大小的包传输,这些包称为数据报。每个数据报包含一个首部和主体。首部包含目标地址和端口、源地址和端口、检测数据是否被破坏和校验、以及用于保证可靠传输的各种信息;主体即数据本身。由于数据报长度有限,通常必须将数据分解为多个包,再在目的地重新组合,也有可能一个包或多个包在传输过程中丢失或遭到破坏,需要重传;或者包乱序到达需要重新排序。所有这些工作Socket都为我们屏蔽了,掩盖了网络的底层细节,如错误检测、包大小、包分解、包重传、网络地址等。

客户端Socket

Socket相关API

  • Socket构造函数指定要连接的主机和端口:主机可以指定为InetAddress或String;端口指定为1~65535之间的int值。
  • 这些构造函数会创建连接的Socket,也就是说在构造函数返回之前,会与远程主机建立一个活动的网络连接。所以这个构造函数不只是创建Socket对象,还会尝试连接远程主机的Socket。如果出于某种原因未能打开连接,则会抛出异常。如果域名服务器无法解析这个主机名或域名服务器没有运行,则会抛出UnknownHostException;如果出于其它原因未能打开Socket,则会抛出IOException。

这些构造函数会创建未连接的Socket,对于底层Socket的行为提供了更多的控制,例如可以选择一个不同的代理服务器或者一个加密机制。

这些构造函数可以指定要连接的远程主机和端口,以及从哪个本地接口和端口连接的。前两个参数表示远程主机和端口号,后两个参数表示本地主机(本地网络接口)和端口号。本地网络接口可以是物理接口(一个以太网卡),也可以是虚拟接口(一个有多个IP地址的主机)。如果localPort参数传入0,Java会随机选择1024~65535之间的可用端口。

通常我们在创建一个Socket对象的同时都会打开与一个远程主机的连接,但是我们可能需要在进行网络连接之前设置Socket。所以当使用无参的构造函数时,就没有指定的主机可以连接,然后通过构造一个SocketAddress设置远程主机,最后调用connect()方法实现网络连接。

try {
       Socket socket = new Socket();
       // 这里可以配置Socket选项
       SocketAddress address = new InetSocketAddress("www.baidu.com", 80);
       socket.connect(address);
       // 这里可以开始使用Socket
} catch (IOException e) {
       e.printStackTrace();
}
  • 前两个方法获取Socket连接到的远程主机和端口号,或者如果现在连接是关闭的,则获取到的是Socket最近一次连接的远程主机和端口号。后两个方法获取Socket连接的源主机地址和端口号。
  • 远程端口(相对于客户端Socket而言)通常是由一个标准委员会预先分配的“已知端口”,而本地端口和远程端口不同,通常是由系统在运行时,从未使用的空闲端口号中选择随机分配的,因此系统中的多个不同的客户端就可以同时访问相同的服务。

public boolean isConnected()
public boolean isClosed()
public boolean isBound()

  • isConnected():并不指示Socket当前是否连接到一个远程主机,实际上表示Socket是否从未连接过一个远程主机;如果这个Socket确实连接过一个远程主机,不管出于连接状态还是关闭状态,都返回true。
  • isClosed():Socket关闭则返回true,否则返回false;如果Socket从一开始从未连接也返回false。
  • isBound():指出Socket是否成功绑定到本地系统上的出站端口,注意是本地客户端。
// 查看当前一个Socket是否打开处于连接状态
boolean connected = socket.isConnected() && !socket.isClosed();

SocketAddress

SocketAddress类的主要用途是为暂时的Socket连接信息(IP地址和端口号)提供一个存储结构,即使最初的Socket已经断开并被垃圾回收,这些信息也可以重新用来创建Socket。其具体实现类为InetSocketAddress。

对于客户端来说:使用一个主机和端口号来创建InetSocketAddress
对于服务端来说:使用一个端口号来创建InetSocketAddress

设置代理服务器

public Socket(Proxy proxy)
public Proxy(Type type, SocketAddress sa)

  • Proxy.Type.SOCKS:是Java理解的唯一一种底层代理类型
  • Proxy.Type.HTTP:作用于应用层而不是传输层
  • Proxy.Type.DIRECT:表示无代理连接
// 创建代理服务器的SocketAddress
SocketAddress proxyAddress = new InetSocketAddress("myproxy.example.com", 1080);
Proxy proxy = new Proxy(Proxy.Type.DIRECT, proxyAddress);
// 构造代理服务器的Socket
Socket socket = new Socket(proxy);
// 创建远程主机的SocketAddress
SocketAddress remoteAddress = new InetSocketAddress("login.ibiblio.org", 25);
socket.connect(remoteAddress);

设置Socket选项

Socket选项指定了Socket类所依赖的原生Socket如何发送和接受数据,对于客户端Socket,Java支持9个选项:

  • TCP_NODELAY
  • SO_LINGER
  • SO_TIMEOUT
  • SO_RCVBUF
  • SO_SNDBUF
  • SO_KEEPALIVE
  • OOBINLINE
  • SO_REUSEADDR
  • IP_TOS
  1. TCP_NODELAY
    public void setTcpNoDelay(boolean on) throws SocketException

设置为true可确保包会尽可能快地发送而不管包的大小。如果这个属性为flase,在正常情况下,小数据包(一字节)在发送前会组合为更大的包,在发送下一个包之前,本地客户端需要等待远程服务器对前一个包的确认响应信息,即Nagle算法,这个算法的问题是,如果远程服务器没有足够快地将确认响应信息发送回本地客户端,那么依赖于小数据量信息稳定传输的应用程序会变得很慢。例如在游戏应用程序中,服务器需要实时跟踪客户端鼠标的移动,Nagle算法会使得响应变慢导致移动卡顿。所以设置为true可以关闭这种缓冲模式,这样所有包一旦就绪就会立即发送。

  1. SO_LINGER:
    public void setSoLinger(boolean on, int linger) throws SocketException

该属性指定了Socket关闭时如何处理未发送的数据报。默认情况下,调用close()方法将立即返回,但系统仍会尝试发送剩余的数据。如果设置为false且延迟时间设置为0,那么当Socket关闭时,所有未发送的数据包都将被丢弃;如果设置为true且延迟时间设置为任意正数,close()方法会阻塞(阻塞时间为指定的秒数),等待发送数据和接受确认,当阻塞时间结束时,Socket将会关闭并且所有剩余数据都不会发送,也不会收到确认。

  1. SO_TIMEOUT
    public synchronized void setSoTimeout(int timeout) throws SocketException

正常情况下,尝试从Socket读取数据时,调用read()方法会阻塞尽可能长的时间来得到足够的字节。设置SO_TIMEOUT可以确保阻塞的时间不会超过我们指定的时间,当时间到期时就会抛出一个InterruptedException异常,应该捕获这个异常。不过虽然抛出了异常,但Socket仍是连接的,虽然这个read()方法调用失败了,但还可以再次尝试读取该Socket。

  1. SO_RCVBUF和SO_SNDBUF
    public synchronized void setReceiveBufferSize(int size) throws SocketException
    public synchronized void setSendBufferSize(int size) throws SocketException

一般来讲,如果发现应用不能充分利用可用带宽(例如有一个25Mbs/s的网络连接,但是数据传输速率仅为1.5Mbs/s),那么可以试着增加缓冲区大小;相反如果存在丢包和拥塞现象,则要减少缓冲区大小;不过在大多数情况下,除非网络在某个方向上负载过大,否则就直接使用默认值就行。

  1. SO_KEEPALIVE
    public void setKeepAlive(boolean on) throws SocketException

如果设置该属性为true,则客户端偶尔会通过一个空闲连接发送一个数据包以确保服务器未奔溃。如果服务器没能响应这个包,客户端会持续尝试11分钟多的时间,直到接收到响应为止,如果在12分钟内没接收到响应,客户端就关闭Socket。如果该属性设置为false,不活动的客户端可能会一直处于打开状态,而不会注意到服务器已经奔溃。

  1. OOBINLINE
    public void sendUrgentData (int data) throws IOException

TCP有一个可以发送单字节的紧急数据的特性。这个数据会立即发送,而且当接收方收到紧急数据时,会优先选择处理这个紧急数据。

public void setOOBInline(boolean on) throws SocketException

默认情况下,Java会忽略从Socket接收的紧急数据,不过如果你希望接收正常数据中的紧急数据,就需要设置该属性为true。一旦打开OOBInline,到达的任何紧急数据就将以正常方式放在Socket的输入流中等待读取。

  1. SO_REUSEADDR
    public void setReuseAddress(boolean on) throws SocketException

默认情况下,当Socket关闭时可能不会马上释放本地端口,有时会等待一小段时间,确保接收到所有要发送到这个端口的延迟数据包,所以当Socket关闭时这些数据包可能仍在网络上传输。如果使用随机端口,则为题不大,如果绑定到已知端口,就会阻止所有其它Socket同时使用这个端口。此时设置SO_REUSEADDR为true,就允许另一个Socket绑定到这个端口,即使此时仍有可能存在前一个Socket未接收到的数据。
使用时setReuseAddress()方法必须在为这个端口绑定新的Socket之前调用,之前连接的Socket和重用老地址的新Socket都必须设置为true才能生效。

  1. IP_TOS
    public void setTrafficClass(int tc) throws SocketException

该属性用来定性描述网络服务质量,IP规定了4种服务类型:

  • 低成本:发送成本低
  • 高可靠性:保证把数据可靠的送到目的地
  • 最高吞吐量:一次可以接收或者发送大批量的数据
  • 最小延迟:传输数据的速度快,把数据快速送达目的地

public void setPerformancePreferences(int connectionTime,int latency, int bandwidth)

该方法也可以设置网络服务质量,为连接时间、延迟、带宽指定相对优先级。例如:connectionTime=2,latency=1,bandwidth=3,那么最大带宽bandwidth是最重要的特性,最小延迟latency最不重要,连接时间居中。

服务端Socket

SercerSocket相关API

  • 这些构造方法可以指定端口、保存入站连接请求所用的队列长度、要绑定的本地网络接口。如果试图将队列长度设置大于操作系统的最大队列长度,则会使用最大队列长度。
  • 默认情况下,如果一个主机有多个IP地址,服务器Socket会在所有IP地址的指定端口上监听,不过第三个参数可以要求只绑定某一个本地IP地址。
  • 如果传入的端口为0,则系统会随机选择可用的端口,这样的端口成为匿名端口。
  • 创建ServerSocket时如果抛出一个IOException异常,往往说明两点:一是可能有另一个服务器Socket已经占用了请求的端口;二是你试图连接1~1023范围内的某个端口,但没有root权限。

无参构造函数会创建一个ServerSocket对象,但未将它具体绑定到某个端口,所以不能接受任何网络连接,需要使用bind()方法进行绑定。这个特性的主要用途是,允许程序在绑定端口之前设置服务器Socket的属性,因为有些属性必须在服务器Socket连接前进行设置。

ServerSocket serverSocket = new ServerSocket();
// 这里可以设置服务器Socket属性...
SocketAddress address = new InetSocketAddress(80);
serverSocket.bind(address);

getInetAddress():返回服务器的地址,如果一个主机有多个IP地址,则随机返回其中一个地址,如果没有绑定网络接口则返回null。
getLocalPort():返回服务器监听的端口号,如果没有绑定端口则返回-1。

设置Socket选项

Socket选项指定了ServerSocket类所依赖的原生Socket如何发送和接受数据,对于服务端Socket,Java支持3个选项:

  • SO_TIMEOUT
  • SO_REUSEADDR
  • SO_RCVBUF
  • 性能首选项
  1. SO_TIMEOUT
    public synchronized void setSoTimeout(int timeout) throws SocketException

该属性指定了accept()阻塞的时间,如果达到了指定的超时时间则会抛出SocketTimeoutException异常。如果设置为0则表示accept()方法永远不会超时,默认值就是0。一般很少设置这个TIMEOUT值,大多数服务器都设计为运行无限长的时间。

  1. SO_REUSEADDR
    public void setReuseAddress(boolean on) throws SocketException

该属性与客户端的SO_REUSEADDR非常类似,确定了是否允许一个新的Socket绑定到之前使用过的一个端口,即使可能还有一些发送到原Socket的数据正在网络上传输。

  1. SO_RCVBUF
    public synchronized void setReceiveBufferSize (int size) throws SocketException

该属性设置了服务器Socket接收的缓存区大小

  1. 设置性能首选项
    public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)

不同的Internet服务有不同的性能需求,例如体育运动的直播视频需要相对较高的带宽;电影可能仍需要高带宽,但是可以接收较大的延迟;电子邮件可以通过低带宽的连接传输,甚至延迟几个小时都允许。为TCP定义了4个通用业务流类型:

  • 低成本
  • 高可靠性
  • 最大吞吐量
  • 最小延迟
上一篇下一篇

猜你喜欢

热点阅读