场景
客户端 服务端 建立连接
客户端发送数据给服务端处理
服务端从输入流取到数据,处理中.....
此时客户断开连接
服务端将处理后的数据发送给客户,预想的情况,连接已断开,输出流已经不存在,write的时候应该报错
package tcp;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
public class TcpClient {
public static void main(String[] args) {
try {
Socket s = new Socket("127.0.0.1", 8080);
// s.setTcpNoDelay(true);
// InputStream is = s.getInputStream();
OutputStream os = s.getOutputStream();
DataOutputStream os_d = new DataOutputStream(os);
for (;;) {// 保持长连接不断发送
InputStreamReader input = new InputStreamReader(System.in);
BufferedReader read = new BufferedReader(input);
String content = read.readLine();
if ("exit".equals(content)) {
s.close();
return;
}
System.out.println("开始发送报文," + content);
os_d.writeBytes(content + System.getProperty("line.separator"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package tcp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
// 先启动服务器端程序
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("tcp 服务端开启....");
while (1 == 1) {
Socket socket = serverSocket.accept();// 阻塞等待消息
socket.setOOBInline(false);
System.out.println("已经获取连接" + socket);
// socket.setSoTimeout(10*1000);//readLine这里等待10s,如果用户还没有输入就抛出异常java.net.SocketTimeoutException
// socket
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
System.out.println("接收客户端信息...");
while (true) {// 长连接不断处理
System.out.println("等待用户输入");
String readLine = bufferedReader.readLine();
if (readLine == null) {
return;
}
System.out.println("接收报文:" + readLine);
try {
System.out.println("后台处理中.....");
Thread.sleep(10*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
StringBuffer sb=new StringBuffer();
// for(int i=0;i<10000;i++)
// sb.append("aaaaaaaaaaaaaa");
bufferedWriter.write(readLine+","+sb.toString());
bufferedWriter.flush();
System.out.println("响应客户端OK");
try {
Thread.sleep(10*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
客户端控制台
aaa
开始发送报文,aaa
exit
服务端控制台
tcp 服务端开启....
已经获取连接Socket[addr=/127.0.0.1,port=3643,localport=8080]
接收客户端信息...
等待用户输入
接收报文:aaa
后台处理中.....
响应客户端OK
等待用户输入
Exception in thread "main" java.net.SocketException: Software caused connection abort: recv failed
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
at java.io.InputStreamReader.read(InputStreamReader.java:167)
at java.io.BufferedReader.fill(BufferedReader.java:136)
at java.io.BufferedReader.readLine(BufferedReader.java:299)
at java.io.BufferedReader.readLine(BufferedReader.java:362)
at tcp.TcpServer.main(TcpServer.java:32)
这里说明服务端在write的时候竟然没问题,在下次read的时候才报连接断开,无法读取类似的异常。。
为什么呢?
write那里了呢?我还特意bufferedWriter.flush();也都没问题。
如果响应的是大量数据呢?,模拟代码如下:
for(int i=0;i<10000;i++)
sb.append("aaaaaaaaaaaaaa");
bufferedWriter.write(readLine+","+sb.toString());
bufferedWriter.flush();
这时候预想的情况来了。
tcp 服务端开启....
已经获取连接Socket[addr=/127.0.0.1,port=3706,localport=8080]
接收客户端信息...
等待用户输入
接收报文:aa
后台处理中.....
Exception in thread "main" java.net.SocketException: Software caused connection abort: socket write error
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
at java.net.SocketOutputStream.write(SocketOutputStream.java:136)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:202)
at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:263)
at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:106)
at java.io.OutputStreamWriter.write(OutputStreamWriter.java:190)
at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:111)
at java.io.BufferedWriter.write(BufferedWriter.java:212)
at java.io.Writer.write(Writer.java:140)
at tcp.TcpServer.main(TcpServer.java:46)
这是为什么呢?估计是缓存的问题,可能跟具体的socket通讯机制有关。
Socket setSoLinger
Enable/disable SO_LINGER with the specified linger time in seconds. The maximum timeout value is platform specific. The setting only affects socket close.
默认情况下,close 连接,实际上系统还会逗留一端时间才会真正关闭,代码close,仅仅是假象。
具体逗留多久有系统平台决定。看来前面的write是真的OK。
修改代码,把服务端的处理时间延长。再试试。
System.out.println("后台处理中.....");
Thread.sleep(60*1000);
这里处理1分钟情况跟之前一样,我猜想2边的通讯方式如下:
服务端在出来完write的时候,通过三次握手,告诉对方有人已经请求我close了不要在给我发送数据了,
这次接收是为了保证数据的完整性,怕我关闭后,立刻有别的socket冒充我,导致把处理后的数据发送给“骗子”
知道对方出问题了(已经关闭了)等下次在read的时候直接抛异常出来。
这个只是猜想,还需要进一步验证。
再次测试
在客户端修改代码
Socket s = new Socket("127.0.0.1", 8080);
s.setSoLinger(true, 0);
close后,不做任何逗留,让系统直接关闭。
tcp 服务端开启....
已经获取连接Socket[addr=/127.0.0.1,port=3842,localport=8080]
接收客户端信息...
等待用户输入
接收报文:ff
后台处理中.....
Exception in thread "main" java.net.SocketException: Connection reset by peer: socket write error
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
at java.net.SocketOutputStream.write(SocketOutputStream.java:136)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:202)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:272)
at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:276)
at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:122)
at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:212)
at java.io.BufferedWriter.flush(BufferedWriter.java:236)
at tcp.TcpServer.main(TcpServer.java:42)
此时就是预想的情况。
这里可能还有个问题,就是为什么在我发送大量的数据给客户端时也会报write的异常呢?
代码:
for(int i=0;i<10000;i++)
sb.append("aaaaaaaaaaaaaa");
bufferedWriter.write(readLine+","+sb.toString());
bufferedWriter.flush();
原因应该是这样的。对于TCP底层来说,每次通讯是发送包的,如果包裹太大,就会打出多个小包
分批来发送,当第一次发送的时候已经发送成功了,但对方已经把自己已经close的状态发送给
服务端了,此时服务端再次发包过去直接失败。
TCP_NODELAY
SO_TIMEOUT
SO_LINGER
SO_RCVBUF
SO_SNDBUF
看来有时间需要把TCP的通讯机制好好系统的学习下了。