二. Socket用法

C/S通信架构中,客户端要主动与服务端建立连接,这个链接就是Socket套接字.服务端收到连接请求后,也会开启Socket记录与客户端的链接.C/S两端都要建路Socket才能正常收发数据.

一.构造Socket

(1)new Socket ( )
(2)new Socket ( InetAddress addres,int port )
(3)new Socket ( String hostnamme,int port )
以上3种创建socket的构造函数,除第一种外,都要试图简历与服务器的连接,连接成功返回Socket对象,连接失败,抛出IOException

/** 扫描主机1-1024之间的端口,判断这些端口上是否有程序进行监听
	 *  PortScanner与1-1024端口建立Socket对象,建立成功说明该端口上有程序进行监听
	 */
	public class PortScanner {
		public static void main(String[] args) {
			Socket socket = null;
			for (int i = 1; i < 1024; i++) {
				try {
					socket = new Socket("localhost",i);
					System.out.println(i+"端口有程序在监听");
				} catch (IOException e) {
					System.out.println(i+"端口空闲");
				}finally {
					try {
						if(socket!=null)
							socket.close();
					} catch (IOException e) {
						e.printStackTrace();
					}   
				}   
			}   
		}   
	}

二. 建立连接超时时间的Socket

    默认情况下,与服务端链接建立Socket时,会一直阻塞,直到服务端的操作系统返回信息表示连接建立,或抛出异常表示该端口无应用程序响应.受网络影响,Socket返回会有很长等待时间,因此可以设置建立连接产生Socket的阻塞时间(超时时间)

Socket socket = new Socket();
socket.connect(new InetSocketAddress("localhost",8080),60*1000); //一分钟超时时间

三. Socket信息

getInetASddress();     // 获取与之连接的服务端ip
getPort();             // 获取与之连接的服务端port
getLocalAddress();     
getLocalPort();        // 客户端为该进程开启的端口

四. Socket关闭

当客户端与服务端通信结束,客户端应及时关闭Socket,释放为其开启的端口标识和其他资源

finally {    // 释放操作放在finally块中
    try {
        if(socket!=null)
            socket.close();
    } catch (IOException e) {
        e.printStackTrace();
}

Socket可以只关闭输入流或者输出流

shutdownInput();
shutdownOutput();

五. Socket选项

**1. TCP_NODELAY **
    Socket连接发送数据默认采用Negale算法,即发送方发送的数据先缓存在底层缓冲区,当缓冲区满后,再将数据发送出去.这种方法减少了数据的传输次数来提高通信效率,适合发送大量数据,且接收方会及时相应的场景下.
    若发送方连续发送小批量数据,这些数据不会立刻发送而是会先缓存起来,导致实时响应速度很慢;例如客户端鼠标移动的信息需要实时发送到服务器上,采用Negale算法,会大大降低实时响应速度.
    关闭Negale发送算法 : setTcpNoDelay();
**2. SO_RESUSEADDR: **
    socket的close()方法触发后,操作系统不会立刻释放该Socket所占端口,而是等一段时间,确保收到网络上发来的延迟数据,并不对该数据做任何处理.该段时间过后才释放端口.防止被其他恰巧绑定到相同端口的新进程收到该延迟数据.
    客户端来讲,操作系统为进程随机开启端口,一般不会使得新进程绑定到老进程的端口.对于服务端来说,进程挂掉后立即重启会重启失败,因为底层端口未被释放,而服务端进程应该响应客户端发来的延迟数据,所以服务端进程可以设置端口重用(setResuseAddress(true)).只有新老进程都设置了SO_RESUSEADDR,才能立刻端口重用

//SO_RESUSEADDR要在Socket未绑定本地port钱调用,否则该选项设置无效,用如下方式开启socket链接
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket();
        socket.setReuseAddress(true);
        socket.connect(new InetSocketAddress("localhost",8080),60*1000); //一分钟超时时间
    }

3. SO_TIEMOUT

byte[] buff = new byte[1024];
InputStream in = socket.getInputStream();
in.read(buff);

若没有数据可读,则会一直阻塞,setTimeout(60*1000),设置读阻塞的超时时间为1分钟
4. SO_LINGER
Socket的close方法会立即返回,但底层端口未被立即释放,会延迟一段时间,等到所有数据发送,且接收到所有返回的确认包后,才真正关闭.socket的setSoLinger(true,3600),设置close()方法自多阻塞3600秒,若还有数据未被发送,则丢弃该部分数据
5. SO_KEEPALIVE
默认情况,Socket的KEEPALIVE选项为false,客户端发完消息,如果不手动调用close(),则socket会一直存在,不释放资源.setKeepAlive(true)后,当通信两端没有数据传输时,操作系统发现该socket已超过2小时空闲,则会自动发送一个测试包给远程机器,测试包会持续尝试11分钟,若在12分钟内没收到远程机器发来的确认包,操作系统则会自动关闭本地Socket,释放资源
**6. OOBINLINE **
当setOOBLine(true),操作系统支持发送紧急数据,sendUrgent(int data)发送一个字节的紧急数据.默认情况OOBINLINE为false.但是接收方仍会把改紧急数据放到普通数据的队列中,紧急数据到来,接收方不会得到任何通知,所以接收方无法区分普通数据和紧急数据,只能按照相同方式处理