接上一篇博文,在小小添加几个Socket选项,
4.SO_LINGER选项:
//设置该选项: public void setSoLinger(boolean on,int seconds) throws SocketException // 注意第二个参数以秒为单位,并非毫秒 //读取该选项: public int getSoLinger() throws SocketException
SO_LINGER选项用来控制Socket关闭时的行为,默认情况下,当执行Socket的close()方法,该方法会立即返回,但底层的Socket实际上并不立即关闭,它会延迟一段时间知道发送完所有剩余的数据,才会真正关闭Socket,才断开连接。
但对于以下情况:
socket.setSoLinger(true,0); //该方法也会立即返回,但底层的Socket也会立即关闭,所有未发送完的剩余数据被丢弃。 //还在网络上传输,但是未被接收方接收的数据, socket.setSoLinger(true,1000); // 而该方法不会立即返回,而是进入阻塞状态。 只有当底层的Soket发送完所有的剩余数据或阻塞时间已经超过了1000秒,也回返回,但是剩余未发送的数据被丢弃。
//对于该选项,尝试着让服务器端先睡眠一会,再开始接受数据:
//服务器端程序: public class SimpleServer { public static void main(String[] args) throws Exception { ServerSocket server = new ServerSocket(7777); Socket socket = server.accept(); System.out.println("服务器困了,于是决定休息会..."); Thread.sleep( 1000*10 ); // 睡眠10秒。 System.out.println("服务器终于睡醒了,然后开始接受数据:"); InputStream in = socket.getInputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); byte[] buff = new byte[1024]; int length = -1; while( ( length = in.read(buff) ) != -1){ buffer.write(buff,0,length); } System.out.println( new String( buffer.toByteArray() )); //把字节数组转换成字符串。 } } //客户端程序: public class SimpleClient { public static void main(String[] args) throws Exception { Socket socket = new Socket("localhost",7777); //socket.setSoLinger(true, 0); //(1) 该条件下,会强行关闭底层的Socket,导致所有未发送数据丢失,而服务器算接受方睡醒后接受数据,由于底层Socket关闭,则会抛出SocketException: Connection reset. //socket.setSoLinger(true, 300); //(2) 注意Socket关闭时间,实际上会等待一段时间在关闭 OutputStream out = socket.getOutputStream(); StringBuffer sb = new StringBuffer(); String str = "关于对setSoLinger选项的设置:"; sb.append( str ); for( int i=1;i<=10000;i++){ // 需注意jvm堆栈的设置。 sb.append( i ); } System.out.println( sb ); out.write( sb.toString().getBytes() ); System.out.println("开始关闭Socket: "); long start = System.currentTimeMillis(); socket.close(); // 注意不同条件下(1)与(2)的关闭的时间 long end = System.currentTimeMillis(); System.out.println("关闭Socket所用时间为: " + (end - start ) +"ms" ); } }
5.SO_RCVBUF选项: 该选项用于 输入数据 的缓冲区的大小。
//设置该选项: public void setReceiveBufferSize(int size) throws SocketException //读取该选项: public int getReceiveBufferSize() throws SocketException
6.SO_SNDBUF选项:该选项用于 输出数据的缓冲区的大小。
//设置该选项: public void setSendBufferSize(int size) throws SocketException; //读取该选项: public void getSendBufferSize() throws SocketException;
public class SocketBufferDemo { public static void main(String[] args) throws SocketException { Socket socket = new Socket();//不带参的构造方法,不会试图建立与服务器端的连接. //默认情况下的输入输出缓冲区的大小: int rcvbuf = socket.getReceiveBufferSize(); // 输入缓冲区大小 int sndbuf = socket.getSendBufferSize(); //输出缓冲区大小 System.out.println( rcvbuf + "\t" + sndbuf ); //重新设置输入输出缓冲区的大小再输出结果: socket.setReceiveBufferSize( 1024*32 ); socket.setSendBufferSize( 1024*32 ); System.out.println( socket.getReceiveBufferSize()+"\t"+socket.getSendBufferSize() ); } }
7.SO_KEEPALIVE选项:
//设置该选项: public void setKeepAlive(boolean on) throws SocketException //读取该选项: public int getKeepAlive() throws SocketException
当该选项为true时,表示底层的TCP实现会监视该连接是否有效。当连接处于空闲状态(即连接的两端没有互相传送数据)超过2H,本地的TCP实现会发送一个数据包给远程的Socket。如果远程Socket没有发回响应,TCP实现就会持续尝试11分钟,直到接收到响应为止。如果在12分钟内未收到响应,TCP实现就会自动关闭本地Socket,断开连接。(在不同的网络平台上,TCP实现尝试与远程Socket对话的时限会有所差别)。
该选项默认值为 false,即表示TCP不会监视连接是否有效,不活动的客户端可能会永久存在下去,而不会注意到服务器已经崩溃。
对于KeepAlive这种系统底层的机制(用于系统维护每一个tcp连接的),可在认识到另一种新的概念,即心跳线程:
链接网址:①; http://code.taobao.org/p/tfs/wiki/dataserver/background/ <心跳线程简单介绍>
②: http://blog.csdn.net/xuyuefei1988/article/details/8279812 《关于心跳包机制》
③:http://blog.sina.com.cn/s/blog_616e189f0100s3px.html <这篇也不错,Socket缓冲区探讨>
8.OOBINLINE选项:
//设置该选项: public void setOOBInline(boolean on ) throws SocketException //读取该选项: public void getOOBInline() throws SocketException public void sendUrgentData(int data) throws IOException //虽然sendUrgentData的参数data是int类型,但只有这个int类型的低字节被发送(即一个字节,8位),其它的三个字节被忽略。
该选项为true时,表示支持发送一个字节的TCP紧急数据。Socket类的sendUrgentData( int data ) 方法用于发送一个字节(8位)的TCP紧急数据。
而为false时,表示当接收方收到紧急数据时不作任何处理,直接丢弃。
但是问题是接收方会把接收到的紧急数据与普通数据放在同样的队列中,除非使用一些更高层次的协议,否则接收方处理紧急数据的能力非常有限,当紧急数据到来时,接收方不会得到任何的通知,因此接收方很难区分普通数据与紧急数据,只好按同样的方式处理他们。
//服务端代码: public class OOBInlineServer { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(7777); System.out.println("服务器已经启动,端口号:7777"); while (true) { Socket socket = serverSocket.accept(); // 须在服务端中也设置为true,否则无法接受到客户端发送过来的紧急数据: socket.setOOBInline(true); InputStream in = socket.getInputStream(); InputStreamReader inReader = new InputStreamReader(in); BufferedReader bReader = new BufferedReader(inReader); System.out.println(bReader.readLine()); System.out.println(bReader.readLine()); socket.close(); } } } //客户端代码: public class OOBInlineClient { public static void main(String[] args) throws Exception { Socket socket = new Socket("localhost", 7777); socket.setOOBInline(true); OutputStream out = socket.getOutputStream(); OutputStreamWriter outWriter = new OutputStreamWriter(out); outWriter.write(67); // 向服务器发送字符"C" outWriter.write(" Hello World\r\n "); socket.sendUrgentData(65); // "A" socket.sendUrgentData(68); // "D" outWriter.flush(); socket.sendUrgentData(322); // "B" 322分布在两个字节上,但是其低位为:0100 0010 即刚好跟 66 一样, socket.close(); } }
猜测下上述输出结果: 猜测可能为:
C Hello World
A D B
但正常结果输出为:
由此可见:使用sendUrgentData()方法发送数据后,系统会立即将这些数据发送出去,而使用write()(首先是将数据存放在了缓冲区)发送数据,必须要使用flush()方法才会真正发送数据。
另外注意的是:在使用 setOOBInline()方法时,要注意必须在客户端和服务端程序同时使用该方法,打开SO_OOBInline选项,否则无法命名sendUrgentData来发送数据。