计算机网络课程设计,基于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(); } }
服务器端运行结果: