У меня есть многопоточный сервер чата, который я преобразовал для работы с сокетами Java SSL. Вы можете увидеть версию без сокетов SSL по сравнению с той, которую я конвертировал здесь, на Github. (Основная ветвь имеет SSL, другая ветвь имеет обычные сокеты)
Эта оригинальная модель (без 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 я использовал сертификаты, хранилища ключей и хранилища доверенных сертификатов. Тогда я столкнулся с той же проблемой, что и здесь, без них, а использовал только фабрику сокетов по умолчанию, которая опирается на файл cacerts, который поставляется вместе с JDK.
Обратите внимание, что до того, как возникла эта ошибка, первой проблемой, которая была устранена, был сбой рукопожатия между клиентом и сервером. Из-за способа работы SSL и класса Java PrintWriter рукопожатие инициируется при первом вызове PrintWriter.flush (), что происходит, как только клиент отправляет сообщение чата на сервер. Это решается только путем ручного включения поддерживаемых наборов шифров в ClientThread (сервер) и ServerThread (клиент), затем вызывая SSLSocket.StartHandshake () как минимум в ClientThread, если не в обоих.
Теперь сервер получает сообщения от клиента, но не передает их клиентам.
Когда я запускаю его в отладчике и пытаюсь пройтись по коду, я нахожу, что ClientThread получает сообщение клиента и отправляет его обратно, вызывая write () для PrintWriter для каждого ClientThread, а затем flush (). Предполагается, что ServerThread получает его, вызывая InputStream.available () для проверки ввода без блокировки, но available () всегда возвращает «0 байт» и поэтому никогда не переходит к Scanner.nextLine ()
Таким образом, либо Printwriter.write () и .flush () не отправляют данные, либо InputStream.available () не читает данные.
РЕДАКТИРОВАТЬ: После дополнительной отладки и тестирования я могу только сузить проблему до вывода со стороны сервера. Я определил это тем, что сервер немедленно отправил собственное сообщение, прежде чем ждать получения сообщений, и заставил клиента просто захватить nextLine () вместо проверки сначала с помощью available (). Поскольку этот тест не пройден, он показывает, что данные должны быть заблокированы каким-либо образом только со стороны сервера.
РЕДАКТИРОВАТЬ 2: я изменил код, чтобы использовать ObjectInputStreams и ObjectOuputStreams вместо использования Scanner и PrintWriters. Теперь я отправляю объекты «Message» из класса Serializable, который я создал для хранения строк. Это исправило проблему вывода для сообщений, приходящих с сервера. Если я заставлю клиента просто ждать ввода, вызывая readObject (), он будет получать сообщения с сервера. Тем не менее, если я сначала использую метод inputStream () метода InputStream, он по-прежнему будет возвращать только 0, даже если не должен. Поскольку InputStream serverInStream инициализируется с помощью socket.getInputStream (), он получает ssl.AppInputStream с ssl.InputRecord, и я предполагаю, что один из двух не реализует корректно доступный ().