Internet上,数据按有限大小的包传输,这些包成为数据报(datagram),每个数据报包含一个首部(header)和一个有效载荷(payload)。首部包含包发送到的地址和端口、包来自的地址和端口、检测数据是否被破坏的校验和,以及用于保证可靠传输的各种其他管理信息。
有效载荷包含数据本身。
不过,由于数据报长度有限,通常必须将数据分解为多个包,再在目的地重新组合。也有可能一个包或多个包在传输中丢失或遭到破坏,需要重传。或者包乱序到达,需要重新排序。所有这些(将数据分解为包、生成首部、解析入站包的首部、跟踪哪些包已经收到而哪些没有收到等)是很繁重的工作,需要大量复杂的代码。
Socket帮你掩盖了这些底层细节,如错误检测、包大小、包分解、包重传、网络地址等。Socket允许程序员将网络连接看作是另外一个可以读写字节的流。
Socket是两台主机之间的一个连接,它可以完成7个基本操作:
1)连接远程主机
2)发送数据
3)接收数据
4)关闭连接
5)绑定端口
6)监听入站数据
7)在绑定端口上接受来自远程机器的连接
一旦建立了socket连接,就可以使用输入输出流,这个连接是全双工的(full-duplex),两台主机都可以同时发送和接收数据。
SMTP是服务器之间或邮件客户端与服务器之间传输电子邮件所用的协议。
半关闭Socket:close方法同时关闭Socket的输入流和输出流,如果只希望关闭连接的一半(输入/输出),调用shutdownInput或shutdownOutput方法即可。这两个方法并不关闭Scoket,实际上,它会调整与Socket连接的流,使它认为已经到了流的末尾。关闭输入之后,再读取输入流会返回-1,关闭输出流之后再写入Socket则会抛出一个IOException异常。
即使半关闭了连接,或者将连接的两半都关闭了,使用结束后还是需要关闭该Socket。shutdown只是影响了socket流,并不释放与socket关联的资源,如占用的端口等。
java.net.Socket是Java完成客户端TCP操作的基础类,它使用原生代码与主机操作系统的本地TCP栈进行通信。
public Socket(String host, int port) throws UnknownHostException, IOException
public Socket(InetAddress host, int port) throws IOException
这两个构造函数,在返回之前会与远程主机建立一个活动的网络连接。port在1~65535之间。
public Socket()
public Socket(Proxy proxy)
protected Socket(SocketImpl impl)
这三个函数可以创建未连接的Socket。
public Socket(String host, int port, InetAddress interface, int localPort)
throws IOException, UnknownHostException
public Socket(InetAddress host, int port, InetAddress interface, int localPort)
throws IOException
这两个构造函数可以用来指定从哪个接口和端口连接。
SocketAddress
SocketAddress表示一个连接端点,理论上可以用于TCP和非TCP socket。实际上只支持TCP/IP Socket。
SocketAddress主要是为了暂时的socket连接信息(如IP地址和端口)提供一个方便的存储,即使最初的socket已断开并被垃圾回收,这些信息也可以重用来创建新的Socket。
boolean connected = socket.isConnected() && ! socket.isClosed();
isConnected表示是否连接过一个远程主机,即使socket已经关闭,因而要判断socket是否打开着的,还要判断是否已经关闭了。
Socket选项
1)TCP_NODELAY
设置为true,可确保包会尽可能快地发送,而不论包的大小。
正常情况下,小数据包在发送前会组合为更大的包,在发送另一个包之前,本地主机要等待远程系统对前一个包的确认,这称为Nagle算法。
Nagle算法的问题是如果远程系统没有足够快地将确认发送回本地系统,那么依赖于小数据量信息稳定传输的应用程序会变慢。对于网络或游戏等计算机应用程序(服务器需要实时跟踪客户端鼠标的移动),这个问题尤为严重,在一个相当慢的网络中,即使简单地打字也会由于持续的缓冲而变得太慢。设置为true,可以关闭这种缓冲模式,这样素有包一旦就绪就会发送。
2)SO_LINGER
指定了Socket关闭时如何处理尚未发送的数据报,默认情况下,close方法将立即返回,但系统仍然会尝试发送剩余的数据,如果延迟时间设置为0,那么当Socket关闭时,所有未发送的数据包都将被丢弃,如果SO_LINGER打开而且延迟时间设置为任意正数,close方法会阻塞指定的时间,等待发送数据和接收确认。指定时间一过,Socket关闭,所有剩余的数据都不会发送,也不会接收确认。
返回-1表示该选项被禁用,会根据需要用更多的时间发送剩余的数据。
3)SO_TIMEOUT
正常情况下,尝试从Socket读取数据时,read()调用会阻塞尽可能长的时间来得到足够的字节。设置timeout确保这个调用会阻塞的时间不会超过指定的阈值,如果超出则抛异常,但是Socket仍然是连接的,可以再次尝试肚饿去这个Socket,下一次调用可能会成功。
0表示无限超时。
4)SO_RCVBUF和SO_SNDBUF
TCP使用缓冲区来提升网络性能,较大的缓冲区会提升快速连接(比如10M/s)的性能,而较慢的拨号连接利用较小的缓冲区会有更好地表现。
一般来讲,传输连续的大数据块时(在ftp和http中较为常见),可以从大缓冲区收益,而对于交互式会话的小数据量传输(比如telnet和很多游戏),大缓冲区则没有多大帮助。如今128K字节已经是一个常见的默认设置。
可以达到的最大带宽=缓冲区大小/延迟。例如,xp上,假设两个主机之间的延迟为500ms,xp上的缓冲区大小为17520字节,则带宽=17520/0.5=273.75kb/s。这是Socket的最大速度,而不论网络速度有多快。对于一个拨号连接来说,这样的速度已经很快了。
可以通过减少延迟来提升速度,不过,延迟与网络硬件有关,另外还取决于你的应用控制之外的其他一些因素。
如果把缓冲区从17520字节提升到128K,则最大带宽会增加到2Mb/s,如果加到256K时,最大带宽会增大到4Mb/s。
当然网络本身对最大带宽也是有限制的,如果将缓冲区设置的过高,程序会试图以过高的速度发送和接收数据,而网络来不及处理,这就会导致拥塞、丢包和性能下降。因此,要得到最大带宽,需要让缓冲区大小与连接的延迟匹配,是它稍小于网络的带宽。
可以用ping去检测主机的延迟。
SO_RCVBUF控制用于网络输入的建议的接收缓冲区的大小,虽然可以独立地设置接收和发送缓冲区的大小,但是实际上缓冲区通常会设置为二者中较小的一个。
Linux系统通常指定一个最大缓冲区大小,一般是64KB或256KB,而且不允许任何socket有更大的缓冲区。
一般情况下,如果你发送你的应用不能充分利用可用带宽(例如,你有一个25Mb/s的Internet连接,但是数据传输速率仅为1.5Mb/s),那么可以试着增加缓冲区的大小;相反,如果存在丢包和拥塞现象,则要减少缓冲区大小。
不过,大部分情况,除非网络在某个方向上负载过大,否则默认值就很合适。具体来说,现代操作系统使用TCP窗口缩放来动态调整缓冲区的大小,以适应网络。
一般经验是,除非你检测到某个问题,否则不要进行调整。一般调整操作系统的最大缓冲区比在Java里头调整单个socket的缓冲区大小效益要高。
5)SO_KEEPALIVE
如果打开这个,客户端偶尔会通过一个空闲连接发送一个数据包(一般两小时一次),以确保服务器没有崩溃。如果服务器没能响应这个包,客户端会持续尝试11分钟多的时间,直到接收到响应为止。如果在12分钟内未收到响应,则客户端就关闭socket。如果不打开这个,不活动的客户端可能会永远存在下去,而不会注意到服务器是否已经崩溃。
6)OOBINLINE
TCP包括一个可以发送单字节带外(Out Of Band,OOB)紧急数据的特性。这个数据会立即发送,此外,当接收方收到紧急数据时会得到通知,在处理其他已收到的数据之前可以选择先处理这个紧急数据(必要时flush当前缓冲区)。
Java里对应的方法是sendUrgentData
默认情况下,Java会忽略从Socket接收的紧急数据,如果希望接收到正常数据中的紧急数据,需要setOOBInline为true。一旦开启,到达的任何紧急数据将以正常方式放在Socket的输入流中等待读取。
7)SO_REUSEADDR
一个Socket关闭时,可能不会立即释放本地端口,尤其是当Socket关闭时若仍有一个打开的连接,就不会释放本地端口,有时会等待一小段时间,确保接收到所有要发送到这个端口的延迟数据包,Socket关闭时这些数据包可能仍在网络上传输,系统不会对接收到的延迟包做任何处理,只是希望确保这些数据不会意外地传入绑定到同一端口的新进程。
如果使用随机端口,则问题不大,但是如果Socket绑定到一个已知的端口,可能会有问题,因为这会阻止所有其他Socket同时使用这个端口,如果开启这个参数(默认是关闭),则允许另外一个Socket绑定到这个端口,即使此时仍有可能存在前一个Socket未接收的数据。
setReuseAddress必须在绑定新Socket之前调用。只有之前连接的Socket和重用老地址的新Scoket的这个值都设置为true,才能生效。
8)IP_TOS
不同类型的Internet服务对性能有不同的需求,比如视频要求相对较高的带宽和较短的延迟,而email可以通过较低带宽的连接传递等。
服务类型存储在IP首部中的一个名为IP_TOS的8位字段中。在Java中使用setTrafficClass来设定,java里头是0-255,但是TCP首部要求是8位,因而只能使用int的低字节。
Socket异常
1)BindException,端口被占用或没有权限使用该端口
2)ConnectException,连接远程主机被拒绝(远程主机忙或者没有进程监听该端口)
3)NoRouteToHostException,连接超时
4)ProtocolException,违反TCP/IP规定
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。