这两天在写socket通信,也就是下面的东西,然后遇到了个问题,怎样才能优雅的关闭socket(正常关闭而不是发生异常导致的关闭)
直接说思路吧,后面再说我遇到的问题!
我们这里说的关闭是用户点击断开按钮后再关闭,如果是用户直接退出程序可以在退出之前调用和关闭按钮一样的方法,或则采用心跳检测判断用户是否在连接,不过这里不说怎么实现这个。
我们一般只说客户端断开连接,如果想要服务器断开和客户的连接也可以采用同样的方法,但是实际情况服务器是一直运行的,所以我们说客户端的就行。
思路:
1、我们在点击断开连接按钮时并不是直接关闭socket,而是先给服务器发送断开连接消息;
2、服务器收到该消息后再向客户端发送确认消息;
3、服务器发送完确认消息之后断开连接;
4、客户端收到确认消息后再断开连接;
5、为了能够再次连接,客户端还要在主程序中关闭socket。(为什么要再次关闭可以看我后面的问题)
其实看到上面的思路应该知道怎么做了,只是很多时候我们没有想到是这样,然后直接关闭socket,虽然可以关闭,但是那时因为发生了异常,你不关闭程序也会帮你关闭。至于为什么会异常,很简单,在接收线程里面是一个死循环,它一直在监听服务器有没有给它发消息,当你关闭socket的时候你无法进入这个循环去关闭它,但是会影响到它使它为空或者突然被关闭(正在监听突然被关闭是有异常的)。
下面以我的代码为例:
主要看三个类
接收线程:一直在监听,为了使接收和发送不相互影响必须使用线程
发送线程:这个其实和普通类一样,因为只有在有需要的时候才发送,这时调用该类里面的发送方法即可,如果是控制台的话那它就必须线程,否则不能使发送和接收分离
主类:用于创建连接和关闭连接
1、当我们点击关闭按钮时,只需要给服务器发送一个关闭消息:readyCloseLink(),它会调用发送线程的发送方法给服务器发送断开连接消息bye
/**
* @Name
* @Description TODO 准备关闭,在点击关闭后并没有实际关闭,
* 而是给服务器发送关闭消息,服务器收到后返回确认消息,同时服务器关闭该连接
* 客户端接收到确认后接收线程中关闭socket,最后在本类中再次关闭socket以保证可以再次连接
* (接收线程中关闭socket并不能关闭本类的socket,在接收线程中关闭是为了程序正常退出而不是因为异常退出)
* @param
* @Return void
* @Author KingSSM
* @Date 2019/12/5 9:22
*/
public void readyCloseLink(){
new SendThread().closeClientSocket("bye");
}
//真正断开连接,此类中的socket置空,发送线程的clientSocket已经关闭,此处关闭可以确保可以再次连接
public void closeLink() {
try {
if (socket != null) {
socket.close();
}
socket = null;
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("<====== 警告:已断开连接 ======>");
uiUpdate.receiveMessage("<====== 警告:已断开连接 ======>");
}
2、服务器收到这个bye后,给客户端发送确认消息(这个消息也是bye)
private void receive(){
BufferedReader in;
String address = serverSocket.getInetAddress().getHostAddress();
String message;
try {
in = new BufferedReader(new InputStreamReader(serverSocket.getInputStream()));
while (true) {
message=in.readLine();
//如果是关闭消息,则关闭
if(message.equals("bye")){
closeSocket();
System.out.println("<------ 提示:"+address+" 断开连接 ------>");
uiUpdate.receiveMessage("<------ 提示:"+address+" 断开连接 ------>");
break;
}
if(message.equals("/bye")){
message = "bye";
}
System.out.println("from "+address+": "+message);
uiUpdate.receiveMessage("from "+address+": "+message);
}
} catch(IOException ex) {
ex.printStackTrace();
} finally {
if(serverSocket!=null){
if(!serverSocket.isClosed()){
try {
serverSocket.close();
serverSocket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
private void closeSocket(){
try {
PrintWriter out = new PrintWriter(serverSocket.getOutputStream());
out.println("bye");
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
3、服务器发送完确认消息后在finally里面关闭连接
4、客户端收到确认消息后退出监听循环,然后关闭socket。这里就是正常退出,因为读到了断开连接消息才退出循环,然后再关闭socket。
private void receive(){
BufferedReader in;
String address = clientSocket.getInetAddress().getHostAddress();
try {
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
while (true) {
String msg = in.readLine();
if(msg.equals("bye")){
uiUpdate.receiveMessage("from "+address+": "+msg);
break;
}
if(msg.equals("/bye")){
msg = "bye";
}
System.out.println("from "+address+": "+msg);
uiUpdate.receiveMessage("from "+address+": "+msg);
}
} catch(IOException ex) {
closeClientSocket(); //如果发生异常也同样关闭连接
new SocketClient().closeLink(); //本线程的clientSocket关闭后还需要关闭SocketClient的socket
} finally {
closeClientSocket();
new SocketClient().closeLink(); //本线程的clientSocket关闭后还需要关闭SocketClient的socket
}
}
//关闭clientSocket
private void closeClientSocket(){
if (clientSocket != null) {
try {
clientSocket.close();
clientSocket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
5、接收线程在关闭socket之后再调用主类中真正关闭的socket的方法。
自此,一个正常的socket关闭结束而不会发生异常。
下面说一下问题,感兴趣的可以看一下
刚开始我以为服务器和客户端的socket是完全同一个东西,一端的关闭后导致另一端的监听发生异常。其实这个大错特错,可以理解为这两端的socket只是在客户端和服务器之间建立了通道而已,一端的关闭并不会影响到另一端。比如说,客户端关闭了socket,那客户端不能再发送也不能再接收,但是服务器可以发送也可以接收,客户端对服务器没有影响。但是在客户端中,发送线程里面关闭了socket,那么接收线程里面的socket也就会因为异常而关闭。
现在来说一下关闭的顺序,这个很重要,为了正常关闭,必须是接收线程先关闭,然后主程序再关闭,或者接收线程不关闭(线程结束它自己也会关闭,我们显式关闭会规范点)直接在主类中关闭(直接关闭也要在 线程运行结束之后才能关闭),但是却对不能主类中关闭socket之后还要发送或则监听。
我们来模拟一下:
在准备关闭方法中,给服务器发送消息后就关闭socket
点击断开连接按钮,出现了两次警告,正常关闭只会有一次
而多出的一次警告是catch里面的代码被执行了,也就是说发生了异常,这个异常正是监听引发的,主类中的socket关闭后,接收线程里面的socket受到了影响。
再来一种情况:
把主类中关闭socket部分的代码注释掉,其他代码不动
当我断开连接连接后再连接,无法连接上去,这里提示已连接服务器,其实是上一次的连接,上一次的连接没有被关闭,也就是说我的接收线程在关闭socket之后并不能影响到主类中的socket。
从上面两种情况抛出我的问题:主类中的socket关闭,导致接收线程里面的socket也会被关闭,但是接收线程里面的socket关闭却不能使主类里面的socket关闭
接收线程里面的socket是这样创建的,所以我觉得主类和接收线程它们的socket要么可以相互影响,要么都不影响,而现在是单方面影响。
如果有人知道是怎么回事希望留言解答一下!!!