计算机网络课程设计,基于TCP连接的文件传输,适用于局域网,转载请注明出处,
作者:一只想翻身的咸鱼
客户端:
package Client;
//GuI绘画包
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Label;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.Panel;
import java.awt.TextArea;
import java.awt.TextField;
//监听事件包
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
//流输入输出包
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
//网络包
import java.net.ServerSocket;
import java.net.Socket;
//GuI绘画包
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
public class Client {
//窗口对象
private Frame frame;
private TextField textField1;
private TextField textField2;
private JButton button2;
private Label label1;
private Label label2;
private Label label3;
private Panel panel1;
private Panel panel2;
private Panel panel3;
private Panel panel4;
//提示窗口对象
private FileDialog fileDialog = new FileDialog(frame);
/**
* 要上传的文件对象
* */
File file;
/**
* 套接字输出流对象,用于向服务器输出字节流
* */
DataOutputStream out;
/**
* 文件输入流对象,用于从文件读取字节流
* */
FileInputStream fis;
/**
套接字变量,此套接字用于记录当前连接的服务器套接字。
*/
Socket Sockets;
/**
字节流输入变量,此变量用于接受来自套接字的流数据。
*/
InputStream is_s;
/**
字符输入缓冲区,从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。。
*/
BufferedReader br_s;
/**
字节流输出变量,此变量用于从套接字发送流数据。
*/
OutputStream os_s;
/**
字符输出缓冲区,向文本输出流打印对象的格式化表示形式。。
*/
PrintWriter pw_s;
//构造函数
/**
* 调用初始化函数,并新建一个连接监听线程
* */
public Client()
{
Init();
}
//初始化函数
private void Init()
{
/**
* 以下都是对窗口界面的设计,包括组件,面板,窗体等GUI设计
* */
frame = new Frame("Client");
frame.setLayout(null);
panel1 = new Panel();
panel2 = new Panel();
panel3 = new Panel();
panel4 = new Panel();
panel4.setBounds(0, 90, 600, 100);
panel3.setBounds(0, 180, 600, 200);
frame.setBounds(300,100,600,400);
frame.setVisible(true);
panel2.setVisible(true);
panel3.setVisible(true);
panel3.setLayout(null);
frame.add(panel3);
textField1 = new TextField(31);
textField1.setBounds(160, 70, 300, 30);
textField1.setFont(new Font("微软雅黑",Font.PLAIN,18));
textField2 = new TextField(31);
textField2.setBounds(160, 120, 300, 30);
textField2.setFont(new Font("微软雅黑",Font.PLAIN,18));
button2 = new JButton("上传");
button2.setBounds(250, 0, 80, 40);
label1 = new Label();
label2 = new Label();
label3 = new Label();
label1.setText("服务器IP");
label1.setFont(new Font("微软雅黑",Font.PLAIN,16));
label1.setBounds(70, 60, 80, 50);
label2.setText("选择文件");
label2.setBounds(70, 110, 80, 50);
label2.setFont(new Font("微软雅黑",Font.PLAIN,16));
label3.setText("上传进度:0%");
label3.setFont(new Font("微软雅黑",Font.PLAIN,22));
label3.setBounds(210, 60, 300, 100);
frame.add(label1);
frame.add(textField1);
frame.add(label2);
frame.add(textField2);
panel3.add(button2);
panel3.add(label3);
/**
* 调用事件函数,为各个组件添加监听事件。
* */
myEvent();
}
/**
* 事件监听函数。
* */
private void myEvent()
{
/**
* 为按钮添加活跃监听事件。
* */
button2.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
jButton2_actionPerformed();
}
});
/**
* 为窗体添加关闭监听事件
* */
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
/**
* 为文件选择输入框添加鼠标点击事件
* 当鼠标点击该输入框时,弹出文件选择窗口,并将选择的文件路径写入到文本输入框。
* */
textField2.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
textField2.setText("");
fileDialog.setVisible(true);
textField2.setText(fileDialog.getDirectory()+fileDialog.getFile());
}
});
}
/**
* 这是一个多线程,用来接收server发送过来的信息。
* 该线程在点击测试按钮时创建,在测试完成后结束。
* */
class ServerMessage implements Runnable
{
public ServerMessage()
{
}
public void run()
{
try{
/**
* 该变量用于存储服务器发来的字符串。
* */
String str = "";
while(true)
{
/**
* 接收服务器传来的消息
* 该循环在收到消息并处理结果后结束
* */
str = br_s.readLine();//读取一个文本行。
/**
* 对服务器传来的字符串进行判定,
* 1、若是success则表明服务器允许上传,开启上传线程。结束循环。
* 2、若是end则表明文件上传结束,关闭连接及字节流,结束循环。
* 3、若是其他字符串则表明返回的是错误信息,显示接收到的错误信息,结束循环。
*
* */
if(str!=null&&str.equals("success")) {
JOptionPane.showMessageDialog(frame,"开始上传");
new Thread(new UploadFile()).start();
break;
}else if(str!=null&&str.equals("end")) {
JOptionPane.showMessageDialog(frame,"上传成功");
/**
* 上传成功关闭连接资源。
* */
is_s.close();
os_s.close();
br_s.close();
pw_s.close();
Sockets.close();
break;
}else if(str!=null){
/**
* 提示错误信息
* */
JOptionPane.showMessageDialog(frame,str);
break;
}
}
}
catch (Exception e)
{
throw new RuntimeException("接收失败");
}
}
}
/**
* 文件上传线程,该线程在文件测试通过后执行,上传完成后结束。
* */
class UploadFile implements Runnable
{
/**
* 初始化文件上传环境
* */
//该变量存储文件大小,单位是字节
long filelength = file.length();
//该变量用于存储已经上传的文件大小,单位为字节
long uploadlength = 0;
//开启消息接收线程,用于接收服务器发送的传输成功的消息。
public UploadFile()
{
new Thread(new ServerMessage()).start();
}
/**
* 上传文件
* */
public void run()
{
//字节缓冲区,用于存储从文件读取流中读取的字节
byte[] bate = new byte[1024];
//用于记录读取的字节数。
int len = 0;
/**
* 发送文件,直到文件发送完成。
* 发送完成后结束线程。
* */
try {
//从文件读取字节,直到读取到文件结束标记-1
while((len = fis.read(bate,0,bate.length))!=-1) {
//将读取到的字节,写入套接字输出流。
out.write(bate, 0, len);
//刷新输出流,将写入的字节发送到服务器。
out.flush();
//记录已经发送的字节数
uploadlength+=len;
//动态更新上传进度
label3.setText("上传进度:"+((uploadlength*100)/filelength)+"%");
}
//文件传输完毕后,向服务器发送结束信号。
Sockets.shutdownOutput();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
*该函数用于连接服务器。建立套接字,用于向服务器发送数据。
*/
public void connection()
{
//从文本框读取服务器IP地址。
String ip = textField1.getText();
//判断读取的IP是否为空。
if(ip!=null&&ip.length()!=0) {
try
{
//创建套接字,并获取套接字的输入流与输出流对象。
Sockets = new Socket(ip,10001);
is_s = Sockets.getInputStream();//返回此套接字的输入流
os_s = Sockets.getOutputStream();//返回此套接字的输出流
br_s = new BufferedReader(new InputStreamReader(is_s));//将字节流转变为字符流
pw_s = new PrintWriter(os_s);//将字节流转变为字符流
}
catch (Exception e)
{
JOptionPane.showMessageDialog(frame,"err:"+e.toString());
}
}else {
//若IP为空,提示用户输入服务器IP地址。
JOptionPane.showMessageDialog(frame,"请输入IP");
}
}
/**
*该函数用于对按钮2事件的处理,当鼠标点击按钮2时,执行此函数。
*连接服务器,将文件信息发送给服务器,并启动线程接收服务器返回的文件测试信息。
*/
public void jButton2_actionPerformed()
{
connection();//连接服务器
String str = "";
str = textField2.getText();//获取文件绝对路径
if(str!=null&&str.length()!=0)
{
file = new File(str);//根据文件路径创建文件对象。
if(file.isFile())
{
try {
//创建文件读取流对象。
fis = new FileInputStream(file);
//创建套接字输出流对象。
out = new DataOutputStream(Sockets.getOutputStream());
// 向服务器发送文件名
out.writeUTF(file.getName());
//刷新流
out.flush();
//向服务器发送文件大小
out.writeLong(file.length());
out.flush();
//启动消息接收线程,接收服务器的允许信息。
new Thread(new ServerMessage()).start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else
{
JOptionPane.showMessageDialog(frame,"您选择的不是一个正确的文件");
}
}
}
/**
* 程序入口
* */
public static void main(String[] args)
{
new Client();
}
}
客户端运行结果:
服务器:
package Server;
import java.awt.*;//GuI绘画包
import java.awt.event.*;//监听事件包
import javax.swing.*;//GuI绘画包
import java.net.*;//网络包
import java.text.DecimalFormat;
import java.io.*;//流输入输出包
import java.math.RoundingMode;
import java.util.*;//集合框架包
public class Server
{
/**
* 各种GUI组件变量定义
* */
private Frame frame;
private TextField textField1;
private JButton button1;
private Label label1;
private Label label2;
private TextArea textArea1;
private Panel panel3;
/**
* 上传文件存放的文件夹
* 默认为d:/upload
* */
private File uploadDir= new File("d:/upload/");
/**
*用于格式化十进制数字格式,设置数字显示格式
* */
private static DecimalFormat df = null;
static {
// 设置数字格式,保留一位有效小数
df = new DecimalFormat("#0.0");
/**
* 设置舍入方式
* RoundingMode.HALF_UP:向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。
* */
df.setRoundingMode(RoundingMode.HALF_UP);
//设置某个数的小数部分中所允许的最小数字位数。
df.setMinimumFractionDigits(1);
//设置某个数的小数部分中所允许的最大数字位数。
df.setMaximumFractionDigits(1);
}
/**
* 构造函数,对象建立时执行
* */
public Server()
{
/**
* 掉用初始化函数,初始化程序界面。
* */
Init();
/**
* 创建初始上传文件接收文件夹
* */
uploadDir.mkdirs();
/**
* 启动线程接收来自客户端的连接请求。
* */
new Thread(new ServerStart()).start();
}
/**
* 初始化函数,初始化界面,添加监听机制。
* */
private void Init()
{
frame = new Frame("Server");
panel3 = new Panel();
panel3.setBounds(2, 130, 580, 400);
frame.setBounds(300,100,600,500);
frame.setVisible(true);
panel3.setVisible(true);
panel3.setLayout(null);
frame.setLayout(null);
frame.add(panel3);
label1 = new Label();
label2 = new Label();
label1.setText("文件存储目录");
label1.setBounds(50, 65, 100, 50);
label1.setFont(new Font("微软雅黑",Font.PLAIN,16));
label2.setText("上传记录");
label2.setFont(new Font("微软雅黑",Font.PLAIN,16));
label2.setBounds(20, 0, 100, 40);
textField1 = new TextField(31);
textField1.setBounds(160, 70, 270, 35);
textField1.setFont(new Font("微软雅黑",Font.PLAIN,18));
button1 = new JButton("确定");
button1.setBounds(450, 70, 70, 35);
textArea1 = new TextArea();
textArea1.setFont(new Font("微软雅黑",Font.PLAIN,14));
textArea1.setBounds(10, 40, 590, 320);
frame.add(label1);
frame.add(textField1);
frame.add(button1);
panel3.add(label2);
panel3.add(textArea1);
/**
* 为各个组件添加事件监听机制。
* */
myEvent();
}
/**
* 设置文件上传目录,获取文本框中的值,若为空则使用默认值。
* */
private void setUploadDir() {
/**
* 从文本输入框接收用户输入的接收文件夹路径。
* */
String str = textField1.getText();
/**
* 判断输入是否为空
* */
if(str!=null&&str.length()!=0) {
File dir = new File(str);
/**
* 测试是否有文件目录,若无则根据文件上传目录创建一个新目录
* */
if(!dir.exists()) {
dir.mkdirs();
}
uploadDir = dir;
JOptionPane.showMessageDialog(frame,"目录设置成功");
}else {
JOptionPane.showMessageDialog(frame,"请输入目录绝对路径");
}
}
/**
* 为各个组件添加监听机制
* */
private void myEvent()
{
/**
* 为“确认”按钮添加活跃监听
* */
button1.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
setUploadDir();
}
});
/**
* 为窗体添加关闭事件监听
* */
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
/**
* 该线程用于监听来自客户端的连接请求,并为每一个请求创建一个线程用于验证并接受上传文件
* 该线程在服务器启动时启动,服务器关闭时关闭。
* */
class ServerStart implements Runnable
{
/**
* ServerSocket对象用于接收客户端连接,并获取客户端套接字
* */
private ServerSocket SSocket1;
public ServerStart()
{
/**
* 设置监听端口为10001
* */
try
{
SSocket1 = new ServerSocket(10001);
}
catch (Exception e)
{}
}
public void run()
{
try
{
while(true)
{
/**
* 获取客户端套接字。
* */
Socket sk =new Socket();
sk = SSocket1.accept();
if(sk != null)
{
/**
* 启动一个线程用于接管该客户端文件传输的的所有事件
* 向该线程传递客户端套接字sk。
* */
new Thread(new Task(sk)).start();
}
else
{
sk.close();
}
}
}
catch (Exception e)
{
throw new RuntimeException("接受失败");
}
}
}
/**
* 该线程用于接管一个客户端上传文件的所有流程
* 在客户端连接时创建,在客户端注销时结束。
* */
class Task implements Runnable {
//该线程的客户端套接字
private Socket socket;
//套接字输入流
private DataInputStream dis;
//文件输出流
private FileOutputStream fos;
//接收传递过来的客户端套接字
public Task(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//获取套接字的输入流对象
dis = new DataInputStream(socket.getInputStream());
// 接收文件名
String fileName = dis.readUTF();
//接收文件总大小
long fileLength = dis.readLong();
//文件已经接收的大小
long uploadlength = 0;
//根据设置的文件保存目录及传递过来的文件名创建文件对象
File file = new File(uploadDir.getAbsolutePath() + File.separatorChar + fileName);
//判断文件是否存在
if(file.exists()) {
send(socket,"文件已存在\n");
}else {
/**
* 文件不存在,可以上传
* */
//向客户端发送上传信号
send(socket,"success\n");
//创建字符串容器对象用于存储,该上传文件的路径、上传客户端的IP、已经上传的大小及上传进度
StringBuffer sb = new StringBuffer();
//获取上传客户端IP
sb.append(socket.getInetAddress().toString().substring(1)+":");
//获取上传文件路径
sb.append(file.getPath()+"已上传:");
// 获取文件输出流,用于向文件中写入数据
fos = new FileOutputStream(file);
//字节缓冲区,作为输入流与输出流之间的桥梁
byte[] bytes = new byte[1024];
//用于记录每次读取的字节数
int length = 0;
//循环读取套接字传递的数据
while((length = dis.read(bytes, 0, bytes.length)) != -1) {
//向文件中写入服务器接收的数据
fos.write(bytes, 0, length);
/**
* 显示上传进度
* */
//记录已经上传的总字节数
uploadlength+=length;
//计算上传百分比
double plan = (uploadlength*100)/fileLength;
//获取显示区的文本内容
String text = textArea1.getText();
//判定显示区的内容是否为空,若为空则说明是服务器启动后的第一个上传文件
if(text.length()==0||text==null) {
//向文本显示区写入文件上传的信息
textArea1.setText(sb.toString()+"[Size:" + getFormatFileSize(uploadlength) + "]:"+plan+"%");
}else {
/**
* 文本显示区不为空,则说明该文件是服务器启动后的第二及其以后的文件
* */
//获取该文件信息在文本显示区的位置。
int index = text.indexOf(sb.toString());
//若位置为-1,则说明该文件信息第一次显示在文本显示区
if(index==-1) {
//直接写入文件信息
textArea1.setText(text+"\n"+sb.toString()+"[Size:" + getFormatFileSize(uploadlength) + "]:"+plan+"%");
}else {
/**
* 不是第一次显示,获取原本文件信息,对其进行更新
* */
//获取文件信息的末尾位置
int last =text.indexOf('%', index);
//截取旧的文件信息
String oldstr = text.substring(index, last+1);
//用新的文件信息,替换旧的文件信息
text = text.replace(oldstr, sb.toString()+"[Size:" + getFormatFileSize(uploadlength) + "]:"+plan+"%");
//将更新后的文本显示在文本显示区
textArea1.setText(text);
}
}
//刷新文件写出流
fos.flush();
}
//文件上传完毕,向客户端发送文件上传结束消息。
send(socket,"end\n");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
/**
* 关闭套接字及流缓冲区
* */
try {
if(fos != null)
fos.close();
if(dis != null)
dis.close();
socket.close();
} catch (Exception e) {}
}
}
}
/**
* 格式化文件大小,将文件大小从字节数转换为其他单位的值
*/
private String getFormatFileSize(long length) {
//获取有多少GB
double size = ((double) length) / (1 << 30);
//判断是否大于1GB
if(size >= 1) {
return df.format(size) + "GB";
}
//获取有多少MB
size = ((double) length) / (1 << 20);
//判断是否大于1MB
if(size >= 1) {
return df.format(size) + "MB";
}
//获取有多少KB
size = ((double) length) / (1 << 10);
//判断是否大于1KB
if(size >= 1) {
return df.format(size) + "KB";
}
return length + "B";
}
/**
* 该方法用于向客户端发送应答信息
* */
public void send(Socket sk,String Str)
{
//套接字字节输出流
OutputStream os_sd=null;
//字符字节转换流,用于将字符转化为字节流
PrintWriter pw_sd=null;
try
{
/**
* 获取流对象
* */
os_sd = sk.getOutputStream();
pw_sd = new PrintWriter(os_sd);
}
catch (Exception e)
{}
if(sk!= null)//判断是否连接
{
try
{
//写入消息字符串
pw_sd.write(Str);
//刷新流内容,将内容发送出去
pw_sd.flush();
}
catch (Exception e1)
{
JOptionPane.showMessageDialog(frame,"发送失败!");
}
}
}
/**
* 程序入口
* */
public static void main(String[] args)
{
new Server();
}
}
服务器端运行结果: