Серия Основы Java: Программирование сокетов

Java

мирской скиталец: Программисты, занимающиеся техническими исследованиями.

Говоря о предыдущем

Теоретическая база без реальных боевых дел - это все хулиганство, поэтому сегодня я в основном хочу, чтобы все углубили свое понимание прошлого через дела здесь

В этом разделе мы шаг за шагом реализуем апплет однорангового чата.

Реализация сокета в Java

InetAddress

InetAddressЭто инкапсуляция IP-адресов в Java. Этот класс является базовым классом.ServerSocketа такжеDatagramSocketНе могу без этого класса

InetAddressне могу пройтиnewСпособ инициализации может быть вызван только путем предоставления статического метода, который он предоставляет:

// 获取本地地址
InetAddress localHost = InetAddress.getLocalHost();

InetAddress的方法

вотInetAddressНекоторые методы:

// 主机名:DESKTOP-ATG4KKE
System.out.println("主机名:" + localHost.getHostName());

// IP地址:192.168.87.1
System.out.println("IP地址:" + localHost.getHostAddress());

// 是否正常:true
System.out.println("是否正常:" + localHost.isReachable(5000));

Вот результат, когда я тестирую,

оisReachable()метод, чтобы определить, доступен ли адрес, поэтому мы можем выполнить некоторые операции проверки работоспособности, такие как:

// 通过主机IP或者域名来得到InetAddress对象
InetAddress inetAddress = InetAddress.getByName("192.168.87.139");
System.out.println("是否正常:" + inetAddress.isReachable(5000));

Старайтесь подключаться к хосту как можно чаще в течение 5 с. Если нет, хост считается недоступным. Это ограниченомежсетевой экрана такжеконфигурация сервера

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

PS: Сетевые операции в производственной среде не будут использовать вещи из этого раздела.В большинстве случаевNetty

ServerSocket

ServerSocketэто серверный сокет, основанный наTCP/IPДостигнуто по соглашению

инициализация

Обычно мы строим так:

ServerSocket serverSocket = new ServerSocket(9999);

ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(9999));

На этом инициализация сервера завершена, и порт9999связывать

ожидание соединения

Если клиент хочет иServerSocketЧтобы установить соединение, нам нужно сделать это

for(;;) {
    Socket socket = serverSocket.accpet();
    // Socket[addr=/0:0:0:0:0:0:0:1,port=62445,localport=9999]
    System.out.println(socket);
}

accpet()слушает сServerSocketУстановленное соединение, этот метод является методом блокировки и всегда будет ждать установления соединения.

Если есть входящее соединение, мы можем получить текущий доступ через возвращаемое значениеSocket

коммуникация

Передача данных в сети фактически соответствуетIOСпособ передать поток, но мы можем получить только поток байтов:

InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();

InputStreamчитать данные,OutputStreamВыписывайте данные. Мы ввели эти основные операции в предыдущем потоке IO, поэтому я больше не скажу здесь.

Здесь для повышения эффективности можно использовать包装流или处理流чтобы справиться с этим, который также был введен ранее

Полный небольшой пример

Собственно вот,ServerSocketКлючевое введение закончено, давайте сделаем небольшой пример:

  • Когда клиент подключается, вернитесь к клиенту:Hello World
public class _ServerSocket {
    // 用来存储请求客户端和Socket之间的对应关系
    static Map<String, Socket> MAP = new HashMap<>(); 
    
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(9999));

            for (; ; ) {
                String token = UUID.randomUUID().toString().replace("-", "").toLowerCase();
                
                Socket socket = serverSocket.accept();
                // 对应
                MAP.put(token, socket);
                
                outHtml(socket);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void outHtml(Socket socket) {
        OutputStream outputStream = null;
        try {
            outputStream = socket.getOutputStream();
            outputStream.write(("HTTP/1.1 200 OK\n\nHello World").getBytes("UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

HTTP/1.1 200 OK\n\nHello World\n

Это тип возвращаемого значения в протоколе HTTP. Фронтальная часть представляет собой фиксированный формат ответа.Hello Worldто, что на самом деле возвращается, так что нашServerSocketможно получить через браузер

ServerSocket

Socket

SocketОн принадлежит клиентскому сокету, и другие операции можно выполнять, только предварительно установив соединение с серверным сокетом.Socketочень прост в использовании

установить соединение

Socket socket = new Socket("127.0.0.1", 9999);

// 验证是否连接成功
if (socket.isConnected()) {
    System.out.println("到服务端连接成功");
}

Это один из способов построения, чаще так

После того, как соединение с сервером будет успешно установлено, последующие операции аналогичныServerSocketиз通信步骤Это то же самое, здесь больше нет ерунды

Вот полный пример для консолидации

Пример: одноранговый чат TCP

Сервер

public class Server {
    /**
     * 将客户端标识和socket关联起来
     */
    private static final Map<String, Socket> SOCKET_MAP = new HashMap<>();
    /**
     * 反向关联,用来获取标识
     */
    private static final Map<Socket, String> SOCKET_TOKEN_MAP = new HashMap<>();

    public static void main(String[] args) throws IOException {
        /**
         * 开启ServerSocket并监听9999端口
         */
        ServerSocket serverSocket = new ServerSocket(9999);

        for (;;) {
            /**
             * 等待客户端连接
             */
            Socket socket = serverSocket.accept();

            /**
             * IO读取是阻塞式方法,所以需要开启新线程,这里可以优化成线程池
             */
            new Thread(() -> {
                try {
                    saveToMap(socket);
                    getClientMsg(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    /**
     * 绑定SOCKET
     */
    private static void saveToMap(Socket socket) throws IOException {
        String token = StringUtil.uuid();
        SOCKET_MAP.put(token, socket);
        SOCKET_TOKEN_MAP.put(socket, token);

        System.out.println("---客户端连接成功,编号:" + token);
        System.out.println("当前用户:" + SOCKET_MAP.size());

        /**
         * 因为没有登录,所以这里要告知客户端自己的标识
         */
        send(token, token, token);
    }

    /**
     * 获取客户端发送过来的消息,并发送出指定指定的客户端
     */
    private static void getClientMsg(Socket socket) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = "";
        while ((line = reader.readLine()) != null) {
            // 读取到一行以后,从这里发送出去
            send(socket, line);
        }
    }

    /**
     * 发送消息
     */
    private static void send(Socket socket, String line) throws IOException {
        String[] s = line.split("#");
        final String from = SOCKET_TOKEN_MAP.get(socket);
        send(s[0], s[1], from);
    }

    /**
     * 发送消息
     * @param token
     * @param msg
     * @param from 这里在目标客户端展示
     * @throws IOException
     */
    private static void send(String token, String msg, String from) throws IOException {
        Socket sk = SOCKET_MAP.get(token);

        if (null == sk)
            return;

        String s = from + ":" + msg;
        System.out.println("---发送给客户端:" + s );
        // 字符流输出
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));
        writer.write(s);
        writer.newLine();
        writer.flush();

    }
}

клиент

public class Client {

    public static void main(String[] args) throws IOException {
        /**
         * 连接到服务端
         */
        Socket socket = new Socket("127.0.0.1", 9999);

        /**
         * 开新线程读取消息,可以优化
         */
        new Thread(() -> {
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String line = "";
                while (StringUtil.isNotBlank(line = reader.readLine())) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        /**
         * 从控制台写入消息并发送出去
         */
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String next = scanner.next();
            send(next, socket);
        }
    }

    private static void send(String msg, Socket socket) throws IOException {
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        writer.write(msg);
        writer.newLine();
        writer.flush();
    }
}

Код прошел проверку, комментарии очень понятные, можете попробовать.标识#消息формат может быть点对点聊天.

Если хочешь群聊:

  • Сохраните Socket в коллекцию, а затем зациклите коллекцию, это очень просто

давно бесполезноSocketЯ написал программу для чата и чуть не сдался

использовать в следующий разNettyнаписать,NettyСравниватьSocketнамного удобнее

DatagramSocket

DatagramSocketсокет для отправки и получения пакетов дейтаграмм, основанный наUDP协议реализация ниже. Согласно официальному представлению в классе:

Сокет дейтаграммы — это точка отправки или получения для службы доставки пакетов. Каждый пакет, отправленный или полученный через сокет дейтаграммы, адресуется и маршрутизируется индивидуально. Несколько пакетов, отправленных с одной машины на другую, могут маршрутизироваться по-разному и могут поступать в любом порядке.

мы также можем понятьUDP协议характеристики.

DatagramPacket

Этот класс представляет数据报包,существуетDatagramSocketЭтот класс используется для передачи и получения данных, таких как:

  • Получить данные
byte[] buffer = new byte[1024];
DatagramPacket p = new DatagramPacket(buffer, buffer.length);
  • отправить данные
DatagramPacket p = new DatagramPacket("123".getBytes(), "123".getBytes().length, InetAddress.getByName("localhost"), 9999);

отправить данные,DatagramPacketВам нужно указать IP и порт принимающей стороны, чтобы вы могли отправить его

Давайте посмотрим, как использовать

инициализация

DatagramSocket socket = new DatagramSocket(9999);

DatagramSocket s = new DatagramSocket(null);
s.bind(new InetSocketAddress(9999));

Инициализировать можно обоими способами, разницы нет

получить сообщение

byte[] buffer = new byte[1024];
DatagramPacket p = new DatagramPacket(buffer, buffer.length);

socket.receive(p);

System.out.println(new String(p.getData(), 0, p.getLength()));

согласно сDatagramPacketПолучить параметры, построитьbyte[], а затем позвонитеreceive(), так что сообщение получено

receive()Это метод блокировки, и он будет продолжать выполняться только при наличии сообщения.

отправлять сообщения

DatagramPacket p = new DatagramPacket("123".getBytes(), "123".getBytes().length, InetAddress.getByName("localhost"), 9999);

socket.send(p);

Создайте пакет отправки, затем вызовитеsend()Метод может завершить передачу пакета данных

UDP не требует подключения, данные можно отправлять напрямую через IP+PORT

Кейс: UDP-чат

public class _DatagramPacket {

    public static void main(String[] args) throws IOException {
        // 从命令行得到需要绑定的端口和发送数据的端口
        DatagramSocket datagramSocket = new DatagramSocket(Integer.parseInt(args[0]));
        
        System.out.println("已启动");

        new Thread(() -> {

            byte[] buffer = new byte[1024];
            DatagramPacket p = new DatagramPacket(buffer, buffer.length);
            try {
                for (;;) {
                    // 构建接收数据
                    datagramSocket.receive(p);
                    System.out.println(p.getPort() + ":" + new String(buffer, 0, p.getLength()));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        Scanner scanner = new Scanner(System.in);
        DatagramPacket p = new DatagramPacket(new byte[0], 0, new InetSocketAddress("127.0.0.1", Integer.parseInt(args[1])));
        while (scanner.hasNext()) {
            String next = scanner.next();
            // 构建发送数据包
            p.setData(next.getBytes());
            datagramSocket.send(p);
        }
    }

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

последние слова

здесь, оSocket编程Мы закончили говорить об аспектах, и методов API введено не так много, они одинаковы при использовании.

Ниже приведеныjava.netКаталог, в котором находится документ:

нажмите здесь, чтобы посмотреть