这是一个SVN通信的项目,涵盖服务器与客户端的文件收发流程,目的是为了保证文件能够正确的在网络中进行传输,客户端上传的文件可以保存在服务器中,同时客户端可以下载服务器之前有用户上传过的所有文件,并以列表的形式展示在客户端。由于当时没有学习数据库,因此在当时处理的时候,文件都是存储在指定文件夹中的,当然这里我们的关注点还是文件在网络中的传输,剩下的可以随着我们学习的深入再进行改进。我认为这种的学习方式非常好,因为学习是一个循序渐进的过程,平时对知识点的学习就应该像单元测试一样,了解我们的关注点并针对进行学习,最后伴随着学习的深入在全部综合运用。
这个项目中我负责的是客户端的文件发送下载与刷新列表,客户端项目的具体分包情况如下:
这里我的业务逻辑的处理基本上都是在这个net.control包下的,我处理的方式是把基本的监听都和界面分开来,单独拿出来处理业务,给他们相应的要传输的参数。
package net.control;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
public class MyClient {
// 声明对象输入输出流
private ObjectOutputStream oos;
private ObjectInputStream ois;
public MyClient() {
setClient();
}
public void setClient() {
try {
Socket client = new Socket("localhost", 9090);
System.out.println("客户端启动成功");
OutputStream ous = client.getOutputStream();
InputStream ins = client.getInputStream();
// 包装数据流
oos = new ObjectOutputStream(ous);
ois = new ObjectInputStream(ins);
} catch (IOException e) {
e.printStackTrace();
}
}
public ObjectOutputStream getOos() {
return oos;
}
public ObjectInputStream getOis() {
return ois;
}
}
这个类是用来创建socket以及对象输入输出流的,这里之前处理的时候以这种比较严谨的方式封装的,后来学了一些设计模式后发现这里还是用单例模式比较好,这样能保证每次只能够拿到一个对应且相同的socket的对象,ObjectOutputStream对象和ObjectInputStream对象。
package net.control;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.swing.JOptionPane;
import net.view.ChooseFrame;
import net.view.LogonPanel;
import net.view.RegistFrame;
public class LogonListener implements ActionListener {
private String str;
private String command;
private String username;
private String password;
public MyClient mc;
public LogonPanel lp;
public LogonListener(LogonPanel lp) {
this.lp = lp;
}
public void actionPerformed(ActionEvent e) {
// 点击登陆按钮并且登陆成功时执行的界面切换
if (e.getSource() == lp.logon_button) {
mc = new MyClient();
username = lp.account_text.getText();
password = lp.password_text.getText();
command = "logon";
// 发消息
try {
mc.getOos().writeUTF(command);
mc.getOos().flush();
mc.getOos().writeUTF(username);
mc.getOos().flush();
mc.getOos().writeUTF(password);
mc.getOos().flush();
str = mc.getOis().readUTF();
if (str.equals("logon ok")) {
lp.lf.dispose();
ChooseFrame cf = new ChooseFrame(lp.lf);
} else if (str.equals("logon fail")) {
JOptionPane.showMessageDialog(null, "用户名或密码不正确");
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (e.getSource() == lp.regist_button) {
RegistFrame rf = new RegistFrame(lp);
command = "注册";
}
}
}
这个类是对登陆的监听,这里把监听器不写作匿名内部类而单独写出来用来实现ActionListener接口,把要传入的参数传进来的目的是进行相应的页面跳转处理。
package net.control;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import javax.swing.JOptionPane;
import net.view.RegistPanel;
public class RegistListener implements ActionListener {
public RegistPanel rp;
public RegistListener(RegistPanel rp) {
this.rp = rp;
}
public void actionPerformed(ActionEvent e) {
System.out.println(rp.rf.lp.ll.mc.getOos());
//
ObjectOutputStream oos = rp.rf.lp.ll.mc.getOos();
ObjectInputStream ois = rp.rf.lp.ll.mc.getOis();
if (e.getSource() == rp.jbt) {
String str = "regist";
try {
oos.writeUTF(str);
oos.flush();
String s = ois.readUTF();
if (s.equals("regist ok")) {
rp.rf.dispose();
} else if (s.equals("regist fail")) {
JOptionPane.showMessageDialog(null, "注册失败,您输入的用户名已经存在或密码为空");
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
这个类是对注册的监听
package net.control;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.Vector;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import net.common.Tool;
import net.dao.Data;
import net.view.SendPanel;
public class SendListener implements ActionListener {
public File file;
private String str;
// 添加该监听的面板
public SendPanel sp;
// 构造方法
public SendListener(SendPanel sp) {
this.sp = sp;
}
public void actionPerformed(ActionEvent e) {
// 点击选择文件按钮时的操作
if (e.getSource() == sp.jbt) {
JFileChooser jfc = new JFileChooser();
jfc.showOpenDialog(null);
file = jfc.getSelectedFile();
sp.pathfield.setText(file.getAbsolutePath().toString());
System.out.println("内容为" + sp.pathfield.getText());
System.out.println(file.getName());
// 文件队列中加入该文件
Data.filelist.add(file);
long length = file.length();
Vector<String> vec = new Vector();
vec.add(file.getName());
vec.add(String.valueOf(file.length()) + "字节");
vec.add(new Date().toString());
vec.add("等待上传");
sp.data.add(vec);
}
// 点击提交按钮时的操作
if (e.getSource() == sp.submit) {
try {
ObjectOutputStream oos = sp.sff.cf.lf.lp.ll.mc.getOos();
oos.writeUTF("send file");
oos.flush();
// 遍历要上传的文件列表
oos.writeObject(Data.filelist);
oos.flush();
for (int i = 0; i < Data.filelist.size(); i++) {
File myfile = Data.filelist.get(i);
Tool.sendFile(myfile, oos);
}
JOptionPane.showMessageDialog(null, "文件传输完毕");
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
这个是对客户端传输文件的监听,如果成功或者失败都会有相应的弹窗提示传输的结果。
package net.control;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Vector;
import javax.swing.JOptionPane;
import net.common.Tool;
import net.dao.Data;
import net.view.DownloadPanel;
public class DownloadListener implements ActionListener {
public ArrayList<String> namelist = new ArrayList<String>();
public Vector<String> vec;
public DownloadPanel dp;
private String str;
public int count;
public DownloadListener(DownloadPanel dp) {
this.dp = dp;
}
public void actionPerformed(ActionEvent e) {
ObjectOutputStream oos = dp.sff.cf.lf.lp.ll.mc.getOos();
ObjectInputStream ois = dp.sff.cf.lf.lp.ll.mc.getOis();
// 点击刷新列表按钮时:
if (e.getSource() == dp.update) {
str = "update jtable";
try {
dp.data.clear();
oos.writeUTF(str);
oos.flush();
// 刷新时先清除列表,再把现存队列中的数据写进去
Data.downlist = (ArrayList<File>) ois.readObject();
for (int i = 0; i < Data.downlist.size(); i++) {
File file = Data.downlist.get(i);
vec = new Vector<String>();
vec.add(file.getName());
vec.add(String.valueOf(file.length()) + "字节");
vec.add(new Date().toString());
vec.add("等待下载");
dp.data.add(vec);
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
// 点击下载按钮时
if (e.getSource().equals(dp.download)) {
str = "download file";
try {
oos.writeUTF(str);
oos.flush();
// 在选择队列添加选择的行
Tool.getSelectedList(dp);
// 把选择的队列发过去
oos.writeObject(Data.selectlist);
oos.flush();
for (int i = 0; i < Data.selectlist.size(); i++) {
FileOutputStream fos = new FileOutputStream("D:/文件下载/" + Data.selectlist.get(i).getName());
Tool.getFile(ois, fos);
}
JOptionPane.showMessageDialog(null, "下载完毕");
} catch (IOException e1) {
e1.printStackTrace();
}
}
// 点击同步服务器按钮时
if (e.getSource() == dp.localout) {
str = "local out";
try {
oos.writeUTF(str);
oos.flush();
for (int i = 0; i < Data.downlist.size(); i++) {
FileOutputStream fos = new FileOutputStream("D:/文件下载/" + Data.downlist.get(i).getName());
Tool.getFile(ois, fos);
System.out.println("客户端同步完毕");
}
JOptionPane.showMessageDialog(null, "同步服务器完毕");
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
这是对从服务器下载文件的监听,这里包括选择单个或几个一起下载和同步服务器(即把服务器所有文件都下载下来)
package net.control;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Vector;
import net.dao.Data;
import net.view.ChoosePanel;
import net.view.SendFileFrame;
public class ChooseListener implements ActionListener {
public ChoosePanel cp;
public Vector<String> vec;
public ChooseListener(ChoosePanel cp) {
this.cp = cp;
}
public void actionPerformed(ActionEvent e) {
// 点击上传文件按钮直接切换界面
if (e.getSource() == cp.update_button) {
// 界面切换
cp.cf.dispose();
SendFileFrame sff = new SendFileFrame(cp.cf);
sff.pp.getCl().first(sff.pp);
}
// 点击下载文件按钮时切换页面并且在页面加载服务器的文件列表
if (e.getSource() == cp.download_button) {
// 界面切换
cp.cf.dispose();
SendFileFrame sff = new SendFileFrame(cp.cf);
sff.pp.getCl().last(sff.pp);
String str = "update jtable";
// 获取对象流
ObjectOutputStream oos = cp.cf.lf.lp.ll.mc.getOos();
ObjectInputStream ois = cp.cf.lf.lp.ll.mc.getOis();
try {
oos.writeUTF(str);
oos.flush();
// 对象输入流写入文件
Data.downlist = (ArrayList<File>) ois.readObject();
for (int i = 0; i < Data.downlist.size(); i++) {
File file = Data.downlist.get(i);
vec = new Vector<String>();
vec.add(file.getName());
vec.add(String.valueOf(file.length()) + "字节");
vec.add(new Date().toString());
vec.add("等待下载");
sff.pp.dlp.data.add(vec);
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
这里是用来刷新展示列表的。那么基本的业务流程就是这些,剩下的工具类和数据类保存着一些 要经常用到的方法或属性,都定义为static的
package net.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import net.dao.Data;
import net.view.DownloadPanel;
public class Tool {
public static void getSelectedList(DownloadPanel dp) {
// 获取选择的文件内容
int[] array = dp.table.getSelectedRows();
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < Data.downlist.size(); j++) {
String filename = (String) dp.table.getValueAt(array[i], 0);
if (filename.equals(Data.downlist.get(j).getName())) {
Data.selectlist.add(Data.downlist.get(j));
}
}
}
}
// 发送文件的方法
public static void sendFile(File file, ObjectOutputStream oos) {
try {
FileInputStream fis = new FileInputStream(file);
// 统计写入次数作为协议传输
long length = file.length() / 2048;
int last = (int) file.length() % 2048;
// 写入协议
oos.writeLong(length);
oos.flush();
oos.writeInt(last);
oos.flush();
System.out.println("发送length:" + length + " " + last);
byte[] bytes = new byte[2048];
while (length > 0) {
fis.read(bytes);
oos.write(bytes);
oos.flush();
length--;
}
if (last > 0) {
bytes = new byte[last];
fis.read(bytes);
oos.write(bytes);
oos.flush();
}
fis.close();
System.out.println("文件写入完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
// 接收文件方法
public static void getFile(ObjectInputStream ois, FileOutputStream fos) {
try {
// 传输过程
long length = ois.readLong();
int last = ois.readInt();
byte[] bytes = new byte[2048];
while (length > 0) {
ois.readFully(bytes);
fos.write(bytes);
fos.flush();
length--;
}
if (last > 0) {
bytes = new byte[last];
ois.readFully(bytes);
fos.write(bytes);
fos.flush();
}
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package net.dao;
import java.io.File;
import java.util.ArrayList;
/**
* Data类用来存各种要传的队列
*
* @author panhao
*
*/
public class Data {
// 队列用于保存要上传的文件
public static ArrayList<File> filelist = new ArrayList<File>();
// 队列用于保存要下载的文件
public static ArrayList<File> downlist = new ArrayList<File>();
// 队列保存选择要下载的文件
public static ArrayList<File> selectlist = new ArrayList<File>();
}
总体上客户端大概的业务和数据代码就这么多,界面的话,swing写的比较丑,代码有太多就不列出来了。
接下来,服务器这边主要就这两个类的代码,由于这块主要不是我弄,罗列一下代码大家看一下吧:
package net.control;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
import net.dao.DataBase;
public class MyServer extends Thread {
private int port;
private ServerSocket server;
private Vector<Vector<String>> data;
public MyServer(int port, Vector<Vector<String>> data) {
this.port = port;
this.data = data;
}
public ServerSocket getServer() {
return server;
}
public void run() {
try {
// 1.创建服务器对象
server = new ServerSocket(9090);
System.out.println("服务器创建好了");
// 2.获取服务
while (true) {
Socket socket;
socket = server.accept();
System.out.println("客户端连接上了" + socket.getRemoteSocketAddress());
ServerThread st = new ServerThread(socket, data);
st.start();
DataBase.list.add(st);
}
} catch (IOException e) {
while (DataBase.list.size() > 0) {
ServerThread st = DataBase.list.remove(0);
try {
st.socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
package net.control;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;
import java.util.Vector;
import net.common.Tool;
import net.dao.Data;
import net.dao.DataBase;
public class ServerThread extends Thread {
private String s;
public Socket socket;
public InputStream ins;
public OutputStream ous;
private Vector<Vector<String>> data;
static {
File file = new File("F:/文件传输数据");
File files[] = file.listFiles();
// 当文件夹存在时
for (int i = 0; i < files.length; i++) {
Data.downlist.add(files[i]);
}
}
public ServerThread(Socket socket, Vector<Vector<String>> data) {
this.socket = socket;
this.data = data;
}
public void run() {
try {
// 通过连接对象获取输入输出流
OutputStream ous = socket.getOutputStream();
InputStream ins = socket.getInputStream();
ObjectOutputStream oos = new ObjectOutputStream(ous);
ObjectInputStream ois = new ObjectInputStream(ins);
String command = ois.readUTF();
String userName = ois.readUTF();
String passWord = ois.readUTF();
System.out.println("获取命令为" + command);
System.out.println("用户名为" + userName);
System.out.println("密码为" + passWord);
// 当接受到消息时
if (command.equals("logon")) {
// 当登陆成功时
if (Tool.isUserExit(userName, passWord, DataBase.hmp)) {
System.out.println("用户名密码相同");
// 在服务器界面上添加用户名登录时间等信息
Vector<String> vec = new Vector();
vec.add(userName);
vec.add(socket.getRemoteSocketAddress().toString());
vec.add(new Date().toString());
data.add(vec);
String str = "logon ok";
oos.writeUTF(str);
oos.flush();
// 执行完登陆消息以后等待客户端指令
while (true) {
s = ois.readUTF();
// 获取到上传命令时
if (s.equals("send file")) {
try {
Data.filelist = (ArrayList<File>) ois.readObject();
for (int i = 0; i < Data.filelist.size(); i++) {
Data.downlist.add(Data.filelist.get(i));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
for (int i = 0; i < Data.filelist.size(); i++) {
// System.out.println("保存文件:" +
// Data.filelist.get(i).getName());
FileOutputStream fos = new FileOutputStream(
"F:/文件传输数据/" + Data.filelist.get(i).getName());
Tool.getFile(ois, fos);
}
}
// 点击更新按钮时
if (s.equals("update jtable")) {
oos.writeObject(Data.downlist);
oos.flush();
}
// 点击下载文件按钮时
if (s.equals("download file")) {
try {
Data.selectlist = (ArrayList<File>) ois.readObject();
for (int i = 0; i < Data.selectlist.size(); i++) {
File file = Data.selectlist.get(i);
Tool.sendFile(file, oos);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 同步服务器时
if (s.equals("local out")) {
for (int i = 0; i < Data.downlist.size(); i++) {
File files = Data.downlist.get(i);
FileInputStream fis = new FileInputStream(files);
Tool.sendFile(files, oos);
}
}
}
} else {
String str = "logon fail";
oos.writeUTF(str);
oos.flush();
}
}
// 注册时的操作
if (s.equals("regist")) {
if (Tool.isRegist(userName, passWord, DataBase.hmp)) {
oos.writeUTF("regist fail");
oos.flush();
} else {
DataBase.hmp.put(userName, passWord);
oos.writeUTF("regist ok");
oos.flush();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package net.dao;
import java.io.File;
import java.util.ArrayList;
public class Data {
// 队列用于选择要上传的文件
public static ArrayList<File> filelist = new ArrayList<File>();
// 队列保存下载的文件
public static ArrayList<File> downlist = new ArrayList<File>();
// 队列保存要下载的文件
public static ArrayList<File> selectlist = new ArrayList<File>();
}
package net.dao;
import java.util.ArrayList;
import java.util.HashMap;
import net.control.ServerThread;
public class DataBase {
public static HashMap<String, String> hmp = new HashMap();
public static ArrayList<ServerThread> list = new ArrayList();
// 静态块,预存账号密码,模拟数据库
static {
for (int i = 0; i < 10; i++) {
hmp.put("a" + i, "a" + i);
}
}
}
package net.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
public class Tool {
// 判断用户名是否已经存在并且密码是否为空
public static boolean isRegist(String userName, String passWord, HashMap<String, String> hmp) {
if (hmp.containsKey(userName)) {
return false;
} else {
if (passWord != null) {
return true;
}
return false;
}
}
// 判断哈希map中用户名密码是否符合
public static boolean isUserExit(String username, String passWord, HashMap<String, String> hmp) {
if (hmp.containsKey(username)) {
if (hmp.containsValue(passWord)) {
return true;
}
}
return false;
}
// 发送文件方法
public static void sendFile(File file, ObjectOutputStream oos) {
try {
FileInputStream fis = new FileInputStream(file);
// 统计写入次数作为协议传输
long length = file.length() / 2048;
int last = (int) file.length() % 2048;
// 写入协议
oos.writeLong(length);
oos.flush();
oos.writeInt(last);
oos.flush();
byte[] bytes = new byte[2048];
while (length > 0) {
fis.read(bytes);
oos.write(bytes);
oos.flush();
length--;
}
if (last > 0) {
bytes = new byte[last];
fis.read(bytes);
oos.write(bytes);
oos.flush();
}
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 接收文件方法
public static void getFile(ObjectInputStream ois, FileOutputStream fos) {
try {
// 传输过程
long length = ois.readLong();
int last = ois.readInt();
System.out.println(length + " " + last);
byte[] bytes = new byte[2048];
while (length > 0) {
ois.readFully(bytes);
fos.write(bytes);
fos.flush();
length--;
}
if (last > 0) {
bytes = new byte[last];
ois.readFully(bytes);
fos.write(bytes);
fos.flush();
}
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
最后实际运行的效果展示给大家看一下吧: