NIO+BIO+AIO. Интенсивная лекция по режиму ввода-вывода в учебном пособии по Java. Продолжение темной лошадки (1)

Учебное пособие по NIO+BIO.Java Интенсивная лекция по режиму ввода-вывода.

Глава 1 Введение в курс BIO, NIO, AIO

1.1 Описание курса

При проектировании и разработке программного обеспечения Java коммуникационная архитектура неизбежна. Нам необходимо использовать технологии, связанные с сетевым взаимодействием, для взаимодействия данных между различными системами или различными процессами или в сценариях обмена данными с высокой степенью параллелизма. Для некоторых опытных программистов раннее сетевое взаимодействие Java Архитектура имела некоторые недостатки, наиболее раздражающим из которых была синхронная блокировка связи ввода-вывода (BIO), основанная на низкой производительности.Требования Java начала поддерживать технологию неблокирующей связи ввода-вывода (NIO) в 2002 году. Когда большинство читателей изучают технологии, связанные с сетевыми коммуникациями, они соприкасаются только с фрагментарными точками коммуникационных технологий, без полной технической архитектуры, так что всегда нет четкого решения для коммуникационных сценариев Java. Этот курс начнется с самой базовой связи BIO с NIO и AIO через большое количество четких и прямых случаев.Читатели могут четко понять явления, концепции и характеристики блокировки, синхронизации и асинхронности, а также их преимущества и недостатки. Этот курс объединяет большое количество случаев, чтобы дать читателям возможность быстро понять использование каждой коммуникационной архитектуры.

1.2 Требования к обучению для этого курса

  • Этот курс не подходит для полных 0 базовых студентов.
  • По крайней мере, вам нужно освоить: базовое программирование Java SE, такое как многопоточность Java, потоковое программирование ввода-вывода Java, основы сети Java (такие как: IP, порт, протокол), общие шаблоны проектирования Java должны иметь определенное понимание.
  • Умение программировать Java ООП, с определенным программным мышлением.

1.3 Задачи, решаемые техникой связи в целом

  • Требования к связи в локальной сети.
  • Базовый механизм передачи сообщений между несколькими системами.
  • В условиях высокой параллелизма требуются сценарии связи с большими объемами данных.
  • игровая индустрия. Будь то мобильный игровой сервер или крупномасштабная онлайн-игра, язык Java используется все шире.

Глава 2 Путь эволюции ввода-вывода Java

2.1 Основное описание модели ввода/вывода

Модель ввода-вывода: какой канал или режим связи и архитектура используются для передачи и приема данных, что в значительной степени определяет производительность программной связи.Java поддерживает 3 модели сетевого программирования/ввода-выводов: BIO, в соответствии с фактическими требованиями к связи NIO и AIO
, различные модели ввода-вывода следует выбирать в соответствии с различными бизнес-сценариями и требованиями к производительности.

2.2 Модель ввода/вывода

Ява БИО

Синхронный и блокирующий (традиционный тип блокировки), режим реализации сервера - один поток на соединение, то есть, когда у клиента есть запрос на соединение, серверу необходимо запустить поток для обработки.Если соединение ничего не делает, оно будет вызвать ненужные накладные расходы на потоки [Простая схематическая диаграмма]
вставьте сюда описание изображения

Java НИО

Java NIO: Synchronous non-blocking , режим реализации сервера — один поток для обработки нескольких запросов (подключений), то есть запросы на подключение, отправленные клиентом, будут регистрироваться на мультиплексоре, а мультиплексор опрашивает соединение с вводом-выводом запрос обработан [простая схема]
вставьте сюда описание изображения

Java-все-в-одном

Java AIO (NIO.2): асинхронный и неблокирующий . Режим реализации сервера — один эффективный запрос и один поток. Клиентский запрос ввода-вывода сначала выполняется ОС, а затем уведомляет серверное приложение о запуске потока для обработки. , Обычно это применимо к количеству подключений.Многие приложения с длительным временем подключения.

2.3 Анализ сценариев применения BIO, NIO и AIO

1. Метод BIO подходит для архитектур с относительно небольшим и фиксированным числом подключений.Этот метод имеет относительно высокие требования к ресурсам сервера, а параллелизм ограничен приложениями.Это был единственный выбор до JDK1.4, но программа простой и понятный.
2. Метод NIO подходит для архитектур с большим количеством подключений и относительно короткими подключениями (легкая операция), такими как чат-серверы, системы заграждений и связь между серверами. Программирование усложнилось, и JDK1.4 начал его поддерживать.

3. Метод AIO используется в архитектурах с большим количеством соединений и длительными соединениями (тяжелыми операциями), такими как серверы фотоальбомов, которые полностью вызывают ОС для участия в параллельных операциях.Программирование более сложное, и JDK7 начал поддержите это.

Глава 3 Углубленный анализ JAVA BIO

3.1 Базовое введение в Java BIO

  • Java BIO — это традиционное программирование ввода-вывода на Java, и связанные с ним классы и интерфейсы находятся в java.io.
  • BIO(blocking I/O): Синхронная блокировка, режим реализации сервера - один поток на соединение, то есть, когда у клиента есть запрос на соединение, серверу необходимо запустить поток для обработки.Если соединение ничего не делает, будут созданы ненужные потоки. Накладные расходы можно уменьшить с помощью механизма пула потоков (реализация нескольких клиентов, подключающихся к серверу).

3.2 Рабочий механизм Java BIO

вставьте сюда описание изображения
Разбираемся с процессом программирования БИО

  1. Сервер запускает ServerSocket , регистрирует порт и вызывает метод accpet для наблюдения за клиентским соединением Socket.
  2. Клиент запускает Socket для связи с сервером.По умолчанию сервер должен установить поток для каждого клиента для связи с ним.

3.3 Обзор традиционных примеров программирования BIO

Базовой моделью сетевого программирования является модель клиент/сервер, то есть два процесса взаимодействуют друг с другом, в которых сервер предоставляет информацию о местоположении (привязка IP-адреса и порта), а клиент инициирует операцию подключения к отслеживаемому адресу порта. сервером.Запрос на соединение основан на трехстороннем рукопожатии по протоколу TCP.После успешного соединения обе стороны обмениваются данными через сетевой сокет (Socket).

При разработке традиционной синхронной модели блокировки ServerSocket на стороне сервера отвечает за привязку IP-адреса и запуск порта прослушивания, а Socket на стороне клиента отвечает за инициирование операции подключения. После успешного подключения обе стороны выполняют синхронную блокировку связи через потоки ввода и вывода.

На основе связи в режиме BIO клиент-сервер полностью синхронизирован и полностью связан.

Ситуация с клиентом следующая

Создайте новый пустой проект: iomodule
вставьте сюда описание изображения


вставьте сюда описание изображения
Создайте новый модуль: имя bio-demo
вставьте сюда описание изображения
Создайте новый клиентский класс
Client.java
вставьте сюда описание изображения

Сервер.java

package com.itheima._02bio01;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
/**
    目标: Socket网络编程。

    Java提供了一个包:java.net下的类都是用于网络通信。
    Java提供了基于套接字(端口)Socket的网络通信模式,我们基于这种模式就可以直接实现TCP通信。
    只要用Socket通信,那么就是基于TCP可靠传输通信。

    功能1:客户端发送一个消息,服务端接口一个消息,通信结束!!

    创建客户端对象:
        (1)创建一个Socket的通信管道,请求与服务端的端口连接。
        (2)从Socket管道中得到一个字节输出流。
        (3)把字节流改装成自己需要的流进行数据的发送
    创建服务端对象:
        (1)注册端口
        (2)开始等待接收客户端的连接,得到一个端到端的Socket管道
        (3)从Socket管道中得到一个字节输入流。
        (4)把字节输入流包装成自己需要的流进行数据的读取。

    Socket的使用:
        构造器:public Socket(String host, int port)
        方法:  public OutputStream getOutputStream():获取字节输出流
               public InputStream getInputStream() :获取字节输入流

    ServerSocket的使用:
        构造器:public ServerSocket(int port)

    小结:
        通信是很严格的,对方怎么发你就怎么收,对方发多少你就只能收多少!!

 */
public class ClientDemo {
    
    
    public static void main(String[] args) throws Exception {
    
    
        System.out.println("==客户端的启动==");
        // (1)创建一个Socket的通信管道,请求与服务端的端口连接。
        Socket socket = new Socket("127.0.0.1",8888);
        // (2)从Socket通信管道中得到一个字节输出流。
        OutputStream os = socket.getOutputStream();
        // (3)把字节流改装成自己需要的流进行数据的发送
        PrintStream ps = new PrintStream(os);
        // (4)开始发送消息
        ps.println("我是客户端,我想约你吃小龙虾!!!");
        ps.flush();
    }
}

Я пишу здесь как

public class Client {
    
    
    public static void main(String[] args) {
    
    
        Socket socket = null;
        OutputStream outputStream = null;
        BufferedWriter bufferedWriter = null;
        try {
    
    
            // 1.创建Socket对象,请求服务端的连接
            socket = new Socket(InetAddress.getLocalHost(), 8888);
            // 2.从Socket对象中,获取一个字节输出流
            outputStream = socket.getOutputStream();
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            bufferedWriter.write("今天天气不错");
            bufferedWriter.flush();

        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 关闭流
            try {
    
    
                bufferedWriter.close();
                outputStream.close();
                socket.close();

            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Дело сервера выглядит следующим образом

Создайте новый сервер
вставьте сюда описание изображения
Сервер: Server.java

public class Server {
    
    
    public static void main(String[] args) {
    
    
        Socket socket = null;
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        InputStreamReader inputStreamReader = null;
        try {
    
    
            // 1. 定义一个ServerSocket对象进行服务端端口的注册
            ServerSocket serverSocket = new ServerSocket(8888);
            // 2.监听客户端的Socket连接请求
            socket = serverSocket.accept();
            // 3.从Socket管道中得到一个字节输入流
            inputStream = socket.getInputStream();
            // 4.按照行序来读取数据,把字节输入流包装成一个缓冲字符输入流
            // 通过转换流,将缓冲字节输入流转换成缓冲字符输入流
            inputStreamReader = new InputStreamReader(inputStream);
            bufferedReader = new BufferedReader(inputStreamReader);
            String msg = "";
            while ((msg = bufferedReader.readLine()) != null) {
    
    
                System.out.println("服务端接收到:" + msg);
            }

        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                bufferedReader.close();
                inputStreamReader.close();
                inputStream.close();
                socket.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Сначала запустите сервер, затем запустите клиент, и сервер получит сообщение.
вставьте сюда описание изображения

краткое содержание

  • В приведенном выше сообщении сервер всегда будет ждать сообщения от клиента.Если клиент не отправляет сообщение, сервер всегда будет находиться в состоянии блокировки .
  • При этом сервер получает сообщения по строке, а это значит, что клиент тоже должен отправлять сообщения по строке, иначе сервер перейдет в состояние блокировки ожидания сообщения!

3.4 Многократная отправка и многократное получение сообщений в режиме BIO

В случае с 1.3 клиент может только отправлять сообщения, а сервер может принимать сообщения , но не может повторно принимать и отправлять сообщения.Нам нужно только добавить логику повторной отправки сообщений согласно строке в клиентском кейсе. ! Код дела выглядит следующим образом:

Мы создаем новый пакет demotwo, копируем приведенный выше код и модифицируем
вставьте сюда описание изображения

Клиент.java

Код клиента выглядит следующим образом

package com.itheima._03bio02;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
    目标: Socket网络编程。

    功能1:客户端可以反复发消息,服务端可以反复收消息

    小结:
        通信是很严格的,对方怎么发你就怎么收,对方发多少你就只能收多少!!

 */
ppublic static void main(String[] args) {
    
    
        Socket socket = null;
        OutputStream outputStream = null;
        PrintStream printStream = null;
        Scanner scanner = null;
        try {
    
    
            // 1.创建Socket对象,请求服务端的连接
            socket = new Socket(InetAddress.getLocalHost(), 8888);
            // 2.从Socket对象中,获取一个字节输出流
            outputStream = socket.getOutputStream();
            printStream = new PrintStream(outputStream);
            while (true) {
    
    
                System.out.println("请输入:");
                scanner = new Scanner(System.in);
                String str = scanner.nextLine();
                printStream.println(str);
                printStream.flush();
            }

        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 关闭流
            try {
    
    
                printStream.close();
                scanner.close();
                outputStream.close();
                socket.close();

            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

Код сервера выглядит следующим образом

Сервер.java

package com.itheima._03bio02;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 服务端
 */
public class ServerDemo {
    
    
    public static void main(String[] args) {
    
    
        Socket socket = null;
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        InputStreamReader inputStreamReader = null;
        try {
    
    
            // 1. 定义一个ServerSocket对象进行服务端端口的注册
            ServerSocket serverSocket = new ServerSocket(8888);
            // 2.监听客户端的Socket连接请求
            socket = serverSocket.accept();
            // 3.从Socket管道中得到一个字节输入流
            inputStream = socket.getInputStream();
            // 4.按照行序来读取数据,把字节输入流包装成一个缓冲字符输入流
            // 通过转换流,将缓冲字节输入流转换成缓冲字符输入流
            inputStreamReader = new InputStreamReader(inputStream);
            bufferedReader = new BufferedReader(inputStreamReader);
            String msg = "";
            while ((msg = bufferedReader.readLine()) != null) {
    
    
                System.out.println("服务端接收到:" + msg);
            }

        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                bufferedReader.close();
                inputStreamReader.close();
                inputStream.close();
                socket.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Сначала запустите сервер, затем клиент.
Сообщения, отправленные на стороне клиента
вставьте сюда описание изображения
Сообщения, полученные на стороне сервера
вставьте сюда описание изображения

краткое содержание

  • В этом случае верно, что клиент может отправлять и получать больше
  • Но сервер может обрабатывать только один клиентский запрос, потому что сервер однопоточный. Сообщения могут передаваться только одному клиенту одновременно.

3.5 Прием нескольких клиентов в режиме BIO

В исходном режиме мы пытаемся реализовать сервер и несколько клиентов.Сначала запустите сервер,
вставьте сюда описание изображения
а затем запустите клиент.Здесь вам нужно настроить несколько клиентов:
Нажмите Изменить конфигурации ,
вставьте сюда описание изображения
нажмите Параметры режима ,
вставьте сюда описание изображения
установите флажок Разрешить несколько экземпляров
вставьте сюда описание изображения
и запустите здесь Один клиент
вставьте сюда описание изображения
Здесь мы используем первый клиент для отправки сообщения,
вставьте сюда описание изображения
а затем мы используем второй клиент для отправки сообщения.Мы
вставьте сюда описание изображения
обнаруживаем, что сервер получает только сообщение, отправленное первым клиентом, но не вторым клиентом.Причина
вставьте сюда описание изображения
: только один поток на стороне сервера, и будет обработано только одно сообщение клиента.

обзор

В приведенном выше случае сервер может получать запросы на связь только от одного клиента, так как же сервер должен обрабатывать запросы на передачу сообщений от многих клиентов? клиент инициирует запрос, сервер создает новый поток для обработки запроса клиента, таким образом реализуя модель клиент-один-поток Режим диаграммы выглядит следующим образом: Давайте представим поток, чтобы увидеть: Создайте новый пакет, Затем
вставьте сюда описание изображения
скопируйте предыдущий класс
вставьте сюда описание изображения

Код дела клиента выглядит следующим образом

Клиент

/**
    目标: Socket网络编程。

    功能1:客户端可以反复发,一个服务端可以接收无数个客户端的消息!!

    小结:
         服务器如果想要接收多个客户端,那么必须引入线程,一个客户端一个线程处理!!

 */
public class Client {
    
    
     public static void main(String[] args) {
    
    
        Socket socket = null;
        OutputStream outputStream = null;
        PrintStream printStream = null;
        Scanner scanner = null;
        try {
    
    
            // 1.创建Socket对象,请求服务端的连接
            socket = new Socket(InetAddress.getLocalHost(), 8888);
            // 2.从Socket对象中,获取一个字节输出流
            outputStream = socket.getOutputStream();
            printStream = new PrintStream(outputStream);
            // 3.使用循环不断发送消息给服务端
            while (true) {
    
    
                System.out.println("请输入:");
                scanner = new Scanner(System.in);
                String str = scanner.nextLine();
                printStream.println(str);
                printStream.flush();
            }

        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 关闭流
            try {
    
    
                printStream.close();
                scanner.close();
                outputStream.close();
                socket.close();

            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Код случая сервера выглядит следующим образом

Сервер

/**
 * @ClassName: Server服务端 实现多收取 引入线程
 * 思路:服务端每接收到一个客户端Socket的请求后,都交给一个独立的线程处理客户端的数据交互
 * @Description:
 * @Author: wty
 * @Date: 2023/4/4
 */
public class Server {
    
    
    public static void main(String[] args) {
    
    
        Socket socket = null;

        try {
    
    
            // 1. 定义一个ServerSocket对象进行服务端端口的注册
            ServerSocket serverSocket = new ServerSocket(8888);
            // 2.定义一个死循环,监听客户端的Socket连接请求
            while (true) {
    
    
                socket = serverSocket.accept();
                // 3.创建一个独立的线程来处理与这个客户端的Socket的通信需求
                new ServerThreadReader(socket).start();
            }

        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    

                socket.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Создайте новый класс потока
вставьте сюда описание изображения
ServerThreadReader.java

public class ServerThreadReader extends Thread {
    
    
    private Socket socket;

    public ServerThreadReader(Socket socket) {
    
    
        this.socket = socket;
    }

    @Override
    public void run() {
    
    
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        try {
    
    
            // 3.从Socket管道中得到一个字节输入流
            inputStream = socket.getInputStream();
            // 4.按照行序来读取数据,把字节输入流包装成一个缓冲字符输入流
            // 通过转换流,将缓冲字节输入流转换成缓冲字符输入流
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String msg = "";
            while ((msg = bufferedReader.readLine()) != null) {
    
    
                System.out.println("服务端接收到:" + msg);
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                bufferedReader.close();
                inputStream.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Сначала запустите сервер, затем запустите клиент
вставьте сюда описание изображения
Клиент 1 отправляет сообщение
вставьте сюда описание изображения
Клиент 2 отправляет сообщение
вставьте сюда описание изображения
Сервер получает сообщение
вставьте сюда описание изображения

краткое содержание

  • 1. При получении каждого сокета будет создаваться поток Конкуренция потоков и переключение контекста влияют на производительность;
  • 2. Каждый поток будет занимать место в стеке и ресурсы ЦП;
  • 3. Не каждый сокет выполняет операции ввода-вывода, бессмысленную обработку потоков;
  • 4. Когда одновременный доступ клиента увеличивается. Сервер будет иметь накладные расходы на поток 1: 1. Чем больше количество посещений, система переполнит стек потоков, и создание потока завершится ошибкой, что в конечном итоге приведет к сбою или зависанию процесса, так что он не сможет обеспечить внешние сервисы.

3.6 Псевдоасинхронное программирование ввода/вывода

обзор

В приведенном выше случае: когда одновременный доступ клиента увеличивается. Сервер будет иметь накладные расходы на поток 1: 1. Чем больше количество посещений, система переполнит стек потоков, и создание потока завершится ошибкой, что в конечном итоге приведет к сбою или зависанию процесса, так что он не сможет обеспечить внешние сервисы.

Затем мы используем псевдоасинхронную структуру связи ввода-вывода, которая реализуется пулом потоков и очередью задач Когда клиент обращается, клиентский сокет инкапсулируется в задачу (эта задача реализует задачу потока java.lang.Runnable). interface ) в пул внутренних потоков для обработки. Пул потоков JDK поддерживает очередь сообщений и активные потоки N для обработки задач Socket в очереди сообщений.Поскольку пул потоков может устанавливать размер очереди сообщений и максимальное количество потоков, его использование ресурсов контролируется, независимо от того, сколько клиентов одновременный доступ не приведет к истощению ресурсов и простоям.

Диаграмма выглядит следующим образом:
вставьте сюда описание изображения
Создайте пакет demofour и скопируйте класс demotwo.
вставьте сюда описание изображения

Анализ исходного кода клиента

public class Client {
    
    

    /**
     * 通过PrintStream 伪异步IO编程
     *
     * @param args
     */
    public static void main(String[] args) {
    
    
        Socket socket = null;
        OutputStream outputStream = null;
        PrintStream printStream = null;
        Scanner scanner = null;
        try {
    
    
            // 1.创建Socket对象,请求服务端的连接
            socket = new Socket(InetAddress.getLocalHost(), 8888);
            // 2.从Socket对象中,获取一个字节输出流
            outputStream = socket.getOutputStream();
            printStream = new PrintStream(outputStream);
            while (true) {
    
    
                System.out.println("请输入:");
                scanner = new Scanner(System.in);
                String str = scanner.nextLine();
                printStream.println(str);
                printStream.flush();
            }

        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 关闭流
            try {
    
    
                printStream.close();
                scanner.close();
                outputStream.close();
                socket.close();

            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Класс обработки пула потоков

вставьте сюда описание изображения
ОбработчикСокетСерверПул

// 线程池处理类
public class HandlerSocketThreadPool {
    
    
   
   // 线程池 
   private ExecutorService executor;
   
   public HandlerSocketThreadPool(int maxPoolSize, int queueSize){
    
    
      
      this.executor = new ThreadPoolExecutor(
            3, // 8
            maxPoolSize,  
            120L, 
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(queueSize) );
   }
   
   public void execute(Runnable task){
    
    
      this.executor.execute(task);
   }
}

Анализ исходного кода сервера

Сервер.java

public class Server {
    
    
   public static void main(String[] args) {
    
    
      try {
    
    
         System.out.println("----------服务端启动成功------------");
         ServerSocket ss = new ServerSocket(9999);

         // 一个服务端只需要对应一个线程池
         HandlerSocketThreadPool handlerSocketThreadPool =
               new HandlerSocketThreadPool(3, 1000);

         // 客户端可能有很多个
         while(true){
    
    
            Socket socket = ss.accept() ; // 阻塞式的!
            System.out.println("有人上线了!!");
            // 每次收到一个客户端的socket请求,都需要为这个客户端分配一个
            // 独立的线程 专门负责对这个客户端的通信!!
            handlerSocketThreadPool.execute(new ReaderClientRunnable(socket));
         }

      } catch (Exception e) {
    
    
         e.printStackTrace();
      }
   }

}

Создайте ServerRunnableTarget.java для обработки задач внутри потока на стороне сервера.
вставьте сюда описание изображения

public class ServerRunnableTarget implements Runnable {
    
    
    private Socket socket = null;

    public ServerRunnableTarget(Socket socket) {
    
    
        this.socket = socket;
    }

    @Override
    public void run() {
    
    
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        try {
    
    
            System.out.println("=====服务端启动=====");
            // 3.从Socket管道中得到一个字节输入流
            inputStream = socket.getInputStream();
            // 4.按照行序来读取数据,把字节输入流包装成一个缓冲字符输入流
            // 通过转换流,将缓冲字节输入流转换成缓冲字符输入流
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String msg = "";
            while ((msg = bufferedReader.readLine()) != null) {
    
    
                System.out.println("服务端接收到:" + msg);
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                bufferedReader.close();
                inputStream.close();
                socket.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Сначала запустите клиент, затем запустите сервер.
Клиент 1.
вставьте сюда описание изображения
Клиент 2.
вставьте сюда описание изображения
Клиент 3.
вставьте сюда описание изображения
Клиент 4.
вставьте сюда описание изображения

Затем мы посмотрели на серверную часть
вставьте сюда описание изображения
и обнаружили, что серверная сторона получила только 3 запроса, причина в том, что мы установили количество ядерных потоков равным 3, а оставшиеся клиентские 4 отправились в очередь блокировки ждать своей очереди.
Затем мы отключаем клиент 1
вставьте сюда описание изображения
и снова смотрим на сервер, и обнаруживаем, что запрос от клиента 4 был получен
вставьте сюда описание изображения

краткое содержание

  • Псевдоасинхронный ввод-вывод реализован с использованием пула потоков, поэтому он позволяет избежать проблемы исчерпания ресурсов потока, вызванной созданием независимого потока для каждого запроса, но поскольку нижний уровень все еще использует модель синхронной блокировки, он не может принципиально решить проблему.
  • Если одно сообщение обрабатывается медленно или все потоки в пуле потоков сервера заблокированы, то последующие сообщения ввода-вывода сокета будут поставлены в очередь в очереди. Новые запросы Socket будут отклонены, а на стороне клиента возникнет большое количество тайм-аутов соединения.

3.7 Загрузка файлов в формате BIO

Новый файл пакета
вставьте сюда описание изображения

Цель

Поддерживает загрузку любого типа файла.

развитие клиента

package com.itheima.file;

import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;

/**
    目标:实现客户端上传任意类型的文件数据给服务端保存起来。

 */
public class Client {
    
    
    public static void main(String[] args) {
    
    
        Socket socket = null;
        DataOutputStream dataOutputStream = null;
        FileInputStream fileInputStream = null;
        try {
    
    
            // 1. 请求与服务端的连接Socket
            socket = new Socket(InetAddress.getLocalHost(), 8888);

            // 2.把字节输出流包装成一个数据输出流
            dataOutputStream = new DataOutputStream(socket.getOutputStream());
            // 3.先发送上传文件的后缀给客户端
            dataOutputStream.writeUTF(".PNG");
            // 4.把文件数据发送给服务端进行接收
            fileInputStream = new FileInputStream("D:\\2.PNG");
            byte[] bytes = new byte[1024];
            int length = 0;
            while ((length = fileInputStream.read(bytes)) > 0) {
    
    
                dataOutputStream.write(bytes, 0, length);
                
            }
            dataOutputStream.flush();

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                dataOutputStream.close();
                fileInputStream.close();
                socket.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

разработка сервера

package com.itheima.file;

import java.net.ServerSocket;
import java.net.Socket;

/**
    目标:服务端开发,可以实现接收客户端的任意类型文件,并保存到服务端磁盘。
 */
public class Server {
    
    
    public static void main(String[] args) {
    
    
        ServerSocket serverSocket = null;
        Socket socket = null;
        try {
    
    
            serverSocket = new ServerSocket(8888);
            while (true) {
    
    
                socket = serverSocket.accept();
                // 交给一个独立的线程来处理与这个客户端的文件通信需求
                ServerReaderThread serverReaderThread = new ServerReaderThread(socket);
                serverReaderThread.start();

            }

        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                socket.close();
                serverSocket.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Создайте новый класс, чтобы открыть поток ServerReaderThread.java.
вставьте сюда описание изображения

/**
 * @ClassName: ServerReaderThread
 * @Description:
 * @Author: wty
 * @Date: 2023/4/5
 */

public class ServerReaderThread extends Thread {
    
    
    private Socket socket;

    public ServerReaderThread(Socket socket) {
    
    
        this.socket = socket;
    }

    @Override
    public void run() {
    
    
        DataInputStream dataInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
    
    
            // 1.得到一个数据输入流读取客户端发送的数据
            dataInputStream = new DataInputStream(socket.getInputStream());
            // 2.读取客户端发送过来的文件类型
            String suffix = dataInputStream.readUTF();
            System.out.println("服务端接收到了文件,文件类型" + suffix);
            String fileName = UUID.randomUUID().toString();
            // 3.定义一个字节输出管道负责把客户端发来的文件写进去
            fileOutputStream = new FileOutputStream("D:\\server\\" + fileName + suffix);
            // 4.从数据输入流中读取文件数据,写出到字节输出流中去
            byte[] bytes = new byte[1024];
            int length = 0;
            while ((length = dataInputStream.read(bytes)) > 0) {
    
    
                fileOutputStream.write(bytes, 0, length);
            }

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                dataInputStream.close();
                fileOutputStream.close();
                System.out.println("文件保存成功");
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Запускаем сначала сервер, а потом запускаем клиент.Мы
видим, что фон IDEA говорит нам, что файл успешно сохранен.Перейдем
вставьте сюда описание изображения
к новому файлу
вставьте сюда описание изображения
и попробуем его открыть.Он оказывается поврежден.В качестве
вставьте сюда описание изображения
решения мы нужно только добавить его в клиент.

socket.shutdownOutput();

вставьте сюда описание изображения

Попробуем отправить еще раз, на этот раз успешно Попробуем другой txt файл,
вставьте сюда описание изображения
модифицируем Client.
вставьте сюда описание изображения

вставьте сюда описание изображения

краткое содержание

Как клиент отправляет, как сервер получает

3.9 Идея переадресации портов в режиме Java BIO

Требования: Необходимо понимать, что сообщение клиента может быть отправлено всем клиентам для получения. (Реализация группового чата)
вставьте сюда описание изображения
Создайте новое
имя модуля: bio_chat_demo
вставьте сюда описание изображения
для создания пакета
вставьте сюда описание изображения
Структура проекта выглядит следующим образом:
вставьте сюда описание изображения

развитие клиента

Клиент

/**
 * @ClassName: Client
 * 目标:实现客户端的开发
 * <p>
 * 基本思路:
 * 1、客户端发送消息给服务端
 * 2、客户端可能还需要接收服务端发送过来的消息
 * @Description:
 * @Author: wty
 * @Date: 2023/4/5
 */

public class Client {
    
    
    public static void main(String[] args) {
    
    
        Socket socket = null;
        PrintStream printStream = null;
        try {
    
    
            // 1、创建于服务端的Socket链接
            socket = new Socket(InetAddress.getLocalHost(), 8888);
            // 2、分配一个线程为客户端socket服务接收服务端发来的消息
            new ClientReaderThread(socket).start();

            printStream = new PrintStream(socket.getOutputStream());
            System.out.println("请输入内容:");
            while (true) {
    
    
                Scanner scanner = new Scanner(System.in);
                String str = scanner.nextLine();
                printStream.println(str);
                printStream.flush();
            }

        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            /*try {
                printStream.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }*/
        }
    }
}

Задача клиентского потока ServerReaderThread.java

public class ServerReaderThread extends Thread {
    
    
    private Socket socket = null;

    public ServerReaderThread(Socket socket) {
    
    
        this.socket = socket;
    }

    @Override
    public void run() {
    
    
        BufferedReader bufferedReader = null;
        try {
    
    
            // 1.从socket中获取当前客户端的输入流
            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 2.按照行,读取消息
            String msg = "";
            while ((msg = bufferedReader.readLine()) != null) {
    
    
                // 服务端接收到消息后,要推送给所有的在线socket
                sendMsgToAllClient(msg);
            }
        } catch (IOException e) {
    
    
            System.out.println("当前有人下线");
            // 下线后,从在线集合中移除当前下线用户
            Server.allSocketOnline.remove(this.socket);
        } finally {
    
    
           /* try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }*/
        }
    }

    /**
     * 把当前服务端接收到的消息,推送给所有在线的客户端socket
     *
     * @param msg
     */
    private void sendMsgToAllClient(String msg) {
    
    
        PrintStream printStream = null;
        try {
    
    
            List<Socket> socketOnlines = Server.allSocketOnline;
            for (Socket socketOnline : socketOnlines) {
    
    
                // 如果当前客户端是自己,就没必要发送了
                if (socketOnline.equals(this.socket)) {
    
    
                    continue;
                }
                // 相当于在把消息写入管道,推送给所有在线客户端
                printStream = new PrintStream(socketOnline.getOutputStream());
                printStream.println(Thread.currentThread().getName() + "群发消息:" + msg);
                printStream.flush();
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            //printStream.close();
        }
    }
}

Реализация сервера

Сервер

/**
 * @ClassName: Server BIO模式下的端口转发思想-服务端实现
 * 服务端实现的需求:
 * 1.注册端口
 * 2. 接收客户端的socket连接,交给一个独立的线程来处理
 * 3.把当前连接的客户端socket存入到一个所谓的在线socket集合中保存
 * 4.接收客户端的消息后,推送给当前所有在线的socket接收
 * @Description:
 * @Author: wty
 * @Date: 2023/4/5
 */

public class Server {
    
    
    public static List<Socket> allSocketOnline = new ArrayList<>();

    public static void main(String[] args) {
    
    
        ServerSocket serverSocket = null;
        Socket socket = null;
        try {
    
    
            // 1.注册端口
            serverSocket = new ServerSocket(8888);
            // 2.定义一个循环
            while (true) {
    
    
                socket = serverSocket.accept();
                // 3.把登录的所有客户端存入到一个在线集合中去
                System.out.println("服务端正在监听……");
                allSocketOnline.add(socket);
                // 4.为当前登录成功的socket分配一个独立的线程来处理与之通信
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            /*try {
                socket.close();
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }*/
        }
    }
}

Создайте класс ServerReaderThread.java для обработки логики потоков.

/**
 * @ClassName: ServerReaderThread
 * @Description:
 * @Author: wty
 * @Date: 2023/4/5
 */
public class ServerReaderThread extends Thread {
    
    
    private Socket socket = null;

    public ServerReaderThread(Socket socket) {
    
    
        this.socket = socket;
    }

    @Override
    public void run() {
    
    
        BufferedReader bufferedReader = null;
        try {
    
    
            // 1.从socket中获取当前客户端的输入流
            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 2.按照行,读取消息
            String msg = "";
            while ((msg = bufferedReader.readLine()) != null) {
    
    
                // 服务端接收到消息后,要推送给所有的在线socket
                sendMsgToAllClient(msg);
            }
        } catch (IOException e) {
    
    
            System.out.println("当前有人下线");
            // 下线后,从在线集合中移除当前下线用户
            Server.allSocketOnline.remove(this.socket);
        } finally {
    
    
           /* try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }*/
        }
    }

    /**
     * 把当前服务端接收到的消息,推送给所有在线的客户端socket
     *
     * @param msg
     */
    private void sendMsgToAllClient(String msg) {
    
    
        PrintStream printStream = null;
        try {
    
    
            List<Socket> socketOnlines = Server.allSocketOnline;
            for (Socket socketOnline : socketOnlines) {
    
    
                // 如果当前客户端是自己,就没必要发送了
                if (socketOnline.equals(this.socket)) {
    
    
                    continue;
                }
                // 相当于在把消息写入管道,推送给所有在线客户端
                printStream = new PrintStream(socketOnline.getOutputStream());
                printStream.println(Thread.currentThread().getName() + "群发消息:" + msg);
                printStream.flush();
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            //printStream.close();
        }
    }
}

Примечание. Здесь реализован групповой чат, чтобы все постоянно общались, поэтому не закрывайте трансляцию.
Клиент 1
вставьте сюда описание изображения
Клиент 2
вставьте сюда описание изображения
Клиент 3
вставьте сюда описание изображения
Сервер
вставьте сюда описание изображения

краткое содержание

3.10 Обмен мгновенными сообщениями на основе режима BIO

На основе обмена мгновенными сообщениями в режиме BIO нам необходимо решить взаимодействие клиент-клиент, то есть нам необходимо реализовать логику переадресации сообщений порта между клиентом и клиентом.

Демонстрация функций проекта

Описание кейса проекта

Этот случай проекта представляет собой проект программного обеспечения для обмена мгновенными сообщениями, который подходит для большого дела по укреплению фундамента и является всеобъемлющим. Чтобы изучить случай этого проекта, требуются как минимум следующие технологические точки Java SE:

    1. Объектно-ориентированный дизайн Java, дизайн синтаксиса.
    1. многопоточная технология.
    1. Технология потока ввода-вывода.
    1. Технологии, связанные с сетевыми коммуникациями.
    1. рамка для коллекции.
    1. Мышление развития проекта.
    1. Часто используемые API в Java.

Краткое описание списка функций:

1. Функция входа в систему клиента

  • Вы можете запустить клиент для входа в систему, и вам нужно только ввести имя пользователя и IP-адрес сервера для входа в систему.

2. Количество людей онлайн обновляется в режиме реального времени.

  • После того, как пользователь клиента входит в систему, столбцы контактной информации всех клиентов должны обновляться синхронно.

3. Обновите количество офлайн-пользователей

  • После обнаружения того, что клиент находится в автономном режиме, необходимо синхронно обновить столбцы контактной информации всех клиентов.

4. Групповой чат

  • Сообщения от любого клиента могут быть переданы всем текущим клиентам для получения.

5. Приватный чат

  • Вы можете выбрать сотрудника, нажать кнопку приватного чата, и тогда отправленное сообщение сможет получить только клиент.

6.@сообщение

  • Сотрудник может быть выбран, и сообщения могут быть отправлены @ этому пользователю, но все остальные могут

7. Пользователи сообщений и моменты времени сообщений

  • Сервер может записывать момент времени сообщения пользователя в режиме реального времени, а затем выполнять многоканальную пересылку или выбор сообщения.

Начало проекта и демонстрация

Демонстрация структуры кода проекта.
вставьте сюда описание изображения

Шаги запуска проекта:

  • 1. Во-первых, вам нужно запустить сервер.Нажмите класс ServerChat и щелкните правой кнопкой мыши, чтобы запустить его.Это покажет, что сервер успешно запущен!

  • 2. Во-вторых, щелкните клиентский класс ClientChat и введите IP-адрес сервера и псевдоним текущего клиента во всплывающем окне.
    вставьте сюда описание изображения

  • 3. После входа в систему интерфейс чата выглядит следующим образом, и вы можете выполнять соответствующие операции.

    • Если вы нажмете отправить напрямую, сообщение группового чата будет отправлено по умолчанию.
  • Если вы выберете пользователя в онлайн-списке справа, @сообщение будет отправлено по умолчанию.

    • Если вы выберете пользователя в онлайн-списке справа, а затем нажмете кнопку приватного чата в правом нижнем углу, сообщение приватного чата будет отправлено по умолчанию.

вставьте сюда описание изображения
вставьте сюда описание изображения

Анализ выбора технологии

Случай этого проекта включает в себя случай укрепления основы Java, и конкретные технические моменты заключаются в следующем:

    1. Объектно-ориентированный дизайн Java, дизайн синтаксиса.
    1. многопоточная технология.
    1. Технология потока ввода-вывода.
    1. Технологии, связанные с сетевыми коммуникациями.
    1. рамка для коллекции.
    1. Мышление развития проекта.
    1. Часто используемые API в Java.

дизайн сервера

Сервер получает множественную клиентскую логику

Цель

Сервер должен получить доступ от нескольких клиентов.

Этапы реализации
  • 1. Сервер должен получать несколько клиентов Текущая стратегия, которую мы используем, заключается в том, что один клиент соответствует одному серверному потоку.
  • 2. В дополнение к регистрации порта серверу также необходимо выделить независимый поток для каждого клиента для связи с ним.
Код
  • Основной код сервера в основном выполняет регистрацию порта, принимает клиента и выделяет поток для обработки клиентского запроса.
public class ServerChat {
    
    
    
    /** 定义一个集合存放所有在线的socket  */
	public static Map<Socket, String> onLineSockets = new HashMap<>();

   public static void main(String[] args) {
    
    
      try {
    
    
         /** 1.注册端口   */
         ServerSocket serverSocket = new ServerSocket(Constants.PORT);

         /** 2.循环一直等待所有可能的客户端连接 */
         while(true){
    
    
            Socket socket = serverSocket.accept();
            /**3. 把客户端的socket管道单独配置一个线程来处理 */
            new ServerReader(socket).start();
         }
      } catch (Exception e) {
    
    
         e.printStackTrace();
      }
   }
}
  • Независимый класс потока, назначаемый сервером, отвечает за обработку конвейерного запроса клиентского сокета.
class ServerReader extends Thread {
    
    
   private Socket socket;
   public ServerReader(Socket socket) {
    
    
      this.socket = socket;
   }
   @Override
   public void run() {
    
    
      try {
    
    
       
      } catch (Exception e) {
    
    
            e.printStackTrace();
      }
   }
}

Константный пакет отвечает за настройку порта

public class Constants {
    
    
   /** 常量 */
   public static final int PORT = 7778 ;

}
краткое содержание

В этом разделе показано, что сервер может получать несколько клиентских запросов.

Сервер получает сообщения о входе в систему и отслеживает в автономном режиме

Цель

В предыдущем разделе мы поняли, что сервер может принимать несколько клиентов, а затем сервер может получать несколько клиентских подключений.Далее нам нужно получить сообщение о входе в систему от клиента.

Этапы реализации
  • Сообщение входа в поток клиента должно быть обработано на стороне сервера.
  • Следует отметить, что может быть много видов сообщений, которые сервер должен получать от клиента.
    • Это сообщения входа в систему, сообщения группового чата, сообщения личного чата и @сообщения.
    • Здесь необходимо договориться, что если клиенту необходимо передать тип сообщения перед отправкой сообщения, мы используем флаг значения сигнала (1, 2, 3) для типа.
      • 1 представляет получение сообщения о входе в систему
      • 2 представляет групповую отправку | @сообщение
      • 3 представляет приватные сообщения чата
  • В потоке сервера есть механизм проверки исключений.Как только клиент будет обнаружен в автономном режиме, он будет обработан в механизме исключений, а затем текущий пользователь клиента будет удален, а последний список пользователей будет отправлен обратно. всем клиентам, чтобы обновить количество онлайн-пользователей.
Код
public class ServerReader extends Thread {
    
    
	private Socket socket;
	public ServerReader(Socket socket) {
    
    
		this.socket = socket;
	}

	@Override
	public void run() {
    
    
		DataInputStream dis = null;
		try {
    
    
			dis = new DataInputStream(socket.getInputStream());
			/** 1.循环一直等待客户端的消息 */
			while(true){
    
    
				/** 2.读取当前的消息类型 :登录,群发,私聊 , @消息 */
				int flag = dis.readInt();
				if(flag == 1){
    
    
					/** 先将当前登录的客户端socket存到在线人数的socket集合中   */
					String name = dis.readUTF() ;
					System.out.println(name+"---->"+socket.getRemoteSocketAddress());
					ServerChat.onLineSockets.put(socket, name);
				}
				writeMsg(flag,dis);
			}
		} catch (Exception e) {
    
    
			System.out.println("--有人下线了--");
			// 从在线人数中将当前socket移出去  
			ServerChat.onLineSockets.remove(socket);
			try {
    
    
				// 从新更新在线人数并发给所有客户端 
				writeMsg(1,dis);
			} catch (Exception e1) {
    
    
				e1.printStackTrace();
			}
		}

	}

	private void writeMsg(int flag, DataInputStream dis) throws Exception {
    
    
        // DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); 
		// 定义一个变量存放最终的消息形式 
		String msg = null ;
		if(flag == 1){
    
    
			/** 读取所有在线人数发给所有客户端去更新自己的在线人数列表 */
			/** onlineNames = [波仔,zhangsan,波妞]*/
			StringBuilder rs = new StringBuilder();
			Collection<String> onlineNames = ServerChat.onLineSockets.values();
			// 判断是否存在在线人数 
			if(onlineNames != null && onlineNames.size() > 0){
    
    
				for(String name : onlineNames){
    
    
					rs.append(name+ Constants.SPILIT);
				}
				// 波仔003197♣♣㏘♣④④♣zhangsan003197♣♣㏘♣④④♣波妞003197♣♣㏘♣④④♣
				// 去掉最后的一个分隔符 
				msg = rs.substring(0, rs.lastIndexOf(Constants.SPILIT));

				/** 将消息发送给所有的客户端 */
				sendMsgToAll(flag,msg);
			}
		}else if(flag == 2 || flag == 3){
    
    
			
			}
		}
	}
	
	private void sendMsgToAll(int flag, String msg) throws Exception {
    
    
		// 拿到所有的在线socket管道 给这些管道写出消息
		Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet();
		for(Socket sk :  allOnLineSockets){
    
    
			DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
			dos.writeInt(flag); // 消息类型
			dos.writeUTF(msg);
			dos.flush();
		}
	}
}
краткое содержание
  • Здесь принимается сообщение о входе в систему клиента, а затем извлекаются все имена пользователей, которые в настоящее время находятся в сети, и имена пользователей, которые вошли в систему, и отправляются всем пользователям, находящимся в сети, для обновления списка их онлайн-номеров.

Сервер получает сообщения группового чата

Цель

В предыдущем разделе принимается сообщение о входе клиента, а затем извлекаются все имена пользователей, которые в настоящее время находятся в сети, и имена пользователей, которые вошли в систему, и отправляются всем пользователям, находящимся в сети, для обновления списка их онлайн-номеров. Затем нам нужно получить сообщение группового чата, отправленное клиентом, и отправить его всем клиентам, которые в данный момент находятся в сети.

Этапы реализации
  • Следующим шагом является получение сообщений группового чата от клиента.
  • Следует отметить, что может быть много видов сообщений, которые сервер должен получать от клиента.
    • Это сообщения входа в систему, сообщения группового чата, сообщения личного чата и @сообщения.
    • Здесь необходимо договориться, что если клиенту необходимо передать тип сообщения перед отправкой сообщения, мы используем флаг значения сигнала (1, 2, 3) для типа.
      • 1 представляет получение сообщения о входе в систему
      • 2 представляет групповую отправку | @сообщение
      • 3 представляет приватные сообщения чата
Код
public class ServerReader extends Thread {
    
    
	private Socket socket;
	public ServerReader(Socket socket) {
    
    
		this.socket = socket;
	}

	@Override
	public void run() {
    
    
		DataInputStream dis = null;
		try {
    
    
			dis = new DataInputStream(socket.getInputStream());
			/** 1.循环一直等待客户端的消息 */
			while(true){
    
    
				/** 2.读取当前的消息类型 :登录,群发,私聊 , @消息 */
				int flag = dis.readInt();
				if(flag == 1){
    
    
					/** 先将当前登录的客户端socket存到在线人数的socket集合中   */
					String name = dis.readUTF() ;
					System.out.println(name+"---->"+socket.getRemoteSocketAddress());
					ServerChat.onLineSockets.put(socket, name);
				}
				writeMsg(flag,dis);
			}
		} catch (Exception e) {
    
    
			System.out.println("--有人下线了--");
			// 从在线人数中将当前socket移出去  
			ServerChat.onLineSockets.remove(socket);
			try {
    
    
				// 从新更新在线人数并发给所有客户端 
				writeMsg(1,dis);
			} catch (Exception e1) {
    
    
				e1.printStackTrace();
			}
		}

	}

	private void writeMsg(int flag, DataInputStream dis) throws Exception {
    
    
        // DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); 
		// 定义一个变量存放最终的消息形式 
		String msg = null ;
		if(flag == 1){
    
    
			/** 读取所有在线人数发给所有客户端去更新自己的在线人数列表 */
			/** onlineNames = [波仔,zhangsan,波妞]*/
			StringBuilder rs = new StringBuilder();
			Collection<String> onlineNames = ServerChat.onLineSockets.values();
			// 判断是否存在在线人数 
			if(onlineNames != null && onlineNames.size() > 0){
    
    
				for(String name : onlineNames){
    
    
					rs.append(name+ Constants.SPILIT);
				}
				// 波仔003197♣♣㏘♣④④♣zhangsan003197♣♣㏘♣④④♣波妞003197♣♣㏘♣④④♣
				// 去掉最后的一个分隔符 
				msg = rs.substring(0, rs.lastIndexOf(Constants.SPILIT));

				/** 将消息发送给所有的客户端 */
				sendMsgToAll(flag,msg);
			}
		}else if(flag == 2 || flag == 3){
    
    
			// 读到消息  群发的 或者 @消息
			String newMsg = dis.readUTF() ; // 消息
			// 得到发件人 
			String sendName = ServerChat.onLineSockets.get(socket);
	
			// 内容
			StringBuilder msgFinal = new StringBuilder();
			// 时间  
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE");
			if(flag == 2){
    
    
				msgFinal.append(sendName).append("  ").append(sdf.format(System.currentTimeMillis())).append("\r\n");
				msgFinal.append("    ").append(newMsg).append("\r\n");
				sendMsgToAll(flag,msgFinal.toString());
			}else if(flag == 3){
    
    
	
			}
		}
	}
	
	private void sendMsgToAll(int flag, String msg) throws Exception {
    
    
		// 拿到所有的在线socket管道 给这些管道写出消息
		Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet();
		for(Socket sk :  allOnLineSockets){
    
    
			DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
			dos.writeInt(flag); // 消息类型
			dos.writeUTF(msg);
			dos.flush();
		}
	}
}
краткое содержание
  • Здесь, в зависимости от типа сообщения, оно оценивается как сообщение группового чата, а затем сообщение группового чата отправляется всем онлайн-клиентам, которые в данный момент находятся в сети.

Сервер получает приватные сообщения чата

Цель

В предыдущем разделе мы получили сообщение группового чата, отправленное клиентом, и отправили его всем онлайн-клиентам, которые в данный момент находятся в сети. Далее нам нужно решить логику отправки сообщения приватного чата.

Этапы реализации
  • Решите логику отправки сообщений частного чата, сообщения частного чата должны знать, чтобы отправлять их конкретному клиенту.
  • Мы можем получить имя пользователя приватного чата, отправленное клиентом, найти канал сокета пользователя в соответствии с именем пользователя, а затем отдельно отправить сообщение в канал сокета.
  • Следует отметить, что может быть много видов сообщений, которые сервер должен получать от клиента.
    • Это сообщения входа в систему, сообщения группового чата, сообщения личного чата и @сообщения.
    • Здесь необходимо договориться, что если клиенту необходимо передать тип сообщения перед отправкой сообщения, мы используем флаг значения сигнала (1, 2, 3) для типа.
      • 1 представляет получение сообщения о входе в систему
      • 2 представляет групповую отправку | @сообщение
      • 3 представляет приватные сообщения чата
Код
public class ServerReader extends Thread {
    
    
	private Socket socket;
	public ServerReader(Socket socket) {
    
    
		this.socket = socket;
	}

	@Override
	public void run() {
    
    
		DataInputStream dis = null;
		try {
    
    
			dis = new DataInputStream(socket.getInputStream());
			/** 1.循环一直等待客户端的消息 */
			while(true){
    
    
				/** 2.读取当前的消息类型 :登录,群发,私聊 , @消息 */
				int flag = dis.readInt();
				if(flag == 1){
    
    
					/** 先将当前登录的客户端socket存到在线人数的socket集合中   */
					String name = dis.readUTF() ;
					System.out.println(name+"---->"+socket.getRemoteSocketAddress());
					ServerChat.onLineSockets.put(socket, name);
				}
				writeMsg(flag,dis);
			}
		} catch (Exception e) {
    
    
			System.out.println("--有人下线了--");
			// 从在线人数中将当前socket移出去  
			ServerChat.onLineSockets.remove(socket);
			try {
    
    
				// 从新更新在线人数并发给所有客户端 
				writeMsg(1,dis);
			} catch (Exception e1) {
    
    
				e1.printStackTrace();
			}
		}

	}

	private void writeMsg(int flag, DataInputStream dis) throws Exception {
    
    
        // DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); 
		// 定义一个变量存放最终的消息形式 
		String msg = null ;
		if(flag == 1){
    
    
			/** 读取所有在线人数发给所有客户端去更新自己的在线人数列表 */
			/** onlineNames = [波仔,zhangsan,波妞]*/
			StringBuilder rs = new StringBuilder();
			Collection<String> onlineNames = ServerChat.onLineSockets.values();
			// 判断是否存在在线人数 
			if(onlineNames != null && onlineNames.size() > 0){
    
    
				for(String name : onlineNames){
    
    
					rs.append(name+ Constants.SPILIT);
				}
				// 波仔003197♣♣㏘♣④④♣zhangsan003197♣♣㏘♣④④♣波妞003197♣♣㏘♣④④♣
				// 去掉最后的一个分隔符 
				msg = rs.substring(0, rs.lastIndexOf(Constants.SPILIT));

				/** 将消息发送给所有的客户端 */
				sendMsgToAll(flag,msg);
			}
		}else if(flag == 2 || flag == 3){
    
    
			// 读到消息  群发的 或者 @消息
			String newMsg = dis.readUTF() ; // 消息
			// 得到发件人 
			String sendName = ServerChat.onLineSockets.get(socket);
	
			// 内容
			StringBuilder msgFinal = new StringBuilder();
			// 时间  
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE");
			if(flag == 2){
    
    
				msgFinal.append(sendName).append("  ").append(sdf.format(System.currentTimeMillis())).append("\r\n");
				msgFinal.append("    ").append(newMsg).append("\r\n");
				sendMsgToAll(flag,msgFinal.toString());
			}else if(flag == 3){
    
    
			msgFinal.append(sendName).append("  ").append(sdf.format(System.currentTimeMillis())).append("对您私发\r\n");
				msgFinal.append("    ").append(newMsg).append("\r\n");
				// 私发 
				// 得到给谁私发 
				String destName = dis.readUTF();
				sendMsgToOne(destName,msgFinal.toString());
			}
		}
	}
	/**
	 * @param destName 对谁私发 
	 * @param msg 发的消息内容 
	 * @throws Exception
	 */
	private void sendMsgToOne(String destName, String msg) throws Exception {
    
    
		// 拿到所有的在线socket管道 给这些管道写出消息
		Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet();
		for(Socket sk :  allOnLineSockets){
    
    
			// 得到当前需要私发的socket 
			// 只对这个名字对应的socket私发消息
			if(ServerChat.onLineSockets.get(sk).trim().equals(destName)){
    
    
				DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
				dos.writeInt(2); // 消息类型
				dos.writeUTF(msg);
				dos.flush();
			}
		}

	}
	

	private void sendMsgToAll(int flag, String msg) throws Exception {
    
    
		// 拿到所有的在线socket管道 给这些管道写出消息
		Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet();
		for(Socket sk :  allOnLineSockets){
    
    
			DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
			dos.writeInt(flag); // 消息类型
			dos.writeUTF(msg);
			dos.flush();
		}
	}
}
краткое содержание
  • В этом разделе мы решили логику отправки сообщений приватного чата.Сообщения приватного чата должны быть отправлены в определенный канал клиентского сокета.
  • Мы можем получить имя пользователя приватного чата, отправленное клиентом, найти канал сокета пользователя в соответствии с именем пользователя, а затем отдельно отправить сообщение в канал сокета.

дизайн клиента

Запустите клиентский интерфейс, войдите в систему, обновите онлайн

Цель

Запустите клиентский интерфейс , войдите в систему и обновите список онлайн-пользователей.

Этапы реализации
  • Интерфейс клиента в основном представляет собой графический интерфейс, а главная страница разделена на интерфейс входа в систему, окно чата и список онлайн-пользователей.
  • Читатели интерфейса GUI могут копировать и использовать сами.
  • Логин После ввода ip сервера и имени пользователя необходимо запросить логин на сервере, а затем сразу же назначить поток чтения текущему клиенту для обработки прочитанного сообщения данных клиента. Потому что клиент может в любой момент получить различную информацию о мгновенных сообщениях, пересылаемую с сервера.
  • После завершения входа клиента сервер немедленно отправит последний список пользователей клиенту после получения имени пользователя для входа.
Код

Код тела клиента:

public class ClientChat implements ActionListener {
    
    
   /** 1.设计界面  */
   private JFrame win = new JFrame();
   /** 2.消息内容框架 */
   public JTextArea smsContent =new JTextArea(23 , 50);
   /** 3.发送消息的框  */
   private JTextArea smsSend = new JTextArea(4,40);
   /** 4.在线人数的区域  */
   /** 存放人的数据 */
   /** 展示在线人数的窗口 */
   public JList<String> onLineUsers = new JList<>();

   // 是否私聊按钮
   private JCheckBox isPrivateBn = new JCheckBox("私聊");
   // 消息按钮
   private JButton sendBn  = new JButton("发送");

   // 登录界面
   private JFrame loginView;

   private JTextField ipEt , nameEt , idEt;

   private Socket socket ;

   public static void main(String[] args) {
    
    
      new ClientChat().initView();

   }

   private void initView() {
    
    
      /** 初始化聊天窗口的界面 */
      win.setSize(650, 600);

      /** 展示登录界面  */
      displayLoginView();

      /** 展示聊天界面 */
      //displayChatView();

   }

   private void displayChatView() {
    
    

      JPanel bottomPanel = new JPanel(new BorderLayout());
      //-----------------------------------------------
      // 将消息框和按钮 添加到窗口的底端
      win.add(bottomPanel, BorderLayout.SOUTH);
      bottomPanel.add(smsSend);
      JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
      btns.add(sendBn);
      btns.add(isPrivateBn);
      bottomPanel.add(btns, BorderLayout.EAST);
      //-----------------------------------------------
      // 给发送消息按钮绑定点击事件监听器
      // 将展示消息区centerPanel添加到窗口的中间
      smsContent.setBackground(new Color(0xdd,0xdd,0xdd));
      // 让展示消息区可以滚动。
      win.add(new JScrollPane(smsContent), BorderLayout.CENTER);
      smsContent.setEditable(false);
      //-----------------------------------------------
      // 用户列表和是否私聊放到窗口的最右边
      Box rightBox = new Box(BoxLayout.Y_AXIS);
      onLineUsers.setFixedCellWidth(120);
      onLineUsers.setVisibleRowCount(13);
      rightBox.add(new JScrollPane(onLineUsers));
      win.add(rightBox, BorderLayout.EAST);
      //-----------------------------------------------
      // 关闭窗口退出当前程序
      win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      win.pack();  // swing 加上这句 就可以拥有关闭窗口的功能
      /** 设置窗口居中,显示出来  */
      setWindowCenter(win,650,600,true);
      // 发送按钮绑定点击事件
      sendBn.addActionListener(this);
   }

   private void displayLoginView(){
    
    

      /** 先让用户进行登录
       *  服务端ip
       *  用户名
       *  id
       *  */
      /** 显示一个qq的登录框     */
      loginView = new JFrame("登录");
      loginView.setLayout(new GridLayout(3, 1));
      loginView.setSize(400, 230);

      JPanel ip = new JPanel();
      JLabel label = new JLabel("   IP:");
      ip.add(label);
      ipEt = new JTextField(20);
      ip.add(ipEt);
      loginView.add(ip);

      JPanel name = new JPanel();
      JLabel label1 = new JLabel("姓名:");
      name.add(label1);
      nameEt = new JTextField(20);
      name.add(nameEt);
      loginView.add(name);

      JPanel btnView = new JPanel();
      JButton login = new JButton("登陆");
      btnView.add(login);
      JButton cancle = new JButton("取消");
      btnView.add(cancle);
      loginView.add(btnView);
      // 关闭窗口退出当前程序
      loginView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setWindowCenter(loginView,400,260,true);

      /** 给登录和取消绑定点击事件 */
      login.addActionListener(this);
      cancle.addActionListener(this);

   }

   private static void setWindowCenter(JFrame frame, int width , int height, boolean flag) {
    
    
      /** 得到所在系统所在屏幕的宽高 */
      Dimension ds = frame.getToolkit().getScreenSize();

      /** 拿到电脑的宽 */
      int width1 = ds.width;
      /** 高 */
      int height1 = ds.height ;

      System.out.println(width1 +"*" + height1);
      /** 设置窗口的左上角坐标 */
      frame.setLocation(width1/2 - width/2, height1/2 -height/2);
      frame.setVisible(flag);
   }

   @Override
   public void actionPerformed(ActionEvent e) {
    
    
      /** 得到点击的事件源 */
      JButton btn = (JButton) e.getSource();
      switch(btn.getText()){
    
    
         case "登陆":
            String ip = ipEt.getText().toString();
            String name = nameEt.getText().toString();
            // 校验参数是否为空
            // 错误提示
            String msg = "" ;
            // 12.1.2.0
            // \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\
            if(ip==null || !ip.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
    
    
               msg = "请输入合法的服务端ip地址";
            }else if(name==null || !name.matches("\\S{1,}")){
    
    
               msg = "姓名必须1个字符以上";
            }

            if(!msg.equals("")){
    
    
               /** msg有内容说明参数有为空 */
               // 参数一:弹出放到哪个窗口里面
               JOptionPane.showMessageDialog(loginView, msg);
            }else{
    
    
               try {
    
    
                  // 参数都合法了
                  // 当前登录的用户,去服务端登陆
                  /** 先把当前用户的名称展示到界面 */
                  win.setTitle(name);
                  // 去服务端登陆连接一个socket管道
                  socket = new Socket(ip, Constants.PORT);

                  //为客户端的socket分配一个线程 专门负责收消息
                  new ClientReader(this,socket).start();

                  // 带上用户信息过去
                  DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                  dos.writeInt(1); // 登录消息
                  dos.writeUTF(name.trim());
                  dos.flush();

                  // 关系当前窗口 弹出聊天界面
                  loginView.dispose(); // 登录窗口销毁
                  displayChatView(); // 展示了聊天窗口了


               } catch (Exception e1) {
    
    
                  e1.printStackTrace();
               }
            }
            break;
         case "取消":
            /** 退出系统 */
            System.exit(0);
            break;
         case "发送":
            
            break;

      }

   }
}

Поток обработки клиентского сокета:

public class ClientReader extends Thread {
    
    

   private Socket socket;
    // 接收客户端界面,方便收到消息后,更新界面数据。
   private ClientChat clientChat ;

   public ClientReader(ClientChat clientChat, Socket socket) {
    
    
      this.clientChat = clientChat;
      this.socket = socket;
   }

   @Override
   public void run() {
    
    
      try {
    
    
         DataInputStream dis = new DataInputStream(socket.getInputStream());
         /** 循环一直等待客户端的消息 */
         while(true){
    
    
            /** 读取当前的消息类型 :登录,群发,私聊 , @消息 */
            int flag = dis.readInt();
            if(flag == 1){
    
    
               // 在线人数消息回来了
               String nameDatas = dis.readUTF();
               // 展示到在线人数的界面
               String[] names = nameDatas.split(Constants.SPILIT);

               clientChat.onLineUsers.setListData(names);
            }else if(flag == 2){
    
    
              
            }
         }
      } catch (Exception e) {
    
    
         e.printStackTrace();
      }
   }
}
краткое содержание
  • Здесь поясняется, что если клиентский интерфейс активирован и активирована функция входа в систему, сервер ответит онлайн-списком и обновит количество онлайн-пользователей для клиента после получения нового сообщения для входа!

Клиент отправляет логику сообщения

Цель

Клиент отправляет сообщения группового чата, сообщения @ и сообщения личного чата.

Этапы реализации
  • После запуска клиента вам необходимо отправить сообщения группового чата, сообщения @ и сообщения личного чата с помощью кнопки отправки в интерфейсе чата.
    вставьте сюда описание изображения

  • Если вы нажмете отправить напрямую, сообщение группового чата будет отправлено по умолчанию.

  • Если вы выберете пользователя в онлайн-списке справа, @сообщение будет отправлено по умолчанию.

  • Если вы выберете пользователя в онлайн-списке справа, а затем нажмете кнопку приватного чата в правом нижнем углу, сообщение приватного чата будет отправлено по умолчанию.

Код

Код тела клиента:

public class ClientChat implements ActionListener {
    
    
	/** 1.设计界面  */
	private JFrame win = new JFrame();
	/** 2.消息内容框架 */
	public JTextArea smsContent =new JTextArea(23 , 50);
	/** 3.发送消息的框  */
	private JTextArea smsSend = new JTextArea(4,40);
	/** 4.在线人数的区域  */
	/** 存放人的数据 */
	/** 展示在线人数的窗口 */
	public JList<String> onLineUsers = new JList<>();

	// 是否私聊按钮
	private JCheckBox isPrivateBn = new JCheckBox("私聊");
	// 消息按钮
	private JButton sendBn  = new JButton("发送");

	// 登录界面
	private JFrame loginView;

	private JTextField ipEt , nameEt , idEt;

	private Socket socket ;

	public static void main(String[] args) {
    
    
		new ClientChat().initView();

	}

	private void initView() {
    
    
		/** 初始化聊天窗口的界面 */
		win.setSize(650, 600);

		/** 展示登录界面  */
		displayLoginView();

		/** 展示聊天界面 */
		//displayChatView();

	}

	private void displayChatView() {
    
    

		JPanel bottomPanel = new JPanel(new BorderLayout());
		//-----------------------------------------------
		// 将消息框和按钮 添加到窗口的底端
		win.add(bottomPanel, BorderLayout.SOUTH);
		bottomPanel.add(smsSend);
		JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
		btns.add(sendBn);
		btns.add(isPrivateBn);
		bottomPanel.add(btns, BorderLayout.EAST);
		//-----------------------------------------------
		// 给发送消息按钮绑定点击事件监听器
		// 将展示消息区centerPanel添加到窗口的中间
		smsContent.setBackground(new Color(0xdd,0xdd,0xdd));
		// 让展示消息区可以滚动。
		win.add(new JScrollPane(smsContent), BorderLayout.CENTER);
		smsContent.setEditable(false);
		//-----------------------------------------------
		// 用户列表和是否私聊放到窗口的最右边
		Box rightBox = new Box(BoxLayout.Y_AXIS);
		onLineUsers.setFixedCellWidth(120);
		onLineUsers.setVisibleRowCount(13);
		rightBox.add(new JScrollPane(onLineUsers));
		win.add(rightBox, BorderLayout.EAST);
		//-----------------------------------------------
		// 关闭窗口退出当前程序
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.pack();  // swing 加上这句 就可以拥有关闭窗口的功能
		/** 设置窗口居中,显示出来  */
		setWindowCenter(win,650,600,true);
		// 发送按钮绑定点击事件
		sendBn.addActionListener(this);
	}

	private void displayLoginView(){
    
    

		/** 先让用户进行登录
		 *  服务端ip
		 *  用户名
		 *  id
		 *  */
		/** 显示一个qq的登录框     */
		loginView = new JFrame("登录");
		loginView.setLayout(new GridLayout(3, 1));
		loginView.setSize(400, 230);

		JPanel ip = new JPanel();
		JLabel label = new JLabel("   IP:");
		ip.add(label);
		ipEt = new JTextField(20);
		ip.add(ipEt);
		loginView.add(ip);

		JPanel name = new JPanel();
		JLabel label1 = new JLabel("姓名:");
		name.add(label1);
		nameEt = new JTextField(20);
		name.add(nameEt);
		loginView.add(name);

		JPanel btnView = new JPanel();
		JButton login = new JButton("登陆");
		btnView.add(login);
		JButton cancle = new JButton("取消");
		btnView.add(cancle);
		loginView.add(btnView);
		// 关闭窗口退出当前程序
		loginView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setWindowCenter(loginView,400,260,true);

		/** 给登录和取消绑定点击事件 */
		login.addActionListener(this);
		cancle.addActionListener(this);

	}

	private static void setWindowCenter(JFrame frame, int width , int height, boolean flag) {
    
    
		/** 得到所在系统所在屏幕的宽高 */
		Dimension ds = frame.getToolkit().getScreenSize();

		/** 拿到电脑的宽 */
		int width1 = ds.width;
		/** 高 */
		int height1 = ds.height ;

		System.out.println(width1 +"*" + height1);
		/** 设置窗口的左上角坐标 */
		frame.setLocation(width1/2 - width/2, height1/2 -height/2);
		frame.setVisible(flag);
	}

	@Override
	public void actionPerformed(ActionEvent e) {
    
    
		/** 得到点击的事件源 */
		JButton btn = (JButton) e.getSource();
		switch(btn.getText()){
    
    
			case "登陆":
				String ip = ipEt.getText().toString();
				String name = nameEt.getText().toString();
				// 校验参数是否为空
				// 错误提示
				String msg = "" ;
				// 12.1.2.0
				// \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\
				if(ip==null || !ip.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
    
    
					msg = "请输入合法的服务端ip地址";
				}else if(name==null || !name.matches("\\S{1,}")){
    
    
					msg = "姓名必须1个字符以上";
				}

				if(!msg.equals("")){
    
    
					/** msg有内容说明参数有为空 */
					// 参数一:弹出放到哪个窗口里面
					JOptionPane.showMessageDialog(loginView, msg);
				}else{
    
    
					try {
    
    
						// 参数都合法了
						// 当前登录的用户,去服务端登陆
						/** 先把当前用户的名称展示到界面 */
						win.setTitle(name);
						// 去服务端登陆连接一个socket管道
						socket = new Socket(ip, Constants.PORT);

						//为客户端的socket分配一个线程 专门负责收消息
						new ClientReader(this,socket).start();

						// 带上用户信息过去
						DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
						dos.writeInt(1); // 登录消息
						dos.writeUTF(name.trim());
						dos.flush();

						// 关系当前窗口 弹出聊天界面
						loginView.dispose(); // 登录窗口销毁
						displayChatView(); // 展示了聊天窗口了


					} catch (Exception e1) {
    
    
						e1.printStackTrace();
					}
				}
				break;
			case "取消":
				/** 退出系统 */
				System.exit(0);
				break;
			case "发送":
				// 得到发送消息的内容
				String msgSend = smsSend.getText().toString();
				if(!msgSend.trim().equals("")){
    
    
					/** 发消息给服务端 */
					try {
    
    
						// 判断是否对谁发消息
						String selectName = onLineUsers.getSelectedValue();
						int flag = 2 ;// 群发 @消息
						if(selectName!=null&&!selectName.equals("")){
    
    
							msgSend =("@"+selectName+","+msgSend);
							/** 判断是否选中了私法 */
							if(isPrivateBn.isSelected()){
    
    
								/** 私法 */
								flag = 3 ;//私发消息
							}

						}

						DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
						dos.writeInt(flag); // 群发消息  发送给所有人
						dos.writeUTF(msgSend);
						if(flag == 3){
    
    
							// 告诉服务端我对谁私发
							dos.writeUTF(selectName.trim());
						}
						dos.flush();

					} catch (Exception e1) {
    
    
						e1.printStackTrace();
					}

				}
				smsSend.setText(null);
				break;

		}

	}
}

Поток обработки клиентского сокета:

class ClientReader extends Thread {
    
    

	private Socket socket;
	private ClientChat clientChat ;

	public ClientReader(ClientChat clientChat, Socket socket) {
    
    
		this.clientChat = clientChat;
		this.socket = socket;
	}

	@Override
	public void run() {
    
    
		try {
    
    
			DataInputStream dis = new DataInputStream(socket.getInputStream());
			/** 循环一直等待客户端的消息 */
			while(true){
    
    
				/** 读取当前的消息类型 :登录,群发,私聊 , @消息 */
				int flag = dis.readInt();
				if(flag == 1){
    
    
					// 在线人数消息回来了
					String nameDatas = dis.readUTF();
					// 展示到在线人数的界面
					String[] names = nameDatas.split(Constants.SPILIT);

					clientChat.onLineUsers.setListData(names);
				}else if(flag == 2){
    
    
					//群发,私聊 , @消息 都是直接显示的。
					String msg = dis.readUTF() ;
					clientChat.smsContent.append(msg);
					// 让消息界面滾動到底端
					clientChat.smsContent.setCaretPosition(clientChat.smsContent.getText().length());
				}
			}
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
	}
}
краткое содержание
  • Здесь клиент отправляет сообщения группового чата, сообщения @ и сообщения личного чата.
  • Если вы нажмете отправить напрямую, сообщение группового чата будет отправлено по умолчанию.
  • Если вы выберете пользователя в онлайн-списке справа, @сообщение будет отправлено по умолчанию.
  • Если вы выберете пользователя в онлайн-списке справа, а затем нажмете кнопку приватного чата в правом нижнем углу, сообщение приватного чата будет отправлено по умолчанию.

Supongo que te gusta

Origin blog.csdn.net/sinat_38316216/article/details/129959256
Recomendado
Clasificación