------------javaSE基础加强d5---01-网络编程-概述-CS-BS架构------------------------------------------------
- 网络编程:
- 可以让设备中的程序与网络上其他设备中的程序进行数据交互的技术(实现网络通信)
- 基本的通信架构:两种形式:CS架构和BS架构
- CS架构:(Client/Server)客户端和服务端;
- 需要开发客户端和服务端
- BS架构:(Browser/Server)浏览器和服务端;
- 只需要开发服务端就好了
- 目前BS架构更流行,只需要维护服务端就好了,浏览器访问是实时更新的。
- CS架构:(Client/Server)客户端和服务端;
- 不论CS架构还是BS架构,都必须依赖网络编程;
- Java提供了哪些网络编程解决方案?
- java.net.*包下提供了网络编程的解决方案;
------------javaSE基础加强d5---02-网络编程-IP详解---------------------------------------------------------------
- 网络通信三要素:IP、端口、协议;
- IP地址:设备在网络中的地址,是设备在网络中的唯一标识;
- 端口:应用程序在 设备中的唯一标识;
- 协议:连接和数据在网络中的传输的规则;
- IP地址:全称:"互联网协议地址"是分配给上网设备的唯一标识;
- 目前广泛使用的IP地址形式有两种:IPv4;IPv6;
- Internet Protocol version 6的缩写,128位地址,分8段;每段每4位编码成一个16进制位表示,每段之间用冒号隔开,成为冒分16进制;
- 2001:0bd8:2001:2001:2001:2001:2001:2008
- IP域名:www.baidu.com; DNS域名解析器,是互联网中用于将域名转换为IP地址的;相当于互联网的电话本。
- 比如你的电脑第一次找域名没找到,他们就去你的运营商服务器找,他们服务器肯定有,找到之后存到你电脑的DNS里,再访问的时候就可以直接访问了。
- 公网IP、内网IP:
- 公网IP;可以连接到互联网的IP地址;
- 内网IP:局域网IP,192.168.0.0--192.168.255.255;省IP嘛,
- 127.0.0.1、localhst:代表本机IP,只会寻找当前程序所在的主机;
- IP常用命令:
- ipconfig:查看本机IP地址。
- ping IP地址:检查网络是否连通;
- InetAddress,代表IP地址,常用方法:
|------------------------------------------------------------------------------|------------------------------|
| InetAddress类的常用方法 | 说明 |
| public static InetAddress getLocalHost() throws UnknownHostException | 获取本机IP,返回一个InetAddress对象 |
| public string getHostName() | 获取该Ip地址对象对应的主机名 |
| public String getHostAddress() | 获取该IP地址对象中的ip地址信息 |
| public static InetAddress getByName(String host) throws UnknownHostException | 根据IP地址或者域名,返回一个inetAddress对象 |
| public boolean isReachable(int timeout) throws IOException | 判断主机在指定毫秒内与该ip对应的主机是否能连通 |
ipconfig /all可以看物理地址,即世界唯一设备;
java
package com.itheima.demo1netaddress;
import java.net.InetAddress;
public class Demo1InetAddress {
public static void main(String[] args) {
//目标:认识InetAddress 获取本机IP对象和对方IP对象;
try {
//1、获取本机IP对象
InetAddress local = InetAddress.getLocalHost();
System.out.println(local); //主机名和IP地址
System.out.println(local.getHostName()); //主机名
System.out.println(local.getHostAddress()); //IP地址
//2、获取对方IP对象
InetAddress remote = InetAddress.getByName("www.baidu.com");
System.out.println(remote); //主机名和IP地址
System.out.println(remote.getHostName()); //主机名
System.out.println(remote.getHostAddress()); //IP地址 因为百度有很多服务器,所以获取到的IP地址可能不是一样的IP地址
//3、判断本机与对方主机能不能在5000ms内;连通?
System.out.println(remote.isReachable(5000));
}catch (Exception e){
e.printStackTrace();
}
}
}
小结:
- 网络通讯至少几要素?
- IP、端口、协议;
- IP地址是做什么的?具体几种?
- 定位网络上的设备,IPv4;IPv6;
- 如何查看本机IP地址?如何看是否与对方互通?
- ipconfig
- ping 192.168.10.23
- 本机IP是谁?
- 127.0.0.1或者localhost
- Java中哪个类代表IP地址?
- InetAddress;
- 如何获得本机IP对象?
- InetAddress benji= InetAddress.getLocalHost();
------------javaSE基础加强d5---03-网络编程-端口-协议---------------------------------------------------------
- 端口:
- 用来标记正在计算机设备上运行的应用程序,被规定为一个16位的二进制,范围0~65535;
- 周知端口:0~1023;被预先定义的知名应用占用(如HTTP占用80;FTP占用21)
- 注册端口:1024-49151,分配给用户进程或某些应用程序;
- 动态端口:49152~65535,之所以是动态端口,一般不固定分配给某种进程,而是动态分配。
- 注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则报错;
小结
- 端口号的作用是什么?
- 唯一标识正在计算机设备上运行的进程(程序);
- 一个设备中,能否出现2个应用程序的端口号一样?why
- 不可以,不一样会出现端口冲突错误;
通信协议::网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议;
-
开放式网络互联标准:OSI网络参考模型;理论上的。
-
TCP/IP网络模型::事实上的国际标准;
|-----------|------------|---------------------|------------------------------|
| OSI网络参考模型 | TCP/IP网络模型 | 各层对应 | 面向操作 |
| 应用层 | 应用层 | HTTP、FTP、SMTP...... | 应用程序需要关注的:浏览器,邮箱。程序员一般在这一层开发 |
| 表示层 | 应用层 | HTTP、FTP、SMTP...... | 应用程序需要关注的:浏览器,邮箱。程序员一般在这一层开发 |
| 1. 会话层 | 应用层 | HTTP、FTP、SMTP...... | 应用程序需要关注的:浏览器,邮箱。程序员一般在这一层开发 |
| 传输层 | 传输层 | UDP、TCP... | 选择使用的TCP,UDP协议 |
| 网络层 | 网络层 | IP... | 封装源和目标IP |
| 数据链路层 | 数据链路层+物理层 | 比特流... | 物理设备中传输 |
| 物理层 | 数据链路层+物理层 | 比特流... | 物理设备中传输 |
欧拉!
- 传输层的2个通信协议:
- UDP (User Datagram Protocol):用户数据报协议
- TCP(Transmission Control Protocol) :数据控制协议;
- UDP协议:
- 特点:无连接、不可靠通信;
- 不事先建立连接,数据按照包发,一包数据包含:自己的IP、端口、目的地IP、端口和数据(限制在64KB内)等;
- 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,所以是不可靠的。
- 适用:语音通话、视频直播、通信效率高的。
- TCP协议:
- 特点:面向连接、可靠通信;
- TCP的最终目的:要保证在不可靠的信道上实现可靠的数据传输。
- TCP主要有三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接;
- 三次握手:确保通信的双方收发消息都是没问题的(全双工)
- aa发送请求,我要连你;
- bb回复收到请求,允许连接;
- aa收到后,再发确认消息,ok
- 四次挥手:目的:确保通信的双方收发消息都已完成;
- aa发送断开连接请求;
- bb返回响应:好的稍等;
- bb返回消息处理完毕,确认断开;
- aa发送确认断开消息,断开连接。
- 适用:高安全性的传输、文件等;
------------javaSE基础加强d5---04-UDP通信-一发一收-多发多收---------------------------------------------
UDP通信的实现:
- 特点:无连接、不可靠通信;
- 不事先建立连接,发送端每次把发送的数据(限制在64KB以内)、接收端IP等信息封装成一个数据包,发出去就不管了;
- java提供了一个java.net.DatagramSocket类来实现UDP通信;
DatagramSocket:用于创建客户端、服务端;
|---------------------------------|------------------------------|
| 构造器 | 说明 |
| public DatagramSocket() | 创建客户端的Socket对象,系统会随机分配一个端口号; |
| public DatagramSocket(int port) | 创建服务端的Socket对象,并指定端口号 |
|----------------------------------------|-----------|
| 方法 | 说明 |
| public void send(DatagramPacket dp) | 发送数据包 |
| public void receive(DatagramPacket dp) | 使用数据包接收数据 |
DatagramPacket:创建数据包
|---------------------------------------------------------------------------------|-----------------------------------------------|
| 构造器 | 说明 |
| public DatagramPacket (byte[ ] buf ,int length, InetAddress address,int port) | 创建发出去的数据包对象(1发送的数据、2发送字节的长度、3目的地IP地址4、目的地的端口) |
| public DatagramPacket(byte[ ] buf,int length) | 创建用来接收数据的数据包 |
搞一个UDP一发一收,
客户端(发送端):
用完要记得关闭socket.close();正常放在try catch里面关闭哦;;
java
package com.itheima.demo2udp1;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Demo1UDPClient {
public static void main(String[] args) throws Exception{
System.out.println("发送端启动中...");
//目标:完成UDP通信一发一收,客户端开发;
//1、创建发送端对象(抛韭菜的人)
DatagramSocket socket = new DatagramSocket(); //DatagramSocket socket = new DatagramSocket(这里面不写就是程序默认分配动态端口,可以指定端口(可能出错));
//2、创建数据包对象,封装数据;
byte[] bytes = "我是客户端,今晚哥们约你来吃小龙虾配烧烤喔,武汉的龙虾很好吃哦".getBytes();
/**
* public DatagramPacket (byte[ ] buf ,int length, InetAddress address,int port)
* 1发送的数据、2发送字节的长度、3目的地IP地址4、目的地的端口
*/
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8080);
//3、发送数据
socket.send(packet);
//socket.close();
}
}
接收端(服务端):
java
package com.itheima.demo2udp1;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Demo2UDPServer {
public static void main(String[] args) throws Exception{
System.out.println("服务端启动了...");
//目标:完成UDP通信一发一收,服务端开发;
//1、创建接收端对象(收菜的人)
DatagramSocket socket = new DatagramSocket(8080);
//2、接收数据
byte[] buf = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3、将接收到的数据封装到数据包对象中
socket.receive(packet);
//4、看看数据是否收到了
//获取当前收到的数据长度
int length = packet.getLength();
String data = new String(buf,0,length);
System.out.println("服务端收到: " + data);
//获取对方的ip对象和程序端口;
String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println("服务端收到: " + ip + ":" + port);
//socket.close();
}
}
小结:
- 实现UDP通信,如何创建客户端、服务端对象?
- public DatagramSocket():创建发送端的Socket对象;
- public DatagramSocket(int port) :创建接收端的Socket对象;
- 数据包对象是哪个?
- DatagramPacket;
- 如何发送、接收数据?
- 使用DatagramSocket的如下方法:
- public void send(DatagramPacket dp) :发送数据包
- public void receive(DatagramPacket dp) :接收数据包
多发多收:
- 客户端实现步骤
- 创建DatagramSocket对象(发送端对象)
- 使用while死循环不断的接收用户的数据输入,如果用户输入的是exit则退出程序;
- 如果用户输入的不是exit,把数据封装成DatagramPacket;
- 使用DatagramSocket对象的send方法将数据包对象进行发送;
- 释放资源;
- 服务端实现步骤:
- 创建DatagramSocket对象并指定端口(接收端对象)
- 创建DatagramPacket对象接收数据(数据包对象)
- 使用DatagramSocket对象的receive方法传入DatagramPacket对象
- 使用while死循环不断的进行第三步;
PS:
多开实例(多开发送端):
1、编辑配置;

2、修改选项------允许多个实例------应用/确定
小结:
- UDP的接收端为什么可以接收很多发送端的消息?
- 接收端只负责接收数据包,无所谓是哪个发送端的数据包
25.40同学要做磊哥的狗,磊哥劝他不要太卑微;
------------javaSE基础加强d5---05-TCP通信-一发一收-多发多收---------------------------------------------
- TCP通信的实现
- 特点:面向连接、可靠通信;
- 通信双方事先会采用三次握手方式建立可靠连接,实现端到端的通信,底层能保证数据成功传给服务器;
- java提供了一个java.net.Socket类来实现TCP通信;
客户端开发:
构造器
|--------------------------------------|--------------------------------------------|
| 构造器 | 说明 |
| public Socket(String host, int port) | 根据指定的服务器ip、端口号请求与服务器建立连接,连接通过就获得了客户端Socket |
方法
|---------------------------------------|-----------|
| 方法 | 说明 |
| public OutputStream getOutputStream() | 获得字节输出流对象 |
| public InputStream getInputStream() | 获得字节输入流对象 |
服务端开发:
构造器:
|------------------------------|------------|
| 构造器 | 说明 |
| public ServerSocket(int put) | 为服务器程序注册端口 |
方法
|------------------------|---------------------------------------------|
| 方法 | 说明 |
| public Socket accept() | 阻塞等等客户端的连接请求,一旦与某个客户端成功连接,则返回服务端这部的Socket对象 |
| | |
小结:
-
TCP通信,客户端的代表类是?
- Socket类;
- public Socket(String host, int port)
-
TCP通信,如何使用Socket管道发送、接收消息?
- OutputStream getOutputStream(): 获得字节输出流对象(发);
- InputStream getInputStream(): 获得字节输入流对象(收);
-
TCP通信服务端用的类是谁?
- ServerSocket类,注册端口;
- 调用accept() 方法阻塞等等接收客户端连接。得到Socket对象;
-
TCP连接:一发一收:
-
客户端:
javapackage com.itheima.demo4tcp1; import java.io.DataOutputStream; import java.io.OutputStream; import java.net.Socket; public class Demo1Client { public static void main(String[] args) throws Exception{ //目标:实现TCP连接,一发一收,客户端开发 //1、常见Socket管道对象,请求与服务端的Socket连接,可靠连接 Socket socket = new Socket("127.0.0.1", 9999); //2、从socket通信管道中获得一个字节输出流 OutputStream os = socket.getOutputStream(); //3、包装成特殊数据流 DataOutputStream dos = new DataOutputStream(os); //用特殊数据流好处:可以发送int、 String不同类型的数据; dos.writeInt(666); dos.writeUTF("运势狗"); dos.flush(); //4、关闭资源 一般是用户主动关 // socket.close(); } }1
-
服务端:
javapackage com.itheima.demo4tcp1; import java.io.DataInputStream; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class Demo2Server { public static void main(String[] args) { // TCP服务端 //1、创建服务端ServerSocket对象,监听客户端连接 try (ServerSocket ss = new ServerSocket(9999)) { System.out.println("服务端启动了..."); while (true) { //2、调用accept方法,获取Socket管道,与客户端建立连接 System.out.println("等待客户端连接..."); Socket socket = ss.accept(); InputStream is = socket.getInputStream(); DataInputStream dis = new DataInputStream(is); int id = dis.readInt(); String msg =dis.readUTF(); System.out.println("服务端收到: " + socket.getInetAddress().getHostAddress() + ", 端口号:" + socket.getPort()); System.out.println("服务端收到: " + id + ", " + msg); System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); } }catch (Exception e){ e.printStackTrace(); } } }1
-
-
TCP持续发:
-
客户端:
javapackage com.itheima.dmeo5tcp2; import java.io.DataOutputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Scanner; public class Demo1Client { public static void main(String[] args) throws Exception{ //目标:实现TCP连接,多发多收,客户端开发 Socket socket = new Socket("127.0.0.1", 9999); OutputStream os = socket.getOutputStream(); DataOutputStream dos = new DataOutputStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请输入:"); String msg = sc.nextLine(); if ("exit".equals(msg)){ System.out.println("客户端退出"); // dos.close(); //主动关闭输出流管道 socket.close(); //一般来说关socket会自动帮你关掉输出流管道; break; } dos.writeUTF(msg); dos.flush(); } } }1
-
服务端:
javapackage com.itheima.dmeo5tcp2; import java.io.DataInputStream; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class Demo2Server { public static void main(String[] args) { // TCP服务端 try (ServerSocket ss = new ServerSocket(9999)) { System.out.println("服务端启动了..."); System.out.println("等待客户端连接..."); Socket socket = ss.accept(); InputStream is = socket.getInputStream(); DataInputStream dis = new DataInputStream(is); while (true) { // int id = dis.readInt(); String msg =dis.readUTF(); System.out.println("服务端收到: " + socket.getInetAddress().getHostAddress() + ", 端口号:" + socket.getPort()); System.out.println("服务端收到: " + msg); System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); } }catch (Exception e){ e.printStackTrace(); } } }
-
------------javaSE基础加强d5---06-TCP通信-支持多个客户端消息---------------------------------------------
同时接收多个用户的消息;
- 目前我们服务端只能建立一条TCP连接,是因为单线程的。
- 如何实现服务端同时接收多个客户端的消息的?
- 主线程定义了循环负责接收客户端Socket管道连接;
- 每接收到一个Socket通信管道后分配一个独立的线程负责处理它。
Thread类:
java
package com.itheima.demo6tcp3;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class ServerReader extends Thread{
private Socket socket;
public ServerReader(Socket socket){
this.socket = socket;
}
@Override
public void run(){
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
System.out.println("------《客户端:" +socket.getInetAddress().getHostAddress() + ",端口号:" + socket.getPort() + "》------已连接");
while (true) {
// int id = dis.readInt();
String msg =dis.readUTF();
System.out.println("服务端收到: " + socket.getInetAddress().getHostAddress() + ", 端口号:" + socket.getPort());
System.out.println("服务端收到: " + msg);
System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
}
} catch (IOException e) {
// e.printStackTrace();
System.out.println(socket.getInetAddress().getHostAddress() + ", 端口号:" + socket.getPort() + "客户端下线咯");
}
}
}
服务端:
java
package com.itheima.demo6tcp3;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Demo2Server {
public static void main(String[] args) {
// TCP服务端
System.out.println("服务端启动了...");
try (ServerSocket ss = new ServerSocket(9999)) {
while (true) {
Socket socket = ss.accept();
new ServerReader(socket).start();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
客户端
java
package com.itheima.demo6tcp3;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Demo1Client {
public static void main(String[] args) throws Exception{
//目标:实现TCP连接,多发多收,客户端开发
Socket socket = new Socket("127.0.0.1", 9999);
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入:");
String msg = sc.nextLine();
if ("exit".equals(msg)){
System.out.println("客户端退出");
// dos.close(); //主动关闭输出流管道
socket.close(); //一般来说关socket会自动帮你关掉输出流管道;
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
------------javaSE基础加强d5---07-TCP通信-BS架构的原理-使用线程池优化------------------------------
- BS架构原理:
- 客户端使用浏览器发起请求(不需要开发客户端)
- 服务端必须按照HTTP协议相应数据;
- http://服务器IP:服务器端口;
- http://127.0.0.1:8080;
- 浏览器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不识别返回的数据;
- HTTP协议规定:相应给浏览器的数据格式必须满足如下格式:

任务类:
java
package com.itheima.demo7_2tcp4;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ServerReader extends Thread{
private Socket socket;
public ServerReader(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
//给当前对应的浏览器管道相应一个网页数据回去;
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("<title>");
ps.println("欢迎来到土豆烹饪课堂");
ps.println("</title>");
ps.println("</head>");
ps.println("<body>");
ps.println("<h1>土豆烹饪18招</h1>");
ps.println("</body>");
ps.println("</html>");
ps.close(); //浏览器是短连接,响应完就直接关闭的;
socket.close();
} catch (Exception e) {
// e.printStackTrace();
System.out.println(socket.getInetAddress().getHostAddress() + ", 端口号:" + socket.getPort() + "客户端下线咯");
}
}
}
服务端代码:
java
package com.itheima.demo7_2tcp4;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
public class Demo2Server {
public static void main(String[] args) {
// BS架构的原理、理解;
System.out.println("服务端启动了...");
try {
ServerSocket ss = new ServerSocket(8080);
//创建线程池:
ExecutorService pool = new ThreadPoolExecutor(3, 10,
10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
while (true) {
Socket socket = ss.accept();
new ServerReader(socket).start();
//使用线程池:线程池处理任务就是调任务的run方法嘛。
pool.execute(new ServerReader(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
------------javaSE基础加强d5---08-项目实战3-项目介绍-前置技术-StringBuilder-BigDecimal-Date-LocalDateTime---------------
- 时间相关的获取方案:
- LocaLDate:代表本地日期(年月日星期)
- LocalTime:代表本地时间(时分秒纳秒)
- LocalDatateTime:代表本地日期、时间(年月日星期、时分秒毫纳秒)
java
package com.itheima.demo8api;
import javax.xml.crypto.Data;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class test1time {
public static void main(String[] args) {
//目标:掌握java提供的获取时间的方案;
// JDK8之前的方案:Date:获取此刻时间,部分老项目还有;前面是英文的月日、几号、时间;
Date d = new Date();
System.out.println(d);
//格式化:SimpleDateFormat 简单日期格式化,格式化日期对象称为我们喜欢的格式:
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy年-MM月-dd日 HH点:mm分:ss秒");
String format = sdf.format(d);
System.out.println(format);
System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
//JDK8之后的方案:LocalDateTime 获取此刻时间;
java.time.LocalDateTime ldt = java.time.LocalDateTime.now();
System.out.println(ldt);
System.out.println(ldt.getYear() + "年" + ldt.getMonthValue() + "月" + ldt.getDayOfMonth() + "日 周" + ldt.getDayOfWeek() + ldt.getHour() + "点" + ldt.getMinute() + "分" + ldt.getSecond() + "秒" + ldt.getNano() + "纳秒");
//比如原来的时间对象,是可以修改的,会丢失的;
//新的时间,修改后会返回一个新的日期对象,不会影响之前的时间对象;
//1、创建一个格式化对象:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分ss秒");
String format1 = dtf.format(ldt);
System.out.println(format1);
}
}
格式化时间对象:
java
java.time.LocalDateTime ldt = java.time.LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分ss秒");
String format1 = dtf.format(ldt);
System.out.println(format1);
字符串的高效操作方案:StringBuilder
-
StringBuilder代表可变字符串对象,相当于是一个容器,它里面封装的字符串是可以改变的,就是用来操作字符串的;
-
好处:StringBuilder比String更适合做字符串的修改操作,效率会更高,代码也会更简洁;
-
构造器:
|----------------------------------|-------------------------|
| 构造器 | 说明 |
| public StringBuilder( ) | 创建一个空白的可变的字符串对象,不包含任何内容 |
| public StringBuilder(String str) | 创建一个指定字符串内容的可变字符串对象 |1
-
方法
|-----------------------------------|-------------------------------------------|
| 方法名称 | 说明 |
| public StringBuilder append(任意类型) | 添加数据并返回StringBuilder对象本身 |
| public StringBuilder reverse( ) | 将对象的内容反转 |
| public int length( ) | 返回对象内容长度 |
| public String tostring( ) | 通过toString()就可以是心啊把StringBuilder转换为String |1
-
代码演示:
javapackage com.itheima.demo8api; public class Test2string { public static void main(String[] args) { //目标:高效拼接字符串 //共享数据性能好,但是修改数据性能差,因为String的对象时不可变变量; String s = ""; java.time.LocalDateTime ldt1 = java.time.LocalDateTime.now(); for (int i = 0; i < 10000; i++) { s = s + "abc"; } java.time.LocalDateTime ldt2 = java.time.LocalDateTime.now(); System.out.println("+号直接拼接s: " + (ldt2.getSecond() - ldt1.getSecond()) +"秒" + (ldt2.getNano() - ldt1.getNano())); System.out.println(ldt1.getYear() + "年" + ldt1.getMonthValue() + "月" + ldt1.getDayOfMonth() + "日 周" + ldt1.getDayOfWeek() + ldt1.getHour() + "点" + ldt1.getMinute() + "分" + ldt1.getSecond() + "秒" + ldt1.getNano() + "纳秒"); System.out.println(ldt2.getYear() + "年" + ldt2.getMonthValue() + "月" + ldt2.getDayOfMonth() + "日 周" + ldt2.getDayOfWeek() + ldt2.getHour() + "点" + ldt2.getMinute() + "分" + ldt2.getSecond() + "秒" + ldt2.getNano() + "纳秒"); // System.out.println(s); java.time.LocalDateTime ldt3 = java.time.LocalDateTime.now(); StringBuilder sb = new StringBuilder(); //对象是可变内容的容器, for (int i = 0; i < 10000; i++) { sb.append("abc"); } java.time.LocalDateTime ldt4 = java.time.LocalDateTime.now(); System.out.println("StringBuilder拼接s: " + (ldt4.getSecond() - ldt3.getSecond()) +"秒" + (ldt4.getNano() - ldt3.getNano())); System.out.println(ldt3.getYear() + "年" + ldt3.getMonthValue() + "月" + ldt3.getDayOfMonth() + "日 周" + ldt3.getDayOfWeek() + ldt3.getHour() + "点" + ldt3.getMinute() + "分" + ldt3.getSecond() + "秒" + ldt3.getNano() + "纳秒"); System.out.println(ldt4.getYear() + "年" + ldt4.getMonthValue() + "月" + ldt4.getDayOfMonth() + "日 周" + ldt4.getDayOfWeek() + ldt4.getHour() + "点" + ldt4.getMinute() + "分" + ldt4.getSecond() + "秒" + ldt4.getNano() + "纳秒"); System.out.println(sb); //打印都是一样的,但是类型是不一样的; //StringBuilder只是拼接字符串的手段,结果还是要恢复成字符串, String rs = sb.toString(); //这样就转成String类型了; //StringBuilder是支持链式编程的; StringBuilder sb2 = new StringBuilder(); sb2.append("你好,").append("sbct").append("吃粑粑"); System.out.println(sb2); } }
工具类:BigDecimal:
-
作用:用于解决浮点型运算时,出现结果失真的问题;
-
使用方法:把小数包装成BigDecimal对象;
-
常见构造器:
|-------------------------------|---------------------|
| 构造器 | 说明 |
| public BigDecimal(double val) | 将double转为BigDecimal |
| public BigDecimal(String val) | 将String转为BigDecimal |1
-
常见方法:
|------------------------------------------------------|----------------------|
| 方法名 | 说明 |
| public static BigDecimal valueOf(double val) | 转一个double成BigDecimal |
| public BigDecimal add(BigDecimal b) | 加法 |
| public BigDecimal subtract(BigDecimal b) | 减法 |
| public BigDecimal multiply(BigDecimal b) | 乘法 |
| public BigDecimal divide(BigDecimal b) | 除法 |
| public BigDecimal divide (另一个BigDecimal对象,精确几位,舍入模式) | 除法。可以控制精确到小数点几位 |
| public double doubleValue() | 将BigDecimal转换为double |1
javaBigDecimal c = a.divide(b, 2, RoundingMode.HALF_UP); //c = a ➗b ,保留两位小数,四舍五入; BigDecimal c = a.divide(b, 2, RoundingMode.FLOOR); // 不要最后一位; BigDecimal c = a.divide(b, 2, RoundingMode.UP); // 最后一位直接进一位; -
代码演示:
javapackage com.itheima.demo8api; import java.math.BigDecimal; import java.math.RoundingMode; public class test3BigDecimal { public static void main(String[] args) { //目标:掌握BigDecimal解决小数运算结果失真的问题: double a = 0.1; double b = 0.2; System.out.println("a= " + a); System.out.println("b= " + b); System.out.println("a+b = " + (a + b)); //无法拼出0.3;只能接近的0.3 //解决: //1、把小数包装成BigDecimal对象; //绝对不能采用这两种方式: BigDecimal a0 = new BigDecimal(0.1); BigDecimal b0 = new BigDecimal(b); //只能采用字符串的方式转BigDecimal对象;不然相当于没用这个; //因为字符串会把这些拆开,相当于0、.、1、三个字符,相当于跨过0.1直接变成了整数计算; BigDecimal a1 = new BigDecimal("0.1"); BigDecimal b1 = new BigDecimal("0.2"); //优化方案:可以直接调用valueOf方法: 这样就可以了。 BigDecimal a2 = BigDecimal.valueOf(0.1); BigDecimal c1 = a1.add(b1); System.out.println("a1.add(b1) = " + c1); double c = c1.doubleValue(); System.out.println("c = " + c); //这样就解决了精度问题咯 //搞个除法吧: BigDecimal i = new BigDecimal("0.1"); BigDecimal j = new BigDecimal("0.2"); BigDecimal x = new BigDecimal("0.3"); BigDecimal k = i.divide(j); System.out.println("k = i.divide(j) = " + k); // BigDecimal l = i.divide(x); // System.out.println("l = i.divide(x) = " + l); //会报错,因为0.3无法精确表示 BigDecimal l1 = i.divide(x, 10, RoundingMode.HALF_UP); // 做除法,1➗ x 精确保留10位;2、四舍五入 System.out.println("l1 = i.divide(x) = " + l1); } }
小结:
- BigDecimal的作用是?
- 解决浮点型运算时,出现结果失真的问题;
- 应该如何吧浮点型转换为BigDecimal对象?
- BigDecimal b1 = BigDecimal.valueOf(0.1);
- BigDecimal 提供了哪些常用方法?
- 如上;
------------javaSE基础加强d5---09-项目实战3-即时通讯-通过AI获取客户端界面---------------------------
完成局域网内沟通软件开发:
- 需求:
- 展示一个用户的登录界面,这个界面只要求用户输入自己聊天的昵称就可以了;
- 登录进入后,展示一个群聊的窗口,这个窗口,展示在线人数,展示消息展示框,消息输入框,发送按钮;
- 可以实现实时展示在线人数,完全做到即时通讯功能;
- 技术选型:
- GUI编程;Swing
- 网络编程
- 面向对象设计
- 常用API
- 思路分析:
- 创建一个模块,代表我们的项目:itheima-chat-system;
- 拿到系统需要的界面:Swing的代码:
- 登录界面:这个界面只需要用户自己聊天的昵称就可以了;
- 获取系统的聊天界面:
- 定义一个App启动类:创建进入界面对象并展示;
------------javaSE基础加强d5---10-项目实战3-即时通讯-架构分析-服务端功能分析-架构搭建---------
- 拿到系统需要的界面:Swing的代码:
- 登录界面:这个界面只需要用户自己聊天的昵称就可以了;
- 获取系统的聊天界面:
- 定义一个App启动类:创建进入界面对象并展示;
- 分析系统的整体架构:
- 需要开发服务端:
- 接收客户端的管道连接;
- 接收登录消息;接收昵称信息;
- 服务端也可能是接收客户端发送过来的群聊消息;
- 服务端存储全部的在线的Socket管道,一边到时候知道哪些客户在线,以便为这些客户端转发消息;
- 如果服务端收到了登录消息,接收昵称,然后更新所有客户端的在线人数列表;
- 如果服务端收到了群聊 消息,需要接收这个人的消息,再转发给所有客户端的在线人数展示这个消息;
- 客户端界面已经准备好了;
- 需要开发服务端:
- 先开发完整的服务端;
- 第一步,创建一个服务端的项目:itheima-chat-server;
- 第二步,创建一个服务端启动类,启动服务器等待客户端的连接;
- 第三步,把这个管道交给一个独立的线程来处理,以便支持很多客户端的信息;同时进来通信;
- 第四步,定义一个集合容器,存储所有登录进来的客户端管道,以便将来群发消息给他们;
- 这个集合只需要一个,记住所有在线的Socket
- 服务端接收登录消息/群聊消息;
------------javaSE基础加强d5---11-项目实战3-即时通讯-服务端-接收登录消息,转发在线人数列表------------
- 5、服务端接收登录消息/群聊消息;
- 接收消息可能是很多种类型,1、登录消息2、群聊消息3、私聊消息;
- 所以客户端必须声明协议,发送消息;
- 比如客户端先发1.就是登录消息,
- 比如客户端先发2,就是群聊消息;
- 比如客户端先发3,就是私聊消息;
- 6、实现服务端的登录消息接收;
- 7、接收客户端的群聊消息
------------javaSE基础加强d5---12-项目实战3-即时通讯-服务端-接收群聊消息并转发------------------
-
7、接收客户端的群聊消息
- 线程每收一个客户端的群聊消息,就应该把这个消息转发给全部在线的客户端对应的socket管道;
-
6、完善整个客户端程序代码;
------------javaSE基础加强d5---13-项目实战3-即时通讯-客户端-登录开发,转到消息聊天界面------
- 6、完善整个客户端程序的代码:
- 第一步,从登录界面开始;
- 给进入按钮绑定一个点击事件监听器,让他可以点击进来,一旦点击了,获取到昵称,立即请求与服务端的socket管道连接;并立即发送登录信息:"1"、"用户昵称";
- 再展示客户端的聊天界面;
- 接收到了昵称、属于自己客户端的socket通信管道;
- 从登录界面开始,完成了登录,完成了socket管道传给消息聊天界面;
- 第一步,从登录界面开始;
------------javaSE基础加强d5---14-项目实战3-即时通讯-客户端-实现读取在线人数并展示------------
- 6、完善整个客户端程序的代码:
- 第一步,从登录界面开始;
- 第二步,立即在消息聊天界面,立即读取socket管道从服务端发来的在线人数更新消息/群聊消息;
- 首先,交给一个独立的线程,专门负责读取socket从服务端收到的在线人数更新数据和群聊消息(简单来说,一个线程收消息,一个线程发消息);
- 收到消息:先判断消息的类型,判断是在线人数更新消息,还是群聊消息;
- 第三步:接收群聊消息;
- 第四步:发送群聊消息;
------------javaSE基础加强d5---15-项目实战3-即时通讯-客户端-群聊功能的实现------------------------
- 第三步:接收群聊消息;
- 接收消息类型,接收群聊数据,展示到界面的面板上;
- 第四步:发送群聊消息;
- 给发送按钮绑定一个点击事件,获取输入框的消息内容,先发送2,再把群聊内容发送给服务端;
局域网聊天室代码:

-
itheima-chat-server
- src
- com.itheima
-
Constart
javapackage com.itheima; public class Constart { public static final int PORT = 6666; } -
Server
javapackage com.itheima; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.itheima.Constart.PORT; public class Server { //这个集合只需要一个,记住所有在线的Socket //应该定义一个map集合,键是存储客户端的管道,值是这个管道的用户名称; public static final Map< Socket,String> onLineSockets = new HashMap<>(); public static void main(String[] args) { System.out.println("启动服务端系统......"); //1、注册端口, try { ServerSocket serverSocket = new ServerSocket(PORT); //2、主线程负责接收客户端的连接请求; while (true){ //3、调用accept方法,获取客户端的Socket对象; System.out.println("等待客户端连接..."); Socket socket = serverSocket.accept(); //第三步,把这个管道交给一个独立的线程来处理,以便支持很多客户端的信息;同时进来通信; new ServerReaderThread(socket).start(); System.out.println("客户端连接成功..."); } } catch (Exception e) { e.printStackTrace(); } } } -
ServerReaderThread
javapackage com.itheima; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Collection; import java.util.Map; public class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { // 接收消息可能是很多种类型,1、登录消息2、群聊消息3、私聊消息; // 所以客户端必须声明协议,发送消息; // 1.比如客户端先发1.就是登录消息, // 2.比如客户端先发2,就是群聊消息; // 3.比如客户端先发3,就是私聊消息; //先从Socket管道中接收客户端发来的消息类型编号; DataInputStream dis = new DataInputStream(socket.getInputStream()); while (true) { int type = dis.readInt(); switch (type) { case 1: //客户端发送了登录消息,更新全部在线客户端的在线 人数列表; String nickname = dis.readUTF(); //把这个登录成功的Socket存入到在线集合; Server.onLineSockets.put(socket,nickname); //更新全部在线客户端的在线 人数列表; updateSocketOnlineList(); break; case 2: //群聊消息,从Socket管道中接收群聊消息、再把群聊消息转发给全部在线客户端;; String msg = dis.readUTF(); sendMsgToAll(msg); break; case 3: //私聊消息,从Socket管道中接收私聊消息、再把私聊消息转发给指定的客户端; break; } } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + " 退出了聊天室"); //下线了要把客户从map集合中去掉; Server.onLineSockets.remove(socket); //下线后也要更新在线列表 updateSocketOnlineList(); } } //给全部客户端推送当前客户发送的消息 private void sendMsgToAll(String msg) { //一定要拼装好这个消息,再发给全部在线socket; //需要包含谁发的,几点发的,什么消息 StringBuilder sb = new StringBuilder(); String name = Server.onLineSockets.get(socket); //获取当前时间; LocalDateTime now = LocalDateTime.now(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a"); String nowStr = dtf.format(now); String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n") .append(msg).append("\r\n").toString(); //然后推送给所有的客户端Socket; for (Socket socket : Server.onLineSockets.keySet()) { try { DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeInt(2); //先发一个消息类型编号,1表示在线人数列表;客户端也要区分消息类型; dos.writeUTF(msgResult); dos.flush(); //刷新,发完数据保证数据发送出去; } catch (Exception e) { e.printStackTrace(); } } } //更新全部在线人数的列表: private void updateSocketOnlineList() { //1、拿到全部在线客户端的用户名称,把这些名称转发给全部在线的socket管道; //2、拿到全部在线的Socket管道; Collection<String> onLineSockets = Server.onLineSockets.values(); //3、把这个集合的所有用户都推送给全部客户端socket管道; for (Socket socket : Server.onLineSockets.keySet()) { try { DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeInt(1); //先发一个消息类型编号,1表示在线人数列表;客户端也要区分消息类型; dos.writeInt(onLineSockets.size()); for (String onLineUser : onLineSockets) { dos.writeUTF(onLineUser); } dos.flush(); //刷新,发完数据保证数据发送出去; } catch (Exception e) { e.printStackTrace(); } } } }
-
- com.itheima
- src
-
itheima-chat-system
- src
-
com.itheima.ui
-
ChatEntryFrame
javapackage com.itheima.ui; //public class ChatEntryFrame { //} import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.DataOutputStream; import java.net.Socket; public class ChatEntryFrame extends JFrame { private JTextField nicknameField; private JButton confirmButton; private JButton cancelButton; private JButton closeButton; private Socket socket; //记住当前客户端系统的通信管道; public ChatEntryFrame() { // 设置窗口标题 setTitle("局域网聊天室 - 登录"); // 设置窗口大小 setSize(400, 200); // 设置居中显示 setLocationRelativeTo(null); // 设置关闭行为(点击关闭按钮时退出程序) setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // 自定义关闭逻辑 // 添加窗口监听器处理关闭按钮 addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { handleExit(); } }); // 创建主面板(使用 BorderLayout) JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); // 昵称标签和输入框 JPanel inputPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10)); JLabel label = new JLabel("请输入昵称:"); nicknameField = new JTextField(15); // 输入框宽度 inputPanel.add(label); inputPanel.add(nicknameField); // 按钮面板 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 15, 10)); confirmButton = new JButton("登录"); cancelButton = new JButton("取消"); closeButton = new JButton("关闭"); buttonPanel.add(confirmButton); buttonPanel.add(cancelButton); buttonPanel.add(closeButton); // 添加到主面板 mainPanel.add(inputPanel, BorderLayout.CENTER); mainPanel.add(buttonPanel, BorderLayout.SOUTH); // 添加主面板到窗口 add(mainPanel); // 绑定事件 setupActions(); this.setVisible(true); } private void setupActions() { // 确认按钮:检查昵称是否为空 confirmButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String nickname = nicknameField.getText().trim(); if (nickname.isEmpty()) { JOptionPane.showMessageDialog(ChatEntryFrame.this, "昵称不能为空!", "提示", JOptionPane.WARNING_MESSAGE); } else { //1、请求一个socket管道,建立与服务端的连接: try { //客户端登录后做的操作; login(nickname); //然后进入聊天室 //聊天界面要知道是谁的聊天界面,所以要把名字送过去; new ClientChatFrame(nickname,socket); } catch (Exception ex) { ex.printStackTrace(); } JOptionPane.showMessageDialog(ChatEntryFrame.this, "欢迎进入聊天室," + nickname + "!", "登录成功", JOptionPane.INFORMATION_MESSAGE); // 这里可以跳转到聊天主界面(后续扩展) // new ChatRoomFrame(nickname).setVisible(true); dispose(); // 可选:关闭登录窗口 } } }); // 取消按钮:清空输入框 cancelButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { nicknameField.setText(""); nicknameField.requestFocus(); } }); // 关闭按钮:退出程序 closeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { handleExit(); } }); } private void login(String name) throws Exception{ socket = new Socket(Constant.SERVER_IP, Constant.SERVER_PORT); //立即发送消息类型1,和自己的昵称给服务器; DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeInt(1); dos.writeUTF(name); dos.flush(); //刷新一下;保证信息发出去了。 } private void handleExit() { System.exit(0); // int choice = JOptionPane.showConfirmDialog(this, // "确定要退出吗?", "确认退出", JOptionPane.YES_NO_OPTION); // if (choice == JOptionPane.YES_OPTION) { // System.exit(0); // } } // public static void ChatEntryFrame(){ // // // 使用 SwingUtilities 确保线程安全 // SwingUtilities.invokeLater(new Runnable() { // @Override // public void run() { //// try { //// // 设置系统默认外观风格(可选) ////// UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel()); //// } catch (Exception e) { //// e.printStackTrace(); //// } // new ChatEntryFrame().setVisible(true); // } // }); // } // 主方法:启动程序 public static void main(String[] args) { // 使用 SwingUtilities 确保线程安全 SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { // 设置系统默认外观风格(可选) // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel()); } catch (Exception e) { e.printStackTrace(); } new ChatEntryFrame(); } }); } } -
ClientChatFrame
javapackage com.itheima.ui; //public class ClientChatFrame { //} import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.util.List; public class ClientChatFrame extends JFrame { private JTextArea chatArea; // 聊天内容显示 private JTextField inputField; // 输入框 private JButton sendButton; // 发送按钮 private DefaultListModel<String> userListModel; // 用户列表数据模型 private JList<String> onLineUsers = new JList<>(); // 用户列表视图 private Socket socket; private String nickname; public ClientChatFrame (String nickname, Socket socket){ this(); //初始化昵称; //立马展示到昵称到窗口; this.setTitle(nickname + " - 聊天室 💬"); this.nickname = nickname; this.socket = socket; //立即把客户端的Socket管道交给一个独立的管道专门负责读取客户端socket从服务器收到的在线人数更新数据或者群聊数据; new ClientReaderThread(socket,this).start(); } public ClientChatFrame() { initializeUI(); } private void initializeUI() { // 窗口基础设置 // setTitle("局域网聊天窗口 - 宝贝专属 💬"); setSize(700, 500); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); setLocationRelativeTo(null); // 居中 // ------------------- 左侧面板:聊天区域 ------------------- JPanel leftPanel = new JPanel(new BorderLayout()); // 聊天显示区 chatArea = new JTextArea(); chatArea.setEditable(false); chatArea.setFont(new Font("微软雅黑", Font.PLAIN, 14)); chatArea.setLineWrap(true); chatArea.setWrapStyleWord(true); JScrollPane chatScroll = new JScrollPane(chatArea); chatScroll.setBorder(BorderFactory.createTitledBorder("聊天记录")); // 输入区域 JPanel inputPanel = new JPanel(new BorderLayout()); inputField = new JTextField(); sendButton = new JButton("发送"); inputPanel.add(inputField, BorderLayout.CENTER); inputPanel.add(sendButton, BorderLayout.EAST); inputPanel.setPreferredSize(new Dimension(0, 60)); leftPanel.add(chatScroll, BorderLayout.CENTER); leftPanel.add(inputPanel, BorderLayout.SOUTH); // ------------------- 右侧面板:在线用户列表 ------------------- userListModel = new DefaultListModel<>(); onLineUsers = new JList<>(userListModel); onLineUsers.setFont(new Font("微软雅黑", Font.PLAIN, 14)); onLineUsers.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); onLineUsers.setBorder(BorderFactory.createTitledBorder("在线用户")); JScrollPane userScroll = new JScrollPane(onLineUsers); // 设置右侧面板最小宽度 userScroll.setPreferredSize(new Dimension(180, 0)); // ------------------- 主分割器:左右布局 ------------------- JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, userScroll); splitPane.setDividerLocation(500); // 默认左边宽 splitPane.setOneTouchExpandable(true); add(splitPane, BorderLayout.CENTER); // ------------------- 事件绑定 ------------------- sendButton.addActionListener(new SendAction()); inputField.addActionListener(e -> sendButton.doClick()); setVisible(true); } public void setMsgTowin(String msg) { //更新群聊消息到界面展示: chatArea.append(msg); } // 发送消息处理 private class SendAction implements ActionListener { @Override public void actionPerformed(ActionEvent e) { String message = inputField.getText().trim(); if (!message.isEmpty()) { // chatArea.append(" " + message + "\n"); inputField.setText(""); //发送消息到服务端: sendMsgToServer(message); // 自动滚动到底部 SwingUtilities.invokeLater(() -> { JScrollBar bar = ((JScrollPane) getContentPane().getComponent(0)) .getVerticalScrollBar(); bar.setValue(bar.getMaximum()); }); } } } //发送消息 private void sendMsgToServer(String message) { //1、从Socket管道中得到一个特殊数据输出流; try { DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); //2、把消息发给服务器 dos.writeInt(2); dos.writeUTF(" " + message); //(nickname + ": " + message) dos.flush(); } catch (Exception e) { e.printStackTrace(); } } // ------------------- 对外方法:接收消息 ------------------- public void receiveMessage(String sender, String message) { SwingUtilities.invokeLater(() -> { chatArea.append(sender + ": " + message + "\n"); // 自动滚动 JScrollBar bar = ((JScrollPane) getContentPane().getComponent(0)) .getVerticalScrollBar(); bar.setValue(bar.getMaximum()); }); } // ------------------- 对外方法:更新在线用户列表 ------------------- public void updateOnlineUsers(String[] onLineNames) { //把这个线程读取到点在线用户名称展示在界面上; onLineUsers.setListData(onLineNames); } // ------------------- 测试入口 ------------------- public static void main(String[] args) { try { // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel()); } catch (Exception e) { e.printStackTrace(); } SwingUtilities.invokeLater(() -> { ClientChatFrame ui = new ClientChatFrame(); // 模拟测试数据(你可以删掉这部分) ui.receiveMessage("小明", "你好呀,我们开始聊天吧~"); ui.receiveMessage("小红", "我也在哦!"); // String[] testUsers = {"小明", "小红", "宝贝", "Java开发者"}; // ui.updateOnlineUsers(testUsers); }); } } -
ClientReaderThread
javapackage com.itheima.ui; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.Socket; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class ClientReaderThread extends Thread{ private Socket socket; private DataInputStream dis; private ClientChatFrame win; public ClientReaderThread(Socket socket ,ClientChatFrame win){ this.win = win; this.socket = socket; } @Override public void run() { try { //先从Socket管道中接收客户端发来的消息类型编号; dis = new DataInputStream(socket.getInputStream()); while (true) { int type = dis.readInt(); switch (type) { case 1: //服务端发来的在线人数列表; updateClientOnLineUserList(); break; case 2: //群聊消息,从Socket管道中接收群聊消息、再把群聊消息转发给全部在线客户端;; getMsgToWin(); break; case 3: //私聊消息,从Socket管道中接收私聊消息、再把私聊消息转发给指定的客户端; break; } } } catch (Exception e) { e.printStackTrace(); } } private void getMsgToWin()throws Exception { //获取群聊消息; String msg = dis.readUTF(); win.setMsgTowin(msg); } private void updateClientOnLineUserList() throws Exception{ //1、先读取有多少个在线用户; int count = dis.readInt(); //2、循环控制读取多少个用户信息; // List< String> onLineNames = new ArrayList<>(); String[] onLineNames = new String[count]; for (int i = 0; i < count; i++) { String nickname = dis.readUTF(); // onLineNames.add(nickname); onLineNames[i] = nickname; } //3、更新到窗口界面的右侧展示出来; win.updateOnlineUsers(onLineNames); } //给全部客户端推送当前客户发送的消息 // private void sendMsgToAll(String msg) { // //一定要拼装好这个消息,再发给全部在线socket; // //需要包含谁发的,几点发的,什么消息 // StringBuilder sb = new StringBuilder(); // String name = Server.onLineSockets.get(socket); // //获取当前时间; // LocalDateTime now = LocalDateTime.now(); // DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a"); // String nowStr = dtf.format(now); // // String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n") // .append(msg).append("\r\n").toString(); // //然后推送给所有的客户端Socket; // for (Socket socket : Server.onLineSockets.keySet()) { // try { // DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); // dos.writeInt(2); //先发一个消息类型编号,1表示在线人数列表;客户端也要区分消息类型; // dos.writeUTF(msgResult); // dos.flush(); //刷新,发完数据保证数据发送出去; // } catch (Exception e) { // e.printStackTrace(); // } // } // // } // // //更新全部在线人数的列表: // private void updateSocketOnlineList() { // //1、拿到全部在线客户端的用户名称,把这些名称转发给全部在线的socket管道; // //2、拿到全部在线的Socket管道; // Collection<String> onLineSockets = Server.onLineSockets.values(); // //3、把这个集合的所有用户都推送给全部客户端socket管道; // for (Socket socket : Server.onLineSockets.keySet()) { // try { // DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); // dos.writeInt(1); //先发一个消息类型编号,1表示在线人数列表;客户端也要区分消息类型; // dos.writeInt(onLineSockets.size()); // for (String onLineUser : onLineSockets) { // dos.writeUTF(onLineUser); // } // dos.flush(); //刷新,发完数据保证数据发送出去; // } catch (Exception e) { // e.printStackTrace(); // } // } // } } -
Constant
javapackage com.itheima.ui; public class Constant { public static final String SERVER_IP = "127.0.0.1"; public static final int SERVER_PORT = 6666; }
-
-
APP
javaimport com.itheima.ui.ChatEntryFrame; import com.itheima.ui.ClientChatFrame; public class App { public static void main(String[] args) { new ChatEntryFrame(); //启动登录界面; } }
-
- src
