学院 |
计算机科学与教育软件学院 |
年级、专业、班 |
网络16* |
姓名 |
卟咚君 |
学号 |
1606100*** |
|
实验课程名称 |
Java语言实验 |
成绩 |
|
|||||
实验项目名称 |
综合应用实验 |
指导老师 |
** |
一.实验目的
熟悉 Java 综合应用程序的开发。
二.实验任务
编写一个 Java 应用程序,实现多人聊天室(需要用到多线程)。
三.实验内容
编写一个 Java 应用程序,实现多人聊天室(需要用到多线程)
client.java
package Socket;
import java.awt.event.ActionListener;
import java.awt.image.ImageObserver;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
import javax.swing.*;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
public class client extends JFrame implements ActionListener, Runnable { // 声明client类使用Ruannable接口
private static final long serialVersionUID = -8086759001371347347L; //串行版本标识符(不知道为什么要加的,不加class名会有警告)
static Socket socket = null; // 定义一个Socket类作为客户端
// Scanner input = new Scanner(INput); // 创建一个scanner对象,用作数据输入
JTextField field; //多人聊天室文本款
Scanner input = new Scanner(System.in); // 创建一个scanner对象,用作数据输入
static String name = null; // 定义登录客户端的用户名字
static String number=null; //定义是哪一个客户端
private static JTextArea textArea; //定义要发送的信息框
//String lineSeparator;
JButton setButton; //定义一个发送按钮
static client logint; //定义一个登录窗口
public client() { //客户端构造函数,初始化客户端多人聊天界面
super("多人聊天室-欢迎您,"+name); //指向JFrame类,定义窗口名字
textArea = new JTextArea(10, 35); //文本框显示10行30个字符的内容
textArea.setEditable(false); //设定这个文本域不可修改
JScrollPane jsp = new JScrollPane(textArea); //为文本域加上滚动条
jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); //确定垂直滚动条何时显示在滚动窗格上
Container con = this.getContentPane(); // 生成一个容器
con.setLayout(new FlowLayout()); //流式布局
JLabel lbBg1 = new JLabel(" 用户:"+client.name+" "); //在客户端的第一行显示用户名
lbBg1.setBounds(0, 0, ImageObserver.WIDTH, ImageObserver.HEIGHT); //设定lbBg1的布局
con.add(lbBg1); //加入容器
con.add(jsp); //将滚动条(还有文本域)加入容器
JLabel L = new JLabel(); //创建一个JLabel组件显示图像
L.setIcon(new ImageIcon("E:\\开发工具\\java\\byd001\\photo\\byd001_"+((int)(Math.random() * 100)%5+1)+".jpg")); // 路径下的随机照片添加到L这个组件上来
L.setVerticalAlignment(SwingUtilities.CENTER); //L这个组件居中
con.add(L); //将组件L加入容器
field = new JTextField(20); //创建一个文本框,输入发送的信息
setButton = new JButton("发送"); //创建一个按钮,用于触发发送消息事件
setButton.addActionListener(this); //添加监控事件
JPanel pan2 = new JPanel(); //创建一个面板组件
pan2.add(field); //将文本框加入面板组件
pan2.add(setButton); //将发送按钮加入面板组件
con.add(pan2); //将面板组件加入容器
this.setResizable(false); //设定窗口不能改变大小
this.setSize(450, 320); //设定窗口的大小
setLocationRelativeTo(null); //设置窗口居中
this.setVisible(true); //显示窗口
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置用户在此窗体上发起"close"时关闭程序
}
public client(int a) { //重载构造函数,创建一个登录窗口
super("多人聊天室-登录窗口"); //指向JFrame类,定义窗口名字
Container con = this.getContentPane(); //定义一个容器类
con.setLayout(new FlowLayout()); //流式布局
JLabel lbBg1 = new JLabel(" "); //为了是图片居于中心,在前面设置一段空格
lbBg1.setBounds(0, 0, ImageObserver.WIDTH, ImageObserver.HEIGHT);
con.add(lbBg1); //将这一段空格加入容器
// con.setLayout(new GridLayout(5,1));
JLabel L = new JLabel(); //定义一个JLabel组件显示图像
L.setIcon(new ImageIcon("E:\\开发工具\\java\\byd001\\photo\\byd001_0.jpg")); // 将登录图像添加到L这个组件上来
L.setVerticalAlignment(SwingUtilities.CENTER);
con.add(L); //将显示登录图像的控件加入容器
JPanel pan1 = new JPanel(); //定义一个面板组件pan1
JLabel L1 = new JLabel("用户:"); // 显示文本的JLabel组件
JTextField te1 = new JTextField(15); // 用户输入框,括号内是输入框长度
pan1.add(L1); //将L1加入面板组件
pan1.add(te1); //将用户输入框加入面板组件
con.add(pan1); //将面板组件加入容器
JPanel pan2 = new JPanel(); //定义一个面板组件pan2
JLabel L2 = new JLabel("密码:"); // 显示文本的JLabel组件
JPasswordField te2 = new JPasswordField(15);// PasswordField的使用密码输入框
te2.setEchoChar('*'); //用*显示输入的数据
pan2.add(L2);
pan2.add(te2);
con.add(pan2);
JButton B1 = new JButton(" 登录 "); //登录控件
ButtonListener li1 = new ButtonListener(te1, te2); //按钮触发事件
B1.addActionListener(li1); // 给登录按钮添加监听
con.add(B1);
JButton B2 = new JButton(" 取消 ");
con.add(B2);
this.setSize(300, 220); //设置窗体大小
setLocationRelativeTo(null); //设置窗口居中
this.setResizable(false); //窗体大小设置为不可改
this.setVisible(true); //窗体设置为可见
}
public void actionPerformed(ActionEvent event) { //设置发送按钮的监听
Object source = event.getSource();
if ((source == setButton)) {
// 如果确定按钮被按下,则将文本框的文本添加到父窗体的文本域中
textArea.append("(你)");
try {
Thread.sleep(1000); // 线程休眠1秒钟
PrintWriter out = new PrintWriter(socket.getOutputStream());
// 使用PrintWriter建立一个向socket发送信息的通道
String msg = field.getText();
out.println(name + "说:" + msg);
out.flush(); // 清空缓冲区out中的数据
} catch (Exception e) {
System.out.println("服务器已断开连接");
}
}
}
public static void main(String[] args) {
logint = new client(1); //使用创建登录界面的构造函数
}
public void run() { // 重写Runnable接口的run()方法
try {
Thread.sleep(1000); // 线程休眠1秒钟
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// getInputStream()用来获取socket传来的字节流,用InputStreamReader将字节流转换成字符流,然后缓存到BufferedReader缓冲区in中
while (true) {
// System.out.println(in.readLine()); // 将缓冲区中的字符流逐行输出,没有内容则进入阻塞状态
String str = in.readLine();
textArea.append(str + "\n");
// System.out.println("abc");
}
} catch (Exception e) {
// e.printStackTrace();
// System.out.println("服务器已断开连接");
textArea.append("服务器已断开连接" + "\n");
}
}
public class ButtonListener implements java.awt.event.ActionListener { //登录,取消按钮监听
public JTextField te1 = new JTextField(); // 传参
public JPasswordField te2 = new JPasswordField();
public ButtonListener(JTextField te1, JPasswordField te2) {//重载窗体上的用户框,密码框传到监听上来
this.te1 = te1;
this.te2 = te2;
}
public void actionPerformed(ActionEvent e) { // 捕获点击动作
String zhang = te1.getText(); // getText 用于获取输入框内的东西
String mi = String.valueOf(te2.getPassword()); // 获得密码框内的东西(获取的不同方法读者可以自行百度)
if (mi.equals("12345")) { // 设置账号密码匹配(一直连不上数据库,只能设置一个非常简单的登录密码来模拟一下程序)
int x = (int) (Math.random() * 100); // 随机生成一个数值作为客户端的序号
name=zhang; //用户名
logint.setVisible(false); //将登录界面设置为不可见,假设关闭了登录界面
client t = new client(); // 创建一个客户端
client.number = "client" + x; // 客户端的名字就是“client”加上随机序号
textArea.append("************客户端" + x + "*************" + "\n");
try {
socket = new Socket("127.0.0.1", 2333); // 连接服务器,网址是本机地址,端口是2333
textArea.append("已经连上服务器了" + "\n");
} catch (Exception e1) {
textArea.append("连接服务器失败" + "\n");
}
Thread print = new Thread(t); // 创建一个print线程
print.start(); // 调用start(),将print线程从新建状态转换成就绪队列状态
} else {
JOptionPane.showConfirmDialog(null, "用户名或密码错误!","提示",JOptionPane.DEFAULT_OPTION);//弹出提示窗口提示用户名或者密码错误
}
}
}
}
server.java
package Socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class server extends JFrame implements Runnable { // 声明server类使用Ruannable接口
private static final long serialVersionUID = 2162755171253451698L; //串行版本标识符(不知道为什么要加的,不加class名会有警告)
static List socketList = new ArrayList(); // 动态Socket类数组
static Socket socket = null; // 定义一个Socket,建立负责连接到服务器的套接字对象
static ServerSocket serverSocket = null; // 定义一个ServerSocket对象,负责将客户端和服务器端连接起来
private static JTextArea textArea;
public server() { // server类的构造函数
super("服务端示例");
try {
serverSocket = new ServerSocket(2333); // 创建一个ServerSocket对象,端口号为9999
} catch (IOException e) {
e.printStackTrace(); // 出现异常则打印出异常的位置
}
textArea = new JTextArea(10, 30); //设置文本域
textArea.setEditable(false); //文本域不能更改
JScrollPane jsp = new JScrollPane(textArea); //为文本域加上滚动条
jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); //确定垂直滚动条何时显示在滚动窗格上
getContentPane().add("Center", new JScrollPane(jsp)); //将文本域加入容器的中间
this.pack(); //窗口自适应大小
this.setVisible(true); //窗体设置为可见
this.setResizable(false); //窗体大小设置为不可改
setLocationRelativeTo(null); //设置窗口居中
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置用户在此窗体上发起"close"时关闭程序
}
public static void main(String[] args) {
server t = new server(); // 创建一个server对象
textArea.append("************服务端*************" + "\n");
int count = 0; // 计数器count,记录登录到该服务器的客户端个数
while (true) {
try {
socket = serverSocket.accept(); // 返回一个和客户端Socket对象相连接的Socket对象socket
count++; // 计算器count加一
// System.out.println("第" + count + "个客户已连接");
textArea.append("第" + count + "个客户已连接" + '\n');
socketList.add(socket); // 将这个socket加入动态Socket类数组中
} catch (IOException e) {
e.printStackTrace();
}
Print p = new Print(socket); // 创建一个Print类,用于存储和输出目前所有的聊天内容
Thread read = new Thread(t); // 创建一个线程read
Thread print = new Thread(p); // 创建一个线程print
read.start(); // 调用start(),将read线程从新建状态转换成就绪队列状态
print.start(); // 调用start(),将print线程从新建状态转换成就绪队列状态
}
}
public void run() { // 重写Runnable接口的run()方法
try {
Thread.sleep(1000); // 线程休眠1秒钟
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// getInputStream()用来获取服务端传来的字节流,用InputStreamReader将字节流转换成字符流,然后缓存到BufferedReader缓冲区in中
while (true) {
String jieshou = in.readLine(); // 从缓冲区中读取一行
// System.out.println(jieshou);
textArea.append(jieshou + "\n");
for (int i = 0; i < socketList.size(); i++) {
Socket socket = socketList.get(i); // 依次取出和客户端相连接的Socket对象
PrintWriter out = new PrintWriter(socket.getOutputStream()); // 使用PrintWriter建立一个向socket发送信息的通道
if (socket != server.socket) {
out.println(jieshou); // 如果不是当前客户端,则直接显示消息
} else {
out.println(jieshou); // 如果是当前客户端,则显示是自己发送的消息
}
out.flush(); // 清空缓冲区out中的数据
}
}
} catch (Exception e) {
textArea.append("客户已断开连接" + "\n");
}
}
}
class Print implements Runnable { // 声明Print类使用Ruannable接口,Print类用于存储和输出目前所有的聊天内容
static List socketList = new ArrayList(); // 动态Socket类数组
Scanner input = new Scanner(System.in); // 创建一个scanner对象,用作数据输入
public Print(Socket s) { // 构造函数
try {
socketList.add(s); // 将Socket对象加入Socket类数组中
} catch (Exception e) {
// e.printStackTrace();
System.out.println("客户已断开连接");
// textArea.append("客户已断开连接"+"\n");
}
}
public void run() { // 重写Runnable接口的run()方法
try {
Thread.sleep(1000); // 线程休眠1秒钟
while (true) {
String msg = input.next(); // 获取输入的字符
for (int i = 0; i < socketList.size(); i++) {
Socket socket = socketList.get(i);
PrintWriter out = new PrintWriter(socket.getOutputStream()); // 使用PrintWriter建立一个向socket发送信息的通道
out.println("服务端说:" + msg);
out.flush(); // 清空缓冲区out中的数据
}
}
} catch (Exception e) {
System.out.println("客户已断开连接");
}
}
}
四.实验结果记录(程序运行结果截图)
1.先开启服务端
2.开启一个客户端,进行用户登录,进入聊天界面
3.登录密码出错时,提示错误
4.多个用户登录
5.多个用户进行聊天
6.退出客户端
五.实验心得与总结
通过这次实验,掌握开发 Java 应用程序的步骤,熟悉类的基本设计方法,根据 Java 类的继承机制有效解决问题。对Java 的多线程应用程序开发方法有了更深入的了解,熟悉了 Java 系统图形用户界面的工作原理和界面设计步骤,掌握图形用户界面的各种常用组件的使用方法和图形用户界面各种布局策略的设计与使用。原本这个实验可以通过简单的多线程在命令行上可以实现的,但是,我想把之前学过的类的继承以及图像用户界面和多线程结合起来,实现一个多人在线的聊天室的一个可视化程序。由于对java语言运用的不熟练,导致实验花了比较多的时间。在多人聊天室中,每一个打开客户端的程序都会分配一个客户端号,并且和服务端进行相连。在服务器中,运用ServerSocket对象,将客户端和服务器端连接起来,每有一个客户端优用户登录,就用serverSocket.accept()创建一个server与客户端进行连接,借鉴了网上一些getInputStream()用来获取服务端传来的字节流的方法,实现客户端和服务器之间的通信连接,也是通过服务器将一个客户端的消息发送给同一个聊天室的其他客户端上的用户。一开始客户端上的用户是以客户端号进行鉴别的,但这不太符合我们的日常情况。所以我添加了一个用户登录的界面,记录用户的信息,这样聊天的时候就可以直接显示用户的名字了。在登录界面跳转到聊天界面的时候,我曾设计了一个login类,专门用于用户登录的,但是发现在登录成功后,如何触发打开聊天界面和关闭登录界面不是很懂的实现,所以后来我将登录界面写入cilent类的一个重载的构造函数中,如果登录成功的话,我就将用户登录的界面进行屏蔽,假装关闭了,然后新建一个窗口,也就是聊天界面,这样就能实现登录界面到聊天界面的转换。为了美观,我给登录的时候添加了一个登录头像,在聊天的时候,随机给用户添加了一个新的聊天头像,这样看起来好看一点。遇到的问题是,我在登录界面的时候,尝试用eclipse连接oracle数据库,试了一个下午都不成功,试了网上的方法也都以失败告终,后来不得不使用了一些简单的登录验证方式来暂时替代一下。通过这次的实验,提高了自己的编程能力,对java语言有了更深入的了解。