package frame;
import javax.swing.*;
//只是发现以后所有的框都是按照刚才那套流程画出来的
//只不过具体的组件,位置,布局都不一样,但是需要做的事情抽象来看都是一样的
//设计一个抽象的规则
//模板方法模式 23个设计模式当中的一个
//还有一种是:缺省适配器模式 Myframe(接口 10个) baseframe(挑一些不重要的实现掉{} 子类去实现一些必要的方法)
public abstract class baseframe extends Jframe {
public baseframe(String title){
super(title);
}
protected abstract void setOther();
protected abstract void addElements();
protected abstract void setSelf();
}
package frame;
import javax.swing.*;
import java.awt.*;
//简单梳理一下关于Swing的技术
//GUI 图形用户接口(规则) Client/Server
//GUI下面有两个接口:AWT Swing。 可以做但很少用来基于Browser/Server HTML
//想要利用Swing技术来实现一个窗口的绘制
//里面有Jframe窗体 可看成类似于
//还有JPanel面板 可看成类似于 无色透明的容器 做布局管理
//各种组件 按钮 文本框 文本域 理解为HTMl中那些
//JButton按钮 JTextField
//最终肯定是需要有功能 事件-方法 HTML(布局)+CSS(样式)+JS(事件) Tomcat
//事件背后才是我们真正需要实现的强大逻辑/功能
//============================================================
//需要自己描述一个类
//可能需要描述属性,方法
//类与类的关系
//继承 泛化(实现) A is a B
// 聚合 组合 A has-a B
// 包含 依赖 A use-a B
public class QQframe extends baseframe{//是一个窗口 类和类之间的关系 A is-a B 继承
//1.需要一个窗体/窗口
private Jframe frame=new Jframe("聊天框");//GC 边界管理
//2.需要一个无色透明的容器--面板
private JPanel panel=new JPanel();// 流式管理 可以有多个
//3.需要一些聊天窗口的组件
//这是两个带滚动条的文本域
private Jtextarea messArea=new Jtextarea();
private JScrollPane messPane=new JScrollPane(messArea);//滚动条范围大,里面是包着文本域的
private Jtextarea sendArea=new Jtextarea();
private JScrollPane sendPand=new JScrollPane(sendArea);
//两个按钮
private JButton sendButton=new JButton("发送");
private JButton cancelButton=new JButton("取消");
//构造方法 里面可以放这三个方法,或者这三个方法可以放在代码块,让它在构造方法之前加载
public QQframe(String title){
super(title);
//2.调用里面的方法
this.setOther();
this.addElements();
this.setSelf();
}
//设计一个方法 做一些设置 布局 字体。。
@Override
protected void setOther(){
//4.为了让布局更好的好看一点,我们采用一个自动布局的方式
// 自定义布局需要将原有的布局清空
panel.setLayout(null);
//所有的组件按照自己的设计位置放置
messPane.setBounds(10, 10, 320, 220);
sendPand.setBounds(10, 240, 320, 140);
sendButton.setBounds(180, 390, 60, 30);
cancelButton.setBounds(260, 390, 60, 30);
//设置上面接收框的信息不能更改
messArea.setEnabled(false);
//设置文本域中字体的效果
messArea.setFont(new Font("宋体", Font.BOLD,18));
sendArea.setFont(new Font("宋体", Font.BOLD,18));
}
//设计一个方法 做一些组件之间的相互添加
@Override
protected void addElements(){
//5.将这些组件放在panel里,panel放在frame里
panel.add(messPane);
panel.add(sendPand);
panel.add(sendButton);
panel.add(cancelButton);
this.add(panel);
}
//设计一个方法 用来做窗体自己的一些设置 初始位置 不可拖拽
@Override
protected void setSelf(){
//需要在创建出来的同时,设置一下窗口的展示
this.setBounds(500, 200, 350, 480);
//可以设置窗口不可以拖拽改变大小
this.setResizable(false);
//设置点击右上角关闭按钮的同时,让程序结束
this.setDefaultCloseOperation(Jframe.EXIT_ON_CLOSE);// private static final int xxx=3;
//其实窗口已经创建出来了,默认效果是隐藏的
//需要设置那个窗口的状态 隐藏--->显示
//面向对象 窗口的状态-----对象的属性
// 设置状态----事情----方法 setXXXX
this.setVisible(true);
}
}
package frame;
public class Test {
public static void main(String[] args) {
//1.创建一个QQframe窗口对象
new QQframe("聊天框");//无参数构造方法
}
}
package client;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TestClient {
public static void main(String[] args) {
try {
System.out.println("我是客户端");
//1.主动访问服务端,访问成功后,即连接成功,获取一个socket对象
Socket socket=new Socket("127.0.0.1",9999);//服务器地址(IP) 服务器开放的端口号(port)
System.out.println("我成功连接到服务器啦?");
//======================================
Scanner input=new Scanner(System.in);
//2.读取服务器发送过来的消息 读消息/输入流对象
//不能自己创建,需要通过socket对象来回去
InputStream is = socket.getInputStream();
//is其实可以拿去读取消息,is是一个字节型输入流
//为了读取字符更加方便,做一个包装
InputStreamReader isr=new InputStreamReader(is);//字节--->字符转换流--->最终是个字符
//isr其实可以读取中文啦,但是由于对面发送过来的是一行为单位的,isr无法读取一行数据
//为了读取一行比较方便,再做一个包装
BufferedReader reader = new BufferedReader(isr);//包装本身体现出来一种设计模式——————装饰者模式 23
//3.客户端回写点什么 写消息/输出流对象
OutputStream os = socket.getOutputStream();
PrintWriter writer=new PrintWriter(os);
while (true){
String value = reader.readLine();
System.out.println("消息"+value);
System.out.println("回写点什么吧:");
String mess=input.nextLine();
writer.println(mess);
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package server;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TestServer {
public static void main(String[] args) {
try {
System.out.println("===Server start===");
//1.开启一个服务
//调用构造方法,缺少参数
//没法构造对象,接口/抽象类
//类中构造方法虽然有,但是都是private(私有化),提供的都是类似工厂一样的方法
//构建对象的时候,出现了编译时异常
// ArrayIndexOutofBoundsException
// NullPointerException
// InputMisMatchException
// NumberFormatException
// ArithmeticException
// ClassCastException
// StringIndexOutOfBoundsException
ServerSocket server=new ServerSocket(9999);//0——65535//服务器开发的端口号,为了让客户的数据从这个端口进入到服务器来的
//2.开启服务之后,等待某一个客户端过来访问我,需要服务器统一/接收
Socket socket = server.accept();
System.out.println("有一个客户端跟我连接成功啦");
//================================================================
Scanner input=new Scanner(System.in);
//3.先让服务器跟客户端说一句话————写消息/输出流
//不是自己随意创建的输出流,得通过socket来获取
OutputStream os = socket.getOutputStream(); //流(读 I/写O) 字节/字符(Reader、Writer)
//上述os对象可以写消息了,但是os是一个字节流,发送中文的时候不方便
//字节流进行一个包装---->字符流
PrintWriter writer=new PrintWriter(os);
//4.服务器读取回写的消息 读信息/输入流对象
InputStream is = socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is);
BufferedReader reader=new BufferedReader(isr);
while(true){
//4.可以发送数据啦
System.out.println("说点什么吧:");
String mess = input.nextLine();
writer.println(mess);
writer.flush();//刷新 清空流管道
String value = reader.readLine();
System.out.println("消息:"+value);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
三、客户端发起聊天
四、完成客户端界面交互
优化代码
package frame;
import javax.swing.*;
//只是发现以后所有的框都是按照刚才那套流程画出来的
//只不过具体的组件,位置,布局都不一样,但是需要做的事情抽象来看都是一样的
//设计一个抽象的规则
//模板方法模式 23个设计模式当中的一个
//还有一种是:缺省适配器模式 Myframe(接口 10个) baseframe(挑一些不重要的实现掉{} 子类去实现一些必要的方法)
public abstract class baseframe extends Jframe {
public baseframe(String title){
super(title);
}
protected abstract void setOther();
protected abstract void addElements();
protected abstract void addListener();
protected abstract void setSelf();
}
package frame;
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.Socket;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
//简单梳理一下关于Swing的技术
//GUI 图形用户接口(规则) Client/Server
//GUI下面有两个接口:AWT Swing。 可以做但很少用来基于Browser/Server HTML
//想要利用Swing技术来实现一个窗口的绘制
//里面有Jframe窗体 可看成类似于
//还有JPanel面板 可看成类似于 无色透明的容器 做布局管理
//各种组件 按钮 文本框 文本域 理解为HTMl中那些
//JButton按钮 JTextField
//最终肯定是需要有功能 事件-方法 HTML(布局)+CSS(样式)+JS(事件) Tomcat
//事件背后才是我们真正需要实现的强大逻辑/功能
//============================================================
//需要自己描述一个类
//可能需要描述属性,方法
//类与类的关系
//继承 泛化(实现) A is a B
// 聚合 组合 A has-a B
// 包含 依赖 A use-a B
public class QQframe extends baseframe{//是一个窗口 类和类之间的关系 A is-a B 继承
//构造方法 里面可以放这三个方法,或者这三个方法可以放在代码块,让它在构造方法之前加载
public QQframe(String uid){
super(uid);
this.uid=uid;
//1.真的跟服务器产生一个连接
this.start();
//2.调用里面的方法
this.setOther();
this.addElements();
this.addListener();
this.setSelf();
}
//1.需要一个窗体/窗口
private Jframe frame=new Jframe("聊天框");//GC 边界管理
//2.需要一个无色透明的容器--面板
private JPanel panel=new JPanel();// 流式管理 可以有多个
//3.需要一些聊天窗口的组件
//这是两个带滚动条的文本域
private Jtextarea messArea=new Jtextarea();
private JScrollPane messPane=new JScrollPane(messArea);//滚动条范围大,里面是包着文本域的
private Jtextarea sendArea=new Jtextarea();
private JScrollPane sendPand=new JScrollPane(sendArea);
//两个按钮
private JButton sendButton=new JButton("发送");
private JButton cancelButton=new JButton("取消");
//设计一个方法 做一些设置 布局 字体。。
@Override
protected void setOther(){
//4.为了让布局更好的好看一点,我们采用一个自动布局的方式
// 自定义布局需要将原有的布局清空
panel.setLayout(null);
//所有的组件按照自己的设计位置放置
messPane.setBounds(10, 10, 320, 220);
sendPand.setBounds(10, 240, 320, 140);
sendButton.setBounds(180, 390, 60, 30);
cancelButton.setBounds(260, 390, 60, 30);
//设置上面接收框的信息不能更改
messArea.setEnabled(false);
//设置文本域中字体的效果
messArea.setFont(new Font("黑体", Font.BOLD,18));
sendArea.setFont(new Font("黑体", Font.BOLD,18));
}
//设计一个方法 做一些组件之间的相互添加
@Override
protected void addElements(){
//5.将这些组件放在panel里,panel放在frame里
panel.add(messPane);
panel.add(sendPand);
panel.add(sendButton);
panel.add(cancelButton);
this.add(panel);
}
//设计一个方法 给窗口中的某些组件添加事件
@Override
protected void addListener(){
//给取消按钮绑定一个事件---对象(帮你去做) 观察者模式Observer 按钮-目标 做事-观察者对象
cancelButton.addActionListener(e->{
sendArea.setText("");
});
//给发送按钮绑定一个事件---
sendButton.addActionListener(e -> {
try {
PrintWriter writer=new PrintWriter(socket.getOutputStream());
String message = sendArea.getText();
//获取一个系统当前时间
Date date=new Date();
DateFormat df=new SimpleDateFormat("yyyy-MM-dd kk:mm:ss");
String time = df.format(date);
writer.println(time+"##@##"+uid+":"+message);//客户端真正发送的消息
writer.flush();
//发送框文字清空
sendArea.setText("");
} catch (IOException e1) {
e1.printStackTrace();
}
});
}
//设计一个方法 用来做窗体自己的一些设置 初始位置 不可拖拽
@Override
protected void setSelf(){
//需要在创建出来的同时,设置一下窗口的展示
this.setBounds(500, 200, 350, 480);
//可以设置窗口不可以拖拽改变大小
this.setResizable(false);
//设置点击右上角关闭按钮的同时,让程序结束
this.setDefaultCloseOperation(Jframe.EXIT_ON_CLOSE);// private static final int xxx=3;
//其实窗口已经创建出来了,默认效果是隐藏的
//需要设置那个窗口的状态 隐藏--->显示
//面向对象 窗口的状态-----对象的属性
// 设置状态----事情----方法 setXXXX
this.setVisible(true);
}
//=============================================================================
//因为当前的QQframe就是那个客户端,
// 所以客户端需要有一个uid
private String uid;
//所以客户端需要一个socket
private Socket socket;
//====================================================
public void start(){
try {
//1.主动访问服务端
socket=new Socket("127.0.0.1",9999);
//2.客户端将自己的uid写给服务器
PrintWriter writer=new PrintWriter(socket.getOutputStream());
writer.println(uid);
writer.flush();
//3.将socket分别交给两个小弟,读/写
ClientReader cr=new ClientReader();
cr.start();
} catch (IOException e) {
e.printStackTrace();
}
}
//====================================================
//客户端读线程(剩下的那个小弟)---只管客户端自己用
//1.不想让别人看见我客户端自己不干活,还有一个小弟帮我
//2.节省一个类文件
//3.客户端读线程,读取的数据不是在控制台展示的,是需要展示在客户端的文本域中
// 最主要才是这个问题,内部类中直接使用外部类成员
private class ClientReader extends Thread{
//频繁的追加新的字符串,不能让聊天框中的文字清空
StringBuilder result=new StringBuilder();
@Override
public void run(){
//读取
try {
InputStream is=socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is);
BufferedReader reader=new BufferedReader(isr);//可以读取一行
while (true){
String value=reader.readLine();
//time##@##uid:message
value.replace("##@##", "rn");
result.append(value);
result.append("n");
//找到那个文本域,value放里即可
messArea.setText(result.toString());
}
} catch (IOException e) {
//e.printStackTrace();
System.out.println("服务器宕机啦");
}
}
}
}
package frame;
import frame.QQframe;
public class TestClient {
public static void main(String[] args) {
//1.创建客户端
new QQframe("zzt");
new QQframe("dmc");
new QQframe("Ella");
}
// public static void main(String[] args) {
// try {
// System.out.println("我是客户端");
// //1.主动访问服务端,访问成功后,即连接成功,获取一个socket对象
// Socket socket=new Socket("127.0.0.1",9999);//服务器地址(IP) 服务器开放的端口号(port)
// System.out.println("我成功连接到服务器啦?");
// //======================================
// Scanner input=new Scanner(System.in);
//
// //2.读取服务器发送过来的消息 读消息/输入流对象
// //不能自己创建,需要通过socket对象来回去
// InputStream is = socket.getInputStream();
// //is其实可以拿去读取消息,is是一个字节型输入流
// //为了读取字符更加方便,做一个包装
// InputStreamReader isr=new InputStreamReader(is);//字节--->字符转换流--->最终是个字符
// //isr其实可以读取中文啦,但是由于对面发送过来的是一行为单位的,isr无法读取一行数据
// //为了读取一行比较方便,再做一个包装
// BufferedReader reader = new BufferedReader(isr);//包装本身体现出来一种设计模式——————装饰者模式 23
// //3.客户端回写点什么 写消息/输出流对象
// OutputStream os = socket.getOutputStream();
// PrintWriter writer=new PrintWriter(os);
//
// while (true){
// String value = reader.readLine();
// System.out.println("消息"+value);
//
// System.out.println("回写点什么吧:");
// String mess=input.nextLine();
// writer.println(mess);
// writer.flush();
// }
// } catch (IOException e) {
// e.printStackTrace();
// }
//
// }
}
package server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
//服务器类
public class Server {
//属性集合----Map
static HashMap userBox=new HashMap();
public void start(int port){
try {
System.out.println("====server start===");
ServerSocket server=new ServerSocket(port);
while (true){//服务器开启,一直等待客户端
Socket socket = server.accept();
//将socket对象存入map集合
//读取客户端发来的Uid 服务端可以获取socket,但是没有Uid(客户端发送过来)
//
InputStream is = socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is);
BufferedReader reader=new BufferedReader(isr);
String uid = reader.readLine();
//构建一个User对象
User user=new User(uid, socket);
userBox.put(uid, user);
System.out.println(uid+"成功连接到服务器啦");
//将这个socker交给一个小弟来负责处理
ServerThread st=new ServerThread(user);
st.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package server;
import java.io.*;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
//服务端线程
public class ServerThread extends Thread{
private User user;
public ServerThread(User user){
this.user=user;
}
@Override
public void run(){
try {
//1.读取刚才那个socket客户端发送过来的数据
InputStream is = user.getSocket().getInputStream();
InputStreamReader isr=new InputStreamReader(is);
BufferedReader reader=new BufferedReader(isr);
while (true){
//服务器从客户端那里读取过来的真实数据/消息
String message =reader.readLine();
System.out.println("接收到"+user.getUid()+"xxx发送过来的"+message);
//2.将数据转发给其他socket对应的客户端------群聊 map遍历 User(socket)
//想要转发出去,需要找到那个map
Map map=Server.userBox;//
//将map集合遍历,获取里面全部的user,拿到每一个user里面的socket--->
Set keySet = map.keySet();
Iterator it = keySet.iterator();
while (it.hasNext()){
String uid= (String)it.next();
User user=map.get(uid);
PrintWriter pw=new PrintWriter(user.getSocket().getOutputStream());
pw.println(message);
pw.flush();
}
}
} catch (IOException e) {
//e.printStackTrace();
System.out.println(user.getUid()+"下线啦");
}
}
}
package server;
public class TestServer {
public static void main(String[] args) {
//1.启动一个服务
Server server=new Server();
//2.启动即可
server.start(9999);
}
// public static void main(String[] args) {
// try {
// System.out.println("===Server start===");
// //1.开启一个服务
// //调用构造方法,缺少参数
// //没法构造对象,接口/抽象类
// //类中构造方法虽然有,但是都是private(私有化),提供的都是类似工厂一样的方法
// //构建对象的时候,出现了编译时异常
// // ArrayIndexOutofBoundsException
// // NullPointerException
// // InputMisMatchException
// // NumberFormatException
// // ArithmeticException
// // ClassCastException
// // StringIndexOutOfBoundsException
// ServerSocket server=new ServerSocket(9999);//0——65535//服务器开发的端口号,为了让客户的数据从这个端口进入到服务器来的
// //2.开启服务之后,等待某一个客户端过来访问我,需要服务器统一/接收
// Socket socket = server.accept();
// System.out.println("有一个客户端跟我连接成功啦");
// //================================================================
//
// Scanner input=new Scanner(System.in);
//
// //3.先让服务器跟客户端说一句话————写消息/输出流
// //不是自己随意创建的输出流,得通过socket来获取
// OutputStream os = socket.getOutputStream(); //流(读 I/写O) 字节/字符(Reader、Writer)
// //上述os对象可以写消息了,但是os是一个字节流,发送中文的时候不方便
// //字节流进行一个包装---->字符流
// PrintWriter writer=new PrintWriter(os);
//
//
// //4.服务器读取回写的消息 读信息/输入流对象
// InputStream is = socket.getInputStream();
// InputStreamReader isr=new InputStreamReader(is);
// BufferedReader reader=new BufferedReader(isr);
//
// while(true){
// //4.可以发送数据啦
// System.out.println("说点什么吧:");
// String mess = input.nextLine();
// writer.println(mess);
// writer.flush();//刷新 清空流管道
//
// String value = reader.readLine();
// System.out.println("消息:"+value);
// }
// } catch (IOException e) {
// e.printStackTrace();
// }
//
//
//
// }
}
package server;
import java.net.Socket;
//服务器端自己创建的一个类型
//目的:为了将一个用户名和这个用户对应的socket对象包括包装在一起
//每一个User对象,其实就是一个存储数据的容器
public class User {
private String uid;
private Socket socket;
public User(String uid, Socket socket) {
this.uid = uid;
this.socket = socket;
}
public String getUid() {
return uid;
}
public Socket getSocket() {
return socket;
}
}



