项目场景:
自学javaSE过程中,发现尚硅谷第十六章网络聊天室没有关闭各种流,想自己手动添加代码关闭,结果出现异常
问题描述
例如:数据传输过程中数据不时出现丢失的情况,偶尔会丢失一部分数据
APP 中接收数据代码:
代码如下
package com.atguigu02.tcpudp.chat;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class ChatServerTest {
//这个集合用来存储所有在线的客户端
static ArrayList<Socket> online = new ArrayList<Socket>();
public static void main(String[] args)throws Exception {
//1、启动服务器,绑定端口号
ServerSocket server = new ServerSocket(8989);
//2、接收n多的客户端同时连接
while(true){
Socket socket = server.accept();
online.add(socket);//把新连接的客户端添加到online列表中
MessageHandler mh = new MessageHandler(socket);
mh.start();//这个线程专门处理这个socket的信息
}
}
static class MessageHandler extends Thread{
private Socket socket;
private String ip;
public MessageHandler(Socket socket) {
super();
this.socket = socket;
}
public void run(){
BufferedReader br = null;
try {
ip = socket.getInetAddress().getHostAddress();
//插入:给其他客户端转发“我上线了”
sendToOther(ip+"上线了");
//(1)接收该客户端的发送的消息
InputStream input = socket.getInputStream();
InputStreamReader reader = new InputStreamReader(input);
br = new BufferedReader(reader);
String str;
while((str = br.readLine())!=null){
//(2)给其他在线客户端转发
sendToOther(ip+":"+str);
}
sendToOther(ip+"下线了");
} catch (IOException e) {
try {
sendToOther(ip+"掉线了");
} catch (IOException e1) {
e1.printStackTrace();
}
}finally{
//从在线人员中移除我
online.remove(socket);
try {
br.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
//封装一个方法:给其他客户端转发xxx消息
public void sendToOther(String message) throws IOException{
//遍历所有的在线客户端,一一转发
for (Socket on : online) {
OutputStream every = on.getOutputStream();
//为什么用PrintStream?目的用它的println方法,按行打印
PrintStream ps = new PrintStream(every);
ps.println(message);
ps.close();
}
}
}
}
public class ChatClientTest {
public static void main(String[] args)throws Exception {
//1、连接服务器
Socket socket = new Socket("127.0.0.1",8989);
//2、开启两个线程
//(1)一个线程负责看别人聊,即接收服务器转发的消息
Receive receive = new Receive(socket);
receive.start();
//(2)一个线程负责发送自己的话
Send send = new Send(socket);
send.start();
send.join();//等我发送线程结束了,才结束整个程序
socket.close();
}
}
class Send extends Thread{
private Socket socket;
public Send(Socket socket) {
super();
this.socket = socket;
}
public void run(){
try {
Scanner input = new Scanner(System.in);
OutputStream outputStream = socket.getOutputStream();
//按行打印
PrintStream ps = new PrintStream(outputStream);
//从键盘不断的输入自己的话,给服务器发送,由服务器给其他人转发
while(true){
System.out.print("自己的话:");
String str = input.nextLine(); //阻塞式的方法
if("bye".equals(str)){
break;
}
ps.println(str);//打印到socket.getOutputStream(),即往服务器端打印
}
input.close();
ps.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Receive extends Thread{
private Socket socket;
public Receive(Socket socket) {
super();
this.socket = socket;
}
public void run(){
try {
InputStream inputStream = socket.getInputStream();
Scanner input = new Scanner(inputStream);
while(input.hasNextLine()){
String line = input.nextLine();
System.out.println(line);
}
input.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
原因分析:
发现是sendToOther方法中关闭了printStream导致出现异常,尝试debug分析
运行服务器程序,发现从断点处直接执行到catch,说明此时已经断开了socket连接。
因为ps是处理流,关闭ps就会直接关闭every——Socket的输出流,我之前错误的理解了这样并不会影响Socket的输入流。
查阅资料发现Java的socket是一个全双工套接字,任何的输入流或输出流的close()都会造成Socket关闭。
解决方案:
使用socket.shutdownOutput()方法关闭套接字的输出流,使服务器知道输出流关闭,可以得到流末尾标志(-1)。
但是这样又会出现新的问题,关闭了输出流,那这个服务器就从以下断点直接跳出循环了,不会再等待读取数据了,客户端直接下线了
此时调用sendToOther因为输出流已关闭,直接就报错了。
所以还是不要乱改代码了,还是不在sendToOther中关闭这些流了。