第十三章 Java网络编程(数据通信方案)

什么是网络编程

可以让设备中的程序与网络上其他设备中的程序进行数据交互的技术(实现网络通信)

基本的通信架构:cs架构和bs架构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

网络通信三要素

在这里插入图片描述
在这里插入图片描述

1、IP地址

在这里插入图片描述
在这里插入图片描述

1.1、IP域名( Domain Name)

在这里插入图片描述

1.2、公网IP,内网IP、本机IP

公网IP:是可以连接到互联网的IP地址
内网IP:也叫局域网IP,是只能组织机构内部使用的IP地址;例如,192.168.开头的就是常见的局域网地址,范围为
192.168.0.0–192.168.255.255,专门为组织机构内部使用
本机IP:127.0.0.1 、localhost:代表本机IP,只会寻找当前程序所在的主机
在这里插入图片描述

1.3、InetAddress(IP地址类)

在这里插入图片描述

public class Test {
    public static void main(String[] args)
    {
        // 认识InetAddress类
        try {
            // 1、获取本机IP
            InetAddress inet1 = InetAddress.getLocalHost();
            System.out.println(inet1.getHostName()); // 获取主机名
            System.out.println(inet1.getAddress()); // 获取IP地址

            // 2、获取对方IP
            InetAddress inet2 = InetAddress.getByName("www.baidu.com");
            System.out.println(inet2.getHostName());
            System.out.println(inet2.getAddress());

            // 3、判断本机与对方主机是否互通
            boolean isReachable = inet2.isReachable(3000);
            System.out.println(isReachable);
            // 4、获取域名
            String domain = inet1.getHostName();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

2、端口

在这里插入图片描述
在这里插入图片描述

3、协议

在这里插入图片描述
在这里插入图片描述

3.1、传输层的2个协议

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2 UDP通信的实现

在这里插入图片描述
在这里插入图片描述

// 服务端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpService {
    public static void main(String[] args) throws Exception {
        // UDP服务端--接收方
        System.out.println("--服务端启动了--");
        // 1.创建服务端对象
        DatagramSocket server = new DatagramSocket(8080);

        // 2.准备一个容器,用于接收数据
        byte[] container = new byte[1024*64];
        DatagramPacket packet = new DatagramPacket(container,container.length);

        // 3.等待客户端发送数据
        server.receive(packet);

        // 4.读取数据
       int len = packet.getLength();// 获取实际接收到的数据长度
        String data = new String(container,0,len); // 获取实际接收到的数据
        System.out.println("服务端收到了:"+data);

        // 5、获取对方的地址和端口
        String ip = packet.getAddress().getHostAddress();
        int port = packet.getPort();
        System.out.println("对方ip:"+ip+",port:"+port);
    }
}
// 客户端

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class UdpClient {
    public static void main(String[] args) throws Exception {

        // 目的:学习使用UDP协议发送数据
        System.out.println("--客户端启动了--");
        // 1.创建客户端
        DatagramSocket client = new DatagramSocket();

        // 2.准备数据
        byte[] data = "你好,我是客户端".getBytes();

        // 3.发送数据
        // 3.1 创建要发出去的数据包对象
        /*
         *public DatagramPacket(byte[] buf, int length, InetAddress address, int port)
         *参数一:要发送的数据(字节数组)
         * 参数二:要发送的字节长度
         * 参数三:目的地ip地址
         * 参数四:对方的程序端口号
         * **/
      DatagramPacket  packet = new DatagramPacket(data,data.length, InetAddress.getLocalHost(),8080);
      // 3.2 发送数据
        client.send(packet);
    }
}

在这里插入图片描述

3.3 UDP通信实现多发多收

在这里插入图片描述
在这里插入图片描述

// 服务器端代码
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UdpService {
    public static void main(String[] args) throws Exception {
        // UDP服务端--接收方
        System.out.println("--服务端启动了--");
        // 1.创建服务端对象
        DatagramSocket server = new DatagramSocket(8080);

        // 2.准备一个容器,用于接收数据
        byte[] container = new byte[1024*64];
        DatagramPacket packet = new DatagramPacket(container,container.length);

        while (true) {
            // 3.等待客户端发送数据
            server.receive(packet);

            // 4.读取数据
            int len = packet.getLength();// 获取实际接收到的数据长度
            String data = new String(container,0,len); // 获取实际接收到的数据
            System.out.println("服务端收到了:"+data);

            // 5、获取对方的地址和端口
            String ip = packet.getAddress().getHostAddress();
            int port = packet.getPort();
            System.out.println("对方ip:"+ip+",port:"+port);
            System.out.println("---------------------------");
        }
    }
}

// 客户端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UdpClient {
    public static void main(String[] args) throws Exception {
        // 目的:学习使用UDP协议发送数据
        System.out.println("--客户端启动了--");
        // 1.创建客户端
        DatagramSocket client = new DatagramSocket();
        // 创建扫描器,接收用户的输入
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请输入要发送的数据:");
            String msg = sc.nextLine();
            // 如果用户输入exit则退出
            if ("exit".equals(msg)) {
                System.out.println("--客户端退出了--");
                //  关闭资源
                client.close();
                break;
            }
            // 2.准备数据
            byte[] data = msg.getBytes(); // 转成字节数组
            // 3.发送数据
            // 3.1 创建要发出去的数据包对象
            /*
             *public DatagramPacket(byte[] buf, int length, InetAddress address, int port)
             *参数一:要发送的数据(字节数组)
             * 参数二:要发送的字节长度
             * 参数三:目的地ip地址
             * 参数四:对方的程序端口号
             * **/
            DatagramPacket  packet = new DatagramPacket(data,data.length, InetAddress.getLocalHost(),8080);
            // 3.2 发送数据
            client.send(packet);
        }
    }
}

3.4 TCP通信的实现

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//服务端
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServiceDemo {
    public static void main(String[] args) throws Exception {

        // 1. 创建TCP服务端
        ServerSocket ss = new ServerSocket(9999);

        // 2. 调用accept方法,阻塞等待客户端连接,一旦有客户端连接,accept方法就会返回一个Socket对象
        Socket socket = ss.accept();

        //3. 获取字节输入流,读取客户端发送的数据
        InputStream is = socket.getInputStream();

        // 4.把字节输入流包装成特殊数据输入流
        DataInputStream dis = new DataInputStream(is);

        // 5.读取数据
        String str = dis.readUTF();
        int id = dis.readInt();
        System.out.println("服务端接收到的数据:" +"id="+id+"str="+ str);

        //6.客户端的ip和端口
        System.out.println("客户端的ip地址:"+socket.getInetAddress().getHostAddress());
        System.out.println("客户端的端口:"+socket.getPort());
    }
}
// 客户端
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
public class ClientDemo1 {
       public static void main(String[] args) throws Exception {
	           // 目标:实现TCP通信一发一收
	
	           // 1.创建Socket管道对象,请求与服务端Socket链接
	           Socket socket = new Socket("127.0.0.1",9999);
	
	           // 2.得到字节输出流
	           OutputStream os = socket.getOutputStream();
	
	           // 3.数据流
	           DataOutputStream dos = new DataOutputStream(os);
	           dos.writeUTF("你好,我是客户端");
	           dos.writeInt(100);
	
	           //4.关闭Socket
	            socket.close();
        }
}

在这里插入图片描述
在这里插入图片描述

// 这样写有问题,不能使用UDP的方式实现多收多发
// 服务端
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServiceDemo {
    public static void main(String[] args) throws Exception {
        // 1. 创建TCP服务端
        ServerSocket ss = new ServerSocket(9090);
        // 2. 调用accept方法,阻塞等待客户端连接,一旦有客户端连接,accept方法就会返回一个Socket对象
        Socket socket = ss.accept();
        //3. 获取字节输入流,读取客户端发送的数据
        InputStream is = socket.getInputStream();
        // 4.把字节输入流包装成特殊数据输入流
        DataInputStream dis = new DataInputStream(is);
        while (true) {
            // 5.读取数据
            String str = dis.readUTF();
            System.out.println("服务端接收到的数据:" +"str="+ str);

            //6.客户端的ip和端口
            System.out.println("客户端的ip地址:"+socket.getInetAddress().getHostAddress());
            System.out.println("客户端的端口:"+socket.getPort());
        }
    }
}
// 客户端

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo1 {
       public static void main(String[] args) throws Exception {
           // 目标:实现TCP通信一发一收

           // 1.创建Socket管道对象,请求与服务端Socket链接
           Socket socket = new Socket("127.0.0.1",9090);

           // 2.得到字节输出流
           OutputStream os = socket.getOutputStream();

           // 3.数据流
           DataOutputStream dos = new DataOutputStream(os);

           Scanner sc = new Scanner(System.in);

           while (true){
               System.out.println("请输入要发送的内容:");
               String msg = sc.nextLine();
               if("exit".equals(msg)){
                   socket.close();
                   break;
               }
               // 4.发送数据
               dos.writeUTF(msg);
               dos.flush();
           }
        }
}

3.5 TCP实现同时接收多个客户端的消息

在这里插入图片描述
在这里插入图片描述

// 创建一个线程类

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;

public class ServerReader extends Thread {

    private final Socket socket;
    // 构造方法
    public ServerReader(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {


        try {
            //3. 获取字节输入流,读取客户端发送的数据
            InputStream is = socket.getInputStream();

            // 4.把字节输入流包装成特殊数据输入流
            DataInputStream dis = new DataInputStream(is);

            while (true) {
                // 5.读取数据
                String str = dis.readUTF();

                System.out.println("服务端接收到的数据:" +"id="+"str="+ str);

                //6.客户端的ip和端口
                System.out.println("客户端的ip地址:"+socket.getInetAddress().getHostAddress());
                System.out.println("客户端的端口:"+socket.getPort());
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("客户端断开了连接:"+socket.getInetAddress().getHostAddress());
        }
    }
}

// 服务端类
import java.net.ServerSocket;
import java.net.Socket;
public class ServiceTcp {
    public static void main(String[] args) throws Exception {

        // 1. 创建TCP服务端
        ServerSocket ss = new ServerSocket(8081);
        while (true) {
            // 2. 调用accept方法,阻塞等待客户端连接,一旦有客户端连接,accept方法就会返回一个Socket对象
            Socket socket = ss.accept();
            System.out.println("一个客户端上线了");
            new ServerReader(socket).start();
        }
    }
}
// 客户端类
import java.net.ServerSocket;
import java.net.Socket;
public class ServiceTcp {
    public static void main(String[] args) throws Exception {

        // 1. 创建TCP服务端
        ServerSocket ss = new ServerSocket(8081);
        while (true) {
            // 2. 调用accept方法,阻塞等待客户端连接,一旦有客户端连接,accept方法就会返回一个Socket对象
            Socket socket = ss.accept();
            System.out.println("一个客户端上线了");
            new ServerReader(socket).start();
        }
    }
}

4、BS架构原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

// BS架构,客户端使用浏览器发起请求,不用开发客户端。只需要开发服务器端
// 定义一个线程类
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ServerReader extends Thread {
    private final Socket socket;
    // 构造方法
    public ServerReader(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try{
            //3. 给当前对应的浏览器管道响应一个网页数据回去
            OutputStream os = socket.getOutputStream();
            // 通过字节输出流包装写出去数据给浏览器
            // 把字节输出流包装成打印流
            PrintStream ps = new PrintStream(os);
            // 写响应的网页数据出去
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=utf-8");
            // 添加空行,分隔响应头和响应体
            ps.println();
            ps.println("<html>");
            ps.println("<head>");
            ps.println("<meta charset='utf-8'>");
            ps.println("</meta>");
            ps.println("<title>测试</title>");
            ps.println("</head>");
            ps.println("<body>");
            ps.println("<h1>Hello World</h1>");
            ps.println(".......");
            ps.println("<h1 style='color:red'>服务器响应出来展示在浏览器里面的数据</h1>");
            ps.println("</body>");
            ps.println("</html>");
            ps.close();
            socket.close();
        }catch(Exception e){
            e.printStackTrace();
            System.out.println("服务器响应浏览器出现异常");
        }
    }
}
// 定义主类
import java.net.ServerSocket;
import java.net.Socket;
public class ServiceBrowser {
    public static void main(String[] args) throws Exception {
        // 1. 创建TCP服务端
        ServerSocket ss = new ServerSocket(8889);
        while (true) {
            // 2. 调用accept方法,阻塞等待客户端连接,一旦有客户端连接,accept方法就会返回一个Socket对象
            Socket socket = ss.accept();
            System.out.println("一个客户端上线了");
            new ServerReader(socket).start();
        }
    }
}

在这里插入图片描述

4.1 进一步优化:使用线程池

网页类的响应,适合用线程池进行进一步优化
原因:每次请求都开一个新线程,高并发时,容易宕机(比如开了100个网页,在网页内容响应完成后,又立即销毁关闭管道)。
因此使用线程池进行优化
在这里插入图片描述

// 使用线程池进行优化
// 创建线程类

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ServerReader extends Thread {
    private final Socket socket;
    // 构造方法
    public ServerReader(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try{
            //3. 给当前对应的浏览器管道响应一个网页数据回去
            OutputStream os = socket.getOutputStream();
            // 通过字节输出流包装写出去数据给浏览器
            // 把字节输出流包装成打印流
            PrintStream ps = new PrintStream(os);
            // 写响应的网页数据出去
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=utf-8");
            // 添加空行,分隔响应头和响应体
            ps.println();
            ps.println("<html>");
            ps.println("<head>");
            ps.println("<meta charset='utf-8'>");
            ps.println("</meta>");
            ps.println("<title>测试</title>");
            ps.println("</head>");
            ps.println("<body>");
            ps.println("<h1>Hello World</h1>");
            ps.println("....哈哈...");
            ps.println("<h1 style='color:red'>服务器响应出来展示在浏览器里面的数据</h1>");
            ps.println("</body>");
            ps.println("</html>");
            ps.close();
            socket.close();
        }catch(Exception e){
            e.printStackTrace();
            System.out.println("服务器响应浏览器出现异常");
        }
    }
}

// 创建服务器端主类:使用了线程池
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

public class ServiceBrowser {
    public static void main(String[] args) throws Exception {
        // 1. 创建TCP服务端
        ServerSocket ss = new ServerSocket(8889);
        // 创建线程池
        ExecutorService pool = new ThreadPoolExecutor(
                3,
                10,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
                );
        while (true) {
            // 2. 调用accept方法,阻塞等待客户端连接,一旦有客户端连接,accept方法就会返回一个Socket对象
            Socket socket = ss.accept();
            System.out.println("一个客户端上线了");
            // 线程对象既可以启动线程,也可以当任务对象使用
            // 3.把这个客户端管道包装成一个任务交给线程池处理
           pool.execute(new ServerReader(socket)) ;
        }
    }
}

常用知识

1、获取时间的方案

JDK8之前的方案:使用new Date(这种老方案有弊端,设计得比较早,且在获取年份的时候要减去1900才能得到正确的年份,很容易出bug,不推荐)
JDK8之后的方案:LocalDate, LocalTime, LocalDateTime,获取此刻日期时间,新项目推荐
在这里插入图片描述
在这里插入图片描述

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.logging.SimpleFormatter;
public class Test {
    public static void main(String[] args) {
        // 创建一个Date对象---JDK8之前的方案
        Date d = new Date();
        System.out.println(d);

        // 格式化日期
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date = sdf.format(d);
        System.out.println(date);

        // JDK8之后的方案:LocalDate, LocalTime, LocalDateTime,获取此刻日期时间,新项目推荐
        // 获取此刻日期时间对象
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);
        System.out.println(now.getYear());
        // 获取一年中第多少天
        System.out.println(now.getDayOfYear());
        // 获取一个月中第几天
        System.out.println(now.getDayOfMonth());

        // 格式化:DateTimeFormatter
        LocalDateTime now1 = LocalDateTime.now();
        System.out.println("格式化之前:"+now1);
        // 1.  创建一个格式化对象
        DateTimeFormatter sdf1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        String format = sdf1.format(now1);
        System.out.println("格式化之后:"+format);
    }
}

2、 字符串的高效操作方案 StringBuilder

在这里插入图片描述
在这里插入图片描述

3、BigDecimal:用于解决浮点型运算时,出现结果失真的问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
案例:聊天室

// 服务器端
// ****常量类****
public class Constant {
    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 6666;
}
// ***** 服务器线程类********
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Collections;

public class ServerReaderThread extends  Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket)
    {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 接收的消息可能有很多种。1、登录信息(包含昵称) 2、群聊信息 3、私聊信息
            // 所以客户端必须声明协议发送信息
            // 比如客户端先发1,代表接下来是登录信息,
            // 比如客户端先发2.代表群聊信息
            // 先从socket管道中接收客户端发送来的消息类型编号
            DataInputStream dis = new DataInputStream(socket.getInputStream()); // 输入流
            while (true) {
                int type = dis.readInt();// 接收消息类型编号
                switch (type) {

                    case 1:
                        // 客户端发来了登录信息,接下来要接收昵称信息,再更新全部在线客户端的在线人数列表
                        String nickname = dis.readUTF();
                        // 存入到 map中
                        Server.onlineSockets.put(socket,nickname);
                        // 更新全部在线客户端的在线人数列表
                         updateClientsOnlineList();

                        break;

                    case 2:
                        // 客户端发来了群聊信息,接下来要接收群聊内容,再把群聊内容转发给全部在线客户端
                        String msg = dis.readUTF();
                        sendMsgToAll(msg);
                        break;
                  // 客户端发来了私聊信息,接下来要接收私聊内容,再把私聊内容转发给指定的客户端
                    case 3:
                        break;
                }
            }

        } catch (Exception e) {
//            e.printStackTrace();
            System.out.println("一个客户端断开了连接..."+socket.getInetAddress());
            // 把下线的客户端socket从map中移除
            Server.onlineSockets.remove(socket);
            // 更新全部在线人数列表
            updateClientsOnlineList();
        }
    }

    // 给全部在线socket推送当前客户端发来的消息
    private void sendMsgToAll(String msg) {

        // 拼消息,再发送
        StringBuilder  sb = new StringBuilder();
        String name = Server.onlineSockets.get(socket);
        LocalDateTime  now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String nowStr = now.format(formatter);
       StringBuilder msgResult =  sb.append(name).append(" ").append(nowStr).append("\r\n").append(msg).append("\r\n");

       //推送给全部在线socket管道
        for (Socket socket : Server.onlineSockets.keySet()) {
            try {
                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                // 告诉客户端,接下来要发多少个用户昵称
                dos.writeInt(2);
                dos.writeUTF(msgResult.toString());
                dos.flush();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void updateClientsOnlineList() {
        // 更新全部客户端的在线人数列表
        // 拿到全部在线客户端的用户名称,把这些名称转发给全部在线socket管道
        // 1.拿到全部在线客户端的用户昵称
        Collection<String> onlineUsers = Server.onlineSockets.values();
        // 2.把这个集合中的所有用户推送给全部客户端的socket管道
        for (Socket socket : Server.onlineSockets.keySet()) {
            try {
                // 3.把用户昵称集合发送给每一个socket管道
                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                // 1表示接下来是在线人数列表信息,2表示发的是群聊信息
                dos.writeInt(1);
                // 告诉客户端,接下来要发多少个用户昵称
                dos.writeInt(onlineUsers.size());
                for (String user : onlineUsers) {
                    dos.writeUTF(user);
                }
                dos.flush();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
// ***** 服务器类********
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class Server {
    // 定义一个集合容器(map)存储所有登录进来的客户端通道,以便将来群发消息给他们
    // key: 存储客户端管道 value: 用户名称
    public static  final Map<Socket,String> onlineSockets = new HashMap<>();
    public static void main(String[] args) {
       // 启动服务端
        System.out.println("启动服务端...");

        try {
            //1.注册端口
            ServerSocket serverSocket = new ServerSocket(Constant.SERVER_PORT);
           // 2.监听客户端的连接,阻塞式等待
            while (true) {
             // 3.调用accept()方法,获取socket管道
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了...");
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 客户端
// *****常量类*******
public class Constant {
    public static final String SERVER_IP = "127.0.0.1";
    public static final int PORT = 6666;
}
// ******用户登录界面*******
import javax.swing.*;
import java.awt.*;
import java.io.DataOutputStream;
import java.net.Socket;

public class ChatLoginFrame extends JFrame {
    private JTextField userNameField;
    private JButton loginButton, cancelButton;
    private Socket socket; //记住当前客户端通信管道

    public ChatLoginFrame() {
        // 设置窗口标题
        super("局域网聊天室");
        initializeComponents();
    }

    private void initializeComponents() {
        // 设置布局管理器为GridBagLayout
        this.setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(10, 10, 10, 10); // 组件之间的间距

        // 用户名标签和输入框
        JLabel label = new JLabel("用户名:");
        userNameField = new JTextField(20);
        label.setFont(new Font("SansSerif", Font.BOLD, 16));
        userNameField.setFont(new Font("SansSerif", Font.PLAIN, 16));

        gbc.gridx = 0;
        gbc.gridy = 0;
        add(label, gbc);
        gbc.gridx = 1;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 1; // 让文本字段随窗口扩展
        add(userNameField, gbc);

        // 创建按钮面板,并将其放在底部
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));

        // 创建“确定”按钮,并添加事件监听器
        loginButton = new JButton("登录");
        loginButton.setFont(new Font("SansSerif", Font.BOLD, 14));
        loginButton.addActionListener(e -> {
            String userName = userNameField.getText();
            if (!userName.isEmpty()) {
                // 进入聊天室逻辑
                try {
                    // 登录逻辑
                    login(userName);
                    // 进入聊天室
                    // 启动聊天界面
                    new GroupChatFrame(userName,socket);

                    // 关闭登录窗口
                    dispose();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }


          }
   else {
                JOptionPane.showMessageDialog(ChatLoginFrame.this,
                        "请输入用户名", "错误",
                        JOptionPane.ERROR_MESSAGE);

            }
        });
        buttonPanel.add(loginButton);

        // 创建“取消”按钮,并添加事件监听器
        cancelButton = new JButton("取消");
        cancelButton.setFont(new Font("SansSerif", Font.BOLD, 14));
        cancelButton.addActionListener(e -> System.exit(0)); // 点击取消按钮退出程序
        buttonPanel.add(cancelButton);

        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.gridwidth = 2; // 跨两列
        gbc.fill = GridBagConstraints.NONE;
        gbc.weightx = 0;
        add(buttonPanel, gbc);

        // 设置窗口属性
//        ImageIcon icon = new ImageIcon("path/to/your/icon.png"); // 替换为你的图标路径
//        setIconImage(icon.getImage());

        setSize(400, 250);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null); // 居中显示
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            ChatLoginFrame ex = new ChatLoginFrame();
            ex.setVisible(true);
        });
    }
    private void login(String name) throws Exception {
        // 立即发送登录请求给服务器
        // 1.创建socket管道请求与服务器socket建立连接
         socket = new Socket(Constant.SERVER_IP, Constant.PORT);
        // 2.立即发送消息类型1和自己的昵称给服务器
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeInt(1);
        dos.writeUTF(name);
        dos.flush();
    }
}
// *******聊天室界面************
import javax.swing.*;
import java.awt.*;
import java.io.DataOutputStream;
import java.net.Socket;

public class GroupChatFrame extends JFrame {
    private JTextArea chatArea;
    private JList<String> onlineUsersList;
    private JTextArea messageField;
    private JButton sendButton;
//    private String nickname;
    private Socket socket;
//    private StringBuffer smsContent;

    public GroupChatFrame() {
        // 设置窗口标题
        super("群聊界面");

        initializeComponents();
    }
    // 有参构造器
    public GroupChatFrame(String nickname, Socket socket) {
        // 调用上面的午餐构造器,初始化界面信息
        this();
        this.socket = socket;
//        this.nickname = nickname;
        // 设置昵称展示到聊天窗口
        this.setTitle( nickname+"的聊天窗口");
        // 立即把客户端的这个Socket管道交给一个独立的线程处理
        if (socket != null) {
            new ClientReaderThread(socket, this).start();
        }
       // new ClientReaderThread(socket,this).start();

    }

    private void initializeComponents() {
        this.setLayout(new BorderLayout());

        // 创建聊天记录显示区
        chatArea = new JTextArea(15, 40);
        chatArea.setEditable(false);
        chatArea.setFont(new Font("SansSerif", Font.PLAIN, 14));
        JScrollPane chatScrollPane = new JScrollPane(chatArea);

        // 创建在线用户列表
        DefaultListModel<String> listModel = new DefaultListModel<>();
        listModel.addElement("张三");
        listModel.addElement("李四");
        listModel.addElement("王五");
        onlineUsersList = new JList<>(listModel);
        onlineUsersList.setFont(new Font("SansSerif", Font.PLAIN, 14));
        JScrollPane userScrollPane = new JScrollPane(onlineUsersList);

        // 使用JSplitPane来分割聊天区域和在线用户列表
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, chatScrollPane, userScrollPane);
        splitPane.setDividerLocation(0.6); // 初始分割位置按比例设置
        splitPane.setResizeWeight(0.6); // 调整大小时保持比例

        this.add(splitPane, BorderLayout.CENTER);

        // 创建底部的消息输入框和发送按钮
        JPanel bottomPanel = new JPanel();
        bottomPanel.setLayout(new BorderLayout());
        bottomPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // 添加一些内边距

        // 多行消息输入框
        messageField = new JTextArea(5, 40); // 行数和列数
        messageField.setLineWrap(true); // 支持自动换行
        messageField.setWrapStyleWord(true); // 换行时以单词为单位
        messageField.setFont(new Font("SansSerif", Font.PLAIN, 14));
        JScrollPane messageScrollPane = new JScrollPane(messageField);
        messageScrollPane.setPreferredSize(new Dimension(-1, 100)); // 设置固定高度为100px

        bottomPanel.add(messageScrollPane, BorderLayout.CENTER);

        sendButton = new JButton("发送");
        sendButton.setFont(new Font("SansSerif", Font.BOLD, 14));
        sendButton.addActionListener(e -> sendMessage());
        bottomPanel.add(sendButton, BorderLayout.EAST);

        this.add(bottomPanel, BorderLayout.SOUTH);

        // 设置窗口属性
        this.setSize(800, 500);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null); // 居中显示
        this.setResizable(false);
        this.setVisible(true);

        // 关闭窗口,退出程序
        this.addWindowListener(new java.awt.event.WindowAdapter() {
            @Override
            public void windowClosing(java.awt.event.WindowEvent windowEvent) {
                // 关闭窗口,退出程序
                System.exit(0);
            }
        });

    }

    private void sendMessage() {
        String messageText = messageField.getText();
        if (!messageText.trim().isEmpty()) {
//            chatArea.append("我: " + messageText + "\n");
            messageField.setText("");
            // 把消息发送给服务端
            SendMsgServer(messageText);


        } else {
            JOptionPane.showMessageDialog(this, "请输入消息内容", "提示", JOptionPane.WARNING_MESSAGE);
        }
    }

    private void SendMsgServer(String messageText) {


        // 把消息发送给服务端
        try {
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            dos.writeInt(2);
            dos.writeUTF(messageText);

            dos.flush();
        } catch (Exception e) {

            e.printStackTrace();

        }

    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            GroupChatFrame ex = new GroupChatFrame();
            ex.setVisible(true);
        });
    }

    public void updateOnlineList(String[] onlineNames) {

        //把这个线程读取到的在线用户名称展示到界面上
        onlineUsersList.setListData(onlineNames);

    }

    // 更新群聊消息到窗口中
    public void setMsgToWin(String msg) {

//        smsContent.append(msg);
      //  sendMessage();

        chatArea.append(msg);

}
    }
// *******客户端线程类***********
import java.io.DataInputStream;
import java.net.Socket;

public class ClientReaderThread extends  Thread{
    private Socket socket;
    private  GroupChatFrame  win;
    public ClientReaderThread(Socket socket,GroupChatFrame win)
    {
        this.win = win;
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 接收的消息可能有很多种。1、在线人数更新的数据 2、群聊信息 3、私聊信息
            // 所以客户端必须声明协议发送信息
            // 比如客户端先发1,代表接下来是登录信息,
            // 比如客户端先发2.代表群聊信息
            // 先从socket管道中接收客户端发送来的消息类型编号
            DataInputStream dis = new DataInputStream(socket.getInputStream()); // 输入流
            while (true) {
                int type = dis.readInt();// 接收消息类型编号
                switch (type) {

                    case 1:
                        // 服务端发来的更新消息
                        updateClientsOnlineList(dis);

                        break;

                    case 2:
                        // 服务端发来的群聊消息
                        getMsgtoWin(dis);

                        break;
                  // 客户端发来了私聊信息,接下来要接收私聊内容,再把私聊内容转发给指定的客户端
                    case 3:
                        break;
                }
            }

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

        }
    }

    private void getMsgtoWin(DataInputStream dis) throws Exception{
        String msg = dis.readUTF();
        win.setMsgToWin(msg);
    }

    private void updateClientsOnlineList(DataInputStream dis) throws Exception {

        // 读取有多少个在线用户
        int onlineCount = dis.readInt();
       // List<String> onlineNames = new ArrayList<>();
        String[] onlineNames = new String[onlineCount];
        // 读取所有用户信息
        for (int i = 0; i < onlineCount; i++) {
            String nickname = dis.readUTF();
            // 将每个用户添加到集合中
            onlineNames[i] = nickname;
        }
        // 更新到窗口界面上的右侧展示出来
        win.updateOnlineList(onlineNames);
    }

}
// 启动类
public class App {
    public static void main(String[] args){
        new ChatLoginFrame().setVisible(true);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值