day26--apiday09(聊天室)

day26--apiday09(聊天室)

1.客户端

package socket;

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

/**
* 聊天室客户端
*/
public class Client {
   /*
       java.net.Socket 插座 套接字
       Socket封装了TCP协议的通讯细节,使用它可以和服务端建立TCP连接,并基于两个流的
       读写完成数据交换。
    */
   private Socket socket;

   /**
    * 构造方法,用于初始化客户端
    */
   public Client() {
       try {
           /*
               实例化Socket时常用的构造方法:
               Socket(String host,int port)
               这个构造器实例化Socket的过程就是与服务端建立连接的过程。
               参数1:服务端的IP地址
               参数2:服务端开启的服务端口
               我们通过服务端的IP可以找到网络上服务端所在的计算机。通过端口号可以找到
               该机器上的服务端应用程序从而与之建立连接。
            */
           System.out.println("正在连接服务端");
           socket = new Socket("localhost", 8088);
           System.out.println("与服务器建立连接了");
      } catch (IOException e) {
           e.printStackTrace();
      }
  }


   /**
    * 客户端开始工作的方法
    */
   public void start() {
       try {
           //启动用于读取服务端发过来的消息的线程
           ServerHandler handler = new ServerHandler();
           Thread t = new Thread(handler);
           t.start();

           /*通过socket获取的字节输出流写出的字节会通过网络发送给远端计算机
            * */
           OutputStream out = socket.getOutputStream();
           OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
           BufferedWriter bw = new BufferedWriter(osw);
           PrintWriter pw = new PrintWriter(bw, true);


           Scanner scanner = new Scanner(System.in);
           String line;
           while (!"exit".equalsIgnoreCase(line = scanner.nextLine())) {
               pw.println(line);


          }
      } catch (IOException e) {
           e.printStackTrace();
      } finally {
           try {
               /*Socket提供了close方法,可以与远端计算机断开连接
               该方法调用时,也会自动关闭通过它获取的输出流和输入流
               * */
               socket.close();
          } catch (IOException e) {
               e.printStackTrace();
          }
      }
  }

   public static void main(String[] args) {
       Client client = new Client();
       client.start();
  }

   //该线程处理服务端发来的消息
   public class ServerHandler implements Runnable {
       @Override
       public void run() {
           try {
               //通过socket获取输入流读取服务端发送过来的消息
               InputStream in = socket.getInputStream();
               InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
               BufferedReader br = new BufferedReader(isr);
               String line;
               //循环服务端发过来的每一行字符串
               while ((line = br.readLine()) != null) {
                   System.out.println(line);
              }
          } catch (IOException e) {

          }
      }
  }

}

 

2.服务端

package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
* 聊天室服务端
*/
public class Server {
   /**
    * java.net.ServerSocket
    * ServerSocket是运行在服务端上的。其主要有两个作用
    * 1:向服务端申请服务端口(客户端Socket就是通过这个端口与服务端建立连接的)
    * 2:监听服务端口,一旦客户端连接会立即常见一个Socket,通过该Socket与客户端交互
    * <p>
    * 如果我们将Socket比喻为"电话",那么ServerSocket相当于"总机"
    */
   private ServerSocket serverSocket;
   private PrintWriter[] allOut = {};

   public Server() {
       try {
           /*
               ServerSocket在创建的时候要申请一个固定的端口号,客户端才能通过这个
               端口建立连接。
               如果该端口被当前操作系统中其他程序使用了,那么这里实例化会抛出异常:
               java.net.BindException:address already in use
               绑定异常:地址被使用了
            */
           System.out.println("正在启动服务端...");
           serverSocket = new ServerSocket(8088);
           System.out.println("服务端启动完毕!");
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   public void start() {
       try {
           /*
               ServerSocket的accept方法是一个阻塞方法。
               开始等待客户端的连接,一旦一个客户端通过端口建立连接,此时accept方法
               会立即返回一个Socket实例。通过该实例可以与该客户端进行交互。
               相当于是接电话的动作。
               阻塞方法:调用后,程序就"卡住"不往下执行了。
            */
           while (true) {

               System.out.println("等待客户端连接");
               Socket socket = serverSocket.accept();
               System.out.println("一个客户端连接了!");
               //启动一个线程处理客户端的交互
               ClientHandler clientHandler = new ClientHandler(socket);
               Thread thread = new Thread(clientHandler);
               thread.start();
          }


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

   public static void main(String[] args) {
       Server server = new Server();
       server.start();
  }

   /*该线程任务是用一个线程来处理客户端的交互工作
    * */
   private class ClientHandler implements Runnable {
       private Socket socket;
       private String host;//记录远端的地址信息

       public ClientHandler(Socket socket) {
           this.socket = socket;
           host = socket.getInetAddress().getHostAddress();
      }

       @Override
       public void run() {
           PrintWriter pw = null;
           try {

               InputStream in = socket.getInputStream();
               InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
               BufferedReader br = new BufferedReader(isr);
               //通过socket获取输出流用于给对方发消息

               OutputStream out = socket.getOutputStream();
               OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
               BufferedWriter bw = new BufferedWriter(osw);
               pw = new PrintWriter(bw, true);

               //该输出流存入共享数组allOut中
               synchronized (Server.this){

                   //1.扩容
                   allOut = Arrays.copyOf(allOut, allOut.length + 1);
                   //2.赋值
                   allOut[allOut.length - 1] = pw;
                   //通知所有客户端,该用户上线了
                   sendMassage(host + "上线了,当前在线人数:" + allOut.length);
              }
               String line;
              ;
           /*
               这里的BufferedReader读取时低下连接的流是通过Socket获取的输入流,
               当远端计算机还处于连接状态,但是暂时没有发送内容时,readLine方法会
               处于阻塞状态,直到对方发送过来一行字符串为止。
               如果返回值为null,则表示对方断开了连接(对方调用了socket.close())。

               对于windows的客户端而言,如果强行杀死进程,会出现以下报错代码
               java.net.
            */
               while ((line = br.readLine()) != null) {


                   sendMassage(host + "说:" + line);

              }
          } catch (IOException e) {
//               e.printStackTrace();
          } finally {
               //处理客户端断开连接后的操作
               synchronized (Server.this){

                   //将pw从allOut中删除
                   for (int i = 0; i < allOut.length; i++) {
                       if (allOut[i] == pw) {
                           allOut[i] = allOut[allOut.length - 1];
                           allOut = Arrays.copyOf(allOut, allOut.length - 1);
                           break;
                      }
                  }
              }
               //通知所有客户端,该用户下线了
               sendMassage(host + "下线了,当前在线人数:" + allOut.length);

               try {

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

       /*将群消息群发给所有客户端
        * */
       private void sendMassage(String line) {
           synchronized (Server.this){

               System.out.println(line);
               //遍历allOut数组,将消息发送给所有客户端
               for (int i = 0; i < allOut.length; i++) {

                   allOut[i].println(host + "说:" + line);
              }
          }

      }
  }

}

 

posted @ 2022-04-06 07:38  约拿小叶  阅读(53)  评论(0)    收藏  举报