经过一周的学习与搬砖,我成功的完成了暑假集训的第一个项目——Java简易聊天室,这里对整个项目做一个总结。(文末附下载地址)
本项目支持的功能:
1.可同时开启多个客户端进行多人聊天;
2.可与在线的所有用户进行群聊;
3.可选择一个在线的用户进行私聊。
4.实时更新的用户列表(用户登陆或者退出时均会在其他已经登陆的用户界面上显示);
5.可将文件上传至服务端;
6.可将已经上传过的文件下载至本地。
PS:
1.本项目仅为学习用项目;
2.本项目的服务器在自己的电脑上运行,客户端也在自己电脑上开启。所有聊天消息,文件传输都基于网络传输进行。
项目准备
在D盘准备upload文件夹和updown文件夹
项目效果
用于开启客户端的界面:
客户端UI界面
群聊
选择私聊用户及选择后的效果
私聊
上传文件
下载文件
项目编写时间安排
第一天:看视频、看书,复习IO流知识,学习多线程知识;
第二天 :看视频学习网络编程知识,开始构思项目流程,在网上找别人的作品并学习思路;
第三天:完成UI界面的设计 对群聊私聊的实现有了初步的规划;
第四天:完成群聊私聊的功能,解决了一些逻辑性问题;
第五天:开始编写文件上传 并对出现的bug进行解决,将界面与功能联系在一起;
第六天:解决文件上传的bug,完成下载功能的实现。
项目难点及思路
1.项目初期,通过看书以及优秀学长的代码,了解到使用一个编写一个Map类用于储存用户名与输出流的对应关系(或者与Socket客户端),这里使我少走了很多弯路,也使我的群聊与私聊功能实现的比较容易;
2.在完成文件上传时,刚开始的思路是使用TCP传输,但我的整个项目都是基于带有标识符的客户端与服务器的收发流来完成的,如果采用TCP 则文件的前后将带上标识符,这破坏了文件的完整性。因此在思考后 :我给出了三种解决方案,并一一尝试:
(1)在接收到带有文件上传标识符的流后,服务器开启另外的线程来接收文件;但这个方法在我仔细思考后pass掉了,因为它与下面的第二种方案效果差不多,而且更复杂低效;
(2)在上传文件时直接进行上传,不带任何标识符,而在用于接收流的服务端线程类ServerThread里,是用if else if else 来接收消息的,我只需要把接收文件的代码写在else 里就可以了,这样虽然文件没有带标识符,还是可以被下载到。
但这个方法在尝试后还是被pass了,因为接收到的文件为空,发生了失真。(项目写完后进行反思时还是觉得是自己的代码有问题);
(3)在群聊私聊时使用了TCP协议,但是在文件上传与下载时不一定使用TCP协议,可以使用UDP协议。因此我使用UDP进行文件传输的功能,并且获得了成功。
项目的基本思路就是这样,接下来是项目最重要的五个类。
package Client;
import UI.ClientUI;
import tool.CrazyitProtocol;
import javax.swing.*;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
/*
客户端主类
*/
public class Client {
private static final int SERVER_PORT = 1211;//端口号
private Socket socket;
private PrintStream ps;
private BufferedReader brServer;
private BufferedReader In;
public String name = "魑魅魍魉魑魅魍魉魑魅魍魉";
public ClientUI ui;
//客户端构造器
public Client(ClientUI clientUI) throws IOException//传入ui界面 对JTextArea和JTextFiled进行管理
{
this.ui = clientUI;
init(ui);
}
public void init(ClientUI ui) throws IOException {
//获取用户输入的名字
String name = ui.getNameJT();
//验空
if(name.equals(""))
{
ui.new_Msg("用户名不能为空!");
return;
}
socket = new Socket("127.0.0.1",SERVER_PORT);
ps = new PrintStream(socket.getOutputStream());
brServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
try {
//发送一个带有登录标识符的流
ps.println(CrazyitProtocol.USER_ROUND + name + CrazyitProtocol.USER_ROUND);
//接收服务器传回的值
String result = brServer.readLine();
//用户名重复
if(result.equals(CrazyitProtocol.NAME_REP))
{
ui.setTalk("用户名重复!请重新输入");
socket.close();
}
//登录成功
if(result.equals(CrazyitProtocol.LOGIN_SUCCESS))
{
ui.setTalk("登录成功!");
ui.setNameJTEdit();
//开启一个客户端接收服务器消息的线程
new ClientThread(brServer,ui).start();
//进行用户列表的更新及文件列表的获取
ps.println(CrazyitProtocol.UPDATE1+ "" + CrazyitProtocol.UPDATE1);
ps.println(CrazyitProtocol.DOWN_SIGN + "" +CrazyitProtocol.DOWN_SIGN);
}
}
catch (UnknownHostException e)
{
ui.setTalk("找不到远程服务器,请确定服务器已经启动!");
closeRs();
}
catch (IOException e)
{
ui.setTalk("网络异常!请检查网络状况");
closeRs();
}
}
//发送消息
public void readAndSend() {
try {
String line = ui.getMsg();
//验空
if (line.equals("")) {
ui.new_Msg("发送的消息不能为空!");
return;
}
//私聊
//如果UI界面的文本输入框内的文字 起始为@而且“:”的位置大于0(即输入了要私聊的名字) 发送带有私聊标识符的消息给服务器
if (line.indexOf(":") > 0 && line.startsWith("@")) {
String name = ui.getNameJT();
//不能私聊自己的判断
if((line.indexOf(name)>0)&&(line.indexOf(name) < line.indexOf(":")))
{
ui.new_Msg("不能给自己发消息!");
ui.setMsg("");
return;
}
//将@分割掉
line = line.substring(1);
ps.println(CrazyitProtocol.PRIVATE_ROUND + line.split(":")[0] + CrazyitProtocol.SPLIT_SIGH +
line.split(":")[1] + CrazyitProtocol.PRIVATE_ROUND);
//设置UI界面消息框为空
ui.setMsg("");
}
//群聊
//发送带有群聊标志的消息
else {
ps.println(CrazyitProtocol.MSG_ROUND + line + CrazyitProtocol.MSG_ROUND);
ui.setMsg("");
}
} catch (Exception e) {
e.printStackTrace();
}
}
//使用Udp进行文件上传
public void UpDate() throws IOException {
//获取要上传文件的文件名
String filename = JOptionPane.showInputDialog("请输入文件名:");
//验空
if (filename.equals("") == false) {
//发送带有文件上传的流
ps.println(CrazyitProtocol.FILE_ROUND + filename + CrazyitProtocol.FILE_ROUND);
DatagramSocket datagramSocket = null;
try {
datagramSocket = new DatagramSocket(1210);
} catch (SocketException socketException) {
socketException.printStackTrace();
}
//创建文件输入流,读取本地文件
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(new File("src\\txt\\" + filename));
} catch (FileNotFoundException fileNotFoundException) {
fileNotFoundException.printStackTrace();
}
//读取输入流中的数据
byte[] bytes = new byte[0];//创建数组
try {
bytes = new byte[fileInputStream.available()];
} catch (IOException ioException) {
ioException.printStackTrace();
}
try {
fileInputStream.read(bytes);//给数组写入数据
} catch (IOException ioException) {
ioException.printStackTrace();
}
//创建数据载体DatagramPacket
DatagramPacket datagramPacket = null;
try {
datagramPacket = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getByName("127.0.0.1"), 1211);
} catch (UnknownHostException unknownHostException) {
unknownHostException.printStackTrace();
}
//发送
try {
datagramSocket.send(datagramPacket);
} catch (IOException ioException) {
ioException.printStackTrace();
}
//发送完成后向服务器发送一条带有文件列表更新标识符的消息 使服务器更新每个用户的文件列表
ps.println(CrazyitProtocol.DOWN_SIGN + "" + CrazyitProtocol.DOWN_SIGN);
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
//成功上传,弹框
datagramSocket.close();
JOptionPane.showMessageDialog(null, "上传成功");
}
}
//关闭流的方法
private void closeRs(){
try {
if(brServer != null)
{
brServer.close();
}
if