我有一个多线程聊天服务器,已将其转换为可与Java SSL套接字一起使用。 You can see the version without SSL sockets compared to the one I converted here, on Github. (Master branch has SSL, other branch has regular sockets)
此原始模型(不带SSL)使用由客户端控制的“ ServerThreads”与其他客户端通信,方法是向服务器端的“ ClientThreads”发送消息,然后将其消息回显给所有其他ServerThreads。
这是ServerThread_w_SSL(客户端)的运行方法
@Override
public void run(){
System.out.println("Welcome :" + userName);
System.out.println("Local Port :" + socket.getLocalPort());
System.out.println("Server = " + socket.getRemoteSocketAddress() + ":" + socket.getPort());
//setup handshake
socket.setEnabledCipherSuites(socket.getSupportedCipherSuites());
try{
PrintWriter serverOut = new PrintWriter(socket.getOutputStream(), false);
InputStream serverInStream = socket.getInputStream();
Scanner serverIn = new Scanner(serverInStream);
// BufferedReader userBr = new BufferedReader(new InputStreamReader(userInStream));
// Scanner userIn = new Scanner(userInStream);
socket.startHandshake();
while(!socket.isClosed()){
if(serverInStream.available() > 0){
if(serverIn.hasNextLine()){
System.out.println(serverIn.nextLine());
}
}
if(hasMessages){
String nextSend = "";
synchronized(messagesToSend){
nextSend = messagesToSend.pop();
hasMessages = !messagesToSend.isEmpty();
}
serverOut.println(userName + " > " + nextSend);
serverOut.flush();
}
}
这是ClientThread_w_SSL(服务器端)的运行方法
@Override
public void run() {
try{
// setup
this.clientOut = new PrintWriter(socket.getOutputStream(), false);
Scanner in = new Scanner(socket.getInputStream());
//setup handshake
socket.setEnabledCipherSuites(socket.getSupportedCipherSuites());
socket.startHandshake();
// start communicating
while(!socket.isClosed()){
if(in.hasNextLine()){
String input = in.nextLine();
// NOTE: if you want to check server can read input, uncomment next line and check server file console.
System.out.println(input);
for(ClientThread_w_SSL thatClient : server.getClients()){
PrintWriter thatClientOut = thatClient.getWriter();
if(thatClientOut != null){
thatClientOut.write(input + "\r\n");
thatClientOut.flush();
}
}
}
}
原始程序可以使用常规套接字,但是在转换为SSL套接字后,我遇到了一个问题:输入没有从ClientThreads(服务器端)回显到ServerThreads(客户端)。
在我第一次尝试转换为SSL时,我使用了证书,密钥库和信任库。然后,我遇到了同样的问题,就像我在没有它们的情况下那样,而是仅使用依赖于JDK随附的cacerts文件的默认套接字工厂。
请注意,在遇到此错误之前,要解决的第一个问题是客户端与服务器之间发生握手失败。 Because of the way SSL and the Java PrintWriter class work,握手在第一次调用PrintWriter.flush()时启动,这在客户端向服务器发送聊天消息后立即发生。只有通过在ClientThread(服务器)和ServerThread(客户端)中手动启用受支持的密码套件,然后至少在ClientThread中调用SSLSocket.StartHandshake()才能解决此问题。
现在服务器正在接收来自客户端的消息,但它没有将它们回显给客户端。
当我在调试器中运行它并尝试单步执行代码时,我发现ClientThread接收到客户端的消息,并通过在PrintWriter上为每个ClientThread调用write()然后发回flush()来将其发送回去。 ServerThread应该通过调用InputStream.available()来检查输入而无阻塞地接收它,但是available()始终返回“ 0字节”,因此它永远不会命中Scanner.nextLine()
所以Printwriter.write()和.flush()没有发送数据,或者InputStream.available()没有读取数据。
编辑:经过更多的调试和测试,我只能将问题缩小到服务器端的输出。我通过让服务器在等待接收消息之前立即发送自己的消息来确定这一点,并让客户端仅获取nextLine()而不是先使用available()进行检查。由于此测试失败,因此表明必须仅以某种方式阻止来自服务器端的数据。
编辑2:我将代码更改为使用ObjectInputStreams和ObjectOuputStreams而不是使用Scanner和PrintWriters。现在,我从一个仅容纳String的Serializable类发送“ Message”对象。这已解决了来自服务器的消息的输出问题。如果我通过调用readObject()使客户端简单地等待输入,它将接收来自服务器的消息。但是,如果我首先使用InputStream的availble()方法,即使它本来不应该也仅返回0。由于InputStream serverInStream是由socket.getInputStream()初始化的,因此它得到的是ssl.AppInputStream和ssl.InputRecord,我猜这两者之一不能正确实现available()。