关于创建一个1对1的聊天的问题。
来源:5-2 UDP搜索IP与端口-2
PerryMore
2019-03-14
本来我的思路是创建一个类HandlerSocketHelper,它的作用就是开启读和写两条线程分别进行数据处理。但是以下代码服务器端调试了很久一直出现无限读取为null的现象,希望老师帮忙看一下哪里的问题。
import helper.HandlerSocketHelper;
import java.io.*;
import java.net.*;
public class Client {
private static final int SERVER_PORT = 20000;
private static final int LOCAL_PORT = 20001;
private static final int NET_DATA_SIZE = 64 * 1024 * 1024;
public static void main(String[] args) throws IOException {
Socket socket = createSocket();
initSocket(socket);
socket.connect(new InetSocketAddress(Inet4Address.getByName("192.168.199.117")
, SERVER_PORT), 3000);
System.out.println("已发起服务器连接,并进入后续流程~");
System.out.println("客户端信息: " + socket.getLocalAddress() + " : " + socket.getLocalPort());
System.out.println("服务端信息: " + socket.getInetAddress() + " : " + socket.getPort());
HandlerSocketHelper mSocketHelper = new HandlerSocketHelper(socket);
mSocketHelper.operateSocket();
}
private static Socket createSocket() throws IOException {
Socket socket = new Socket();
socket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), LOCAL_PORT));
return socket;
}
private static void initSocket(Socket socket) throws IOException {
// 设置读取超时时间为2s
socket.setSoTimeout(2000);
// 1、是否复用未完全关闭的socket地址,对于指定bind操作后的套接字有效
// 2、当TCP连接调用关闭操作时,此时网络层连接通道在一小段时间内还是会保持
// 着超时连接状态
// 3、需要在bind操作前进行设置。
//
// 翻译:对于我们使用一个众所周知的IP地址和端口而言,如果牵连到这个端口的
// 连接正处于超时连接状态,这将可能导致我们无法绑定到这个所需的端口上。
socket.setReuseAddress(true);
// 是否开启nagle算法
// 1、比较严谨的模式下:一份数据包发送完后,服务端将进行收到数据的回送消息。
// 但这样回送包将充斥在网络通道中,有种方式就是将几个数据打包成一份数据,然
// 后同时发送给服务器端,最后收到一份回送包数据。
// 2、网络环境中,小片数据发送时,其首部的封装数据比实际有用数据量还要大,这
// 显然会扩大网络数据传输的消耗。
// true:无延迟,表示不使用Nagle算法。
socket.setTcpNoDelay(false);
// 是否需要在长时无数据响应时发送确认数据(类似心跳包),时间大约2小时。
socket.setKeepAlive(true);
// 对于close关闭操作的行为做何种处理,默认(false,0)
socket.setSoLinger(false, 0);
// 是否让紧急数据内敛,默认为false;通过 socket.sendUrgentData();进行发送
// 所谓内敛,就是是否会和应用层的行为数据出现在一起。一般不希望出现这种情况。
socket.setOOBInline(false);
socket.setReceiveBufferSize(NET_DATA_SIZE);
socket.setSendBufferSize(NET_DATA_SIZE);
// 设置性能参数:短链接,延迟,宽带的相对权重
socket.setPerformancePreferences(1, 1, 1);
}
}
import helper.HandlerSocketHelper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
private static final int SERVER_PORT = 20000;
public static void main(String[] args) throws IOException {
ServerSocket server = createServerSocket();
System.out.println("服务器已开启,等待客户端连接~");
System.out.println("服务端信息: " + server.getInetAddress() + " : " + server.getLocalPort());
// 这里的无限循环是为了接受不同的客户端连接
for (; ; ) {
// 这个方法会阻塞,只有新的客户端进行连接时才会被调用
Socket client = server.accept();
// 一个客户端连接后,为了能够持续的接受到其发过来的消息,那么就需要一个无限循环来不断从已连接的
// 流通道中读取数据。
HandlerSocketHelper mSocketHelper = new HandlerSocketHelper(client);
mSocketHelper.operateSocket();
}
}
private static ServerSocket createServerSocket() throws IOException {
ServerSocket server = new ServerSocket();
// 设置超时时间,当2s内没有新的客户端连接时(也就是accept方法没有返回时),服务器端将会断开。
// server.setSoTimeout(2000);
server.bind(new InetSocketAddress(InetAddress.getLocalHost(),SERVER_PORT));
return server;
}
}
package helper;
import java.io.*;
import java.net.Socket;
public class HandlerSocketHelper {
private Socket mSocket;
private int mUserPort;
private String mUserAddress;
private String mUserFormat;
public HandlerSocketHelper(Socket mSocket) {
this.mSocket = mSocket;
mUserFormat = "聊天用户 %s 说:";
}
public void operateSocket() {
if (mSocket == null)
return;
mUserPort = mSocket.getPort();
mUserAddress = mSocket.getInetAddress().getHostAddress();
System.out.println("客户信息:IP:" + mUserAddress);
System.out.println("Port:" + mUserPort);
ReadThread readThread = new ReadThread();
readThread.start();
WriteThread writeThread = new WriteThread();
writeThread.start();
}
public void exit() {
if (mSocket != null) {
try {
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
mSocket = null;
}
}
class ReadThread extends Thread {
private boolean isDone;
@Override
public void run() {
super.run();
BufferedReader readStream;
try {
readStream = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
return;
}
while (!isDone) {
try {
String line = readStream.readLine();
if ("bye".equalsIgnoreCase(line)) {
isDone = true;
} else {
System.out.println(String.format(mUserFormat, mUserAddress) + line);
}
} catch (IOException e) {
HandlerSocketHelper.this.exit();
System.out.println("异常退出!");
break;
}
}
try {
readStream.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(String.format(mUserFormat, mUserAddress) + "我退出了!");
}
}
class WriteThread extends Thread {
@Override
public void run() {
super.run();
PrintStream printStream;
try {
printStream = new PrintStream(mSocket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
HandlerSocketHelper.this.exit();
return;
}
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
try {
String line = bufferedReader.readLine();
printStream.println(line);
} catch (IOException e) {
HandlerSocketHelper.this.exit();
break;
}
}
}
}
}
写回答
2回答
-
这个问题,我调试了一下的确是出现循环读取null的情况。
一般来说读取操作是阻塞的,也就是会等待到数据到来才会返回,但是此刻直接返回了,原因无他:
客户端已异常退出,所以关闭了连接
服务器并为在客户端断开时进行退出处理,所以依然处于读取数据状态
但是读取数据的逻辑中我们可以调试跟踪进去看见:
他进入到了EOF判断块中,并且当前并为含有任何的读取数据;所以可以判定是已经读取到末尾了。
读取到末尾的原因也是因为客户端断开导致的问题。
所以本质来说没有任何的问题,只是客户端断开时服务器应该进行对应的处理即可。
一般来说:
String line = readStream.readLine();
只要返回了null其实我们就可以判定为出现了异常断开了,而不必继续去进行读取操作。
012019-03-24 -
Qiujuer
2019-03-15
好的,我看看哈,一会儿给你答复。
00
相似问题