java实现两台电脑间TCP协议文件传输

  记录下以前所作的客户端向服务端发送文件的小项目,总结下学习到的一些方法与思路。java

注:本文参考自《黑马程序员》视频。程序员

  首先明确需求,在同一局域网下的机器人A想给喜欢了好久的机器人B发送情书,可是机器人B事先并不知道小A的心思,那么做为月老(红娘)该如何帮助他们呢?正则表达式

  而后创建模型并拆分需求。这里两台主机使用网线直连,在物理层上确保创建了链接,接下来即是利用相应的协议将信息从电脑A传给电脑B。在这一步上,能够将此过程抽象为网络+I/O(Input、Output)的过程。若是能在一台电脑上实现文件之间的传输,再加上相互的网络协议,羞涩的A不就能够将情书发送给B了吗?所以要先解决在一台电脑上传输信息的问题。为了在网络上传输,使用必要的协议是必要的,TCP/IP协议簇就是为了解决计算机间通讯而生,而这里主要用到UDP和TCP两种协议。当小A能够向小B发送情书后,又出现了众多的追求者,那么小B如何去处理这么多的并发任务呢?这时便要用到多线程的技术。编程

  所以接下来将分别介绍此过程当中所用到了I/O流(最基础)、网络编程(最重要)、多线程知识(较重要)和其中一些小技巧。数组

1、I/O流

  I/O流用来处理设备之间的数据传输,Java对数据的传输经过流的方式。服务器

  流按操做数据分为两种:字节流与字符流。若是数据是文本类型,那么须要使用字符流;若是是其余类型,那么使用字节流。简单来讲,字符流=字节流+编码表。网络

  流按流向分为:输入流(将硬盘中的数据读入内存),输出流(将内存中的数据写入硬盘)。多线程

  简单来讲,想要将某文件传到目的地,须要将此文件关联输入流,而后将输入流中的信息写入到输出流中。将目的关联输出流,就能够将信息传输到目的地了。并发

  Java提供了大量的流对象可供使用,其中有两大基类,字节流的两个顶层父InputStream与OutputStream;字符流的两个顶层父类Reader与Writer。这些体系的子类都以父类名做为后缀,而子类名的前缀就是该对象的功能。ide

流对象技巧

  下提供4个明确的要点,只要明确如下几点就能比较清晰的确认使用哪几个流对象。

  1, 明确源和目的(汇)

  • 源     :InputStream       Reader
  • 目的  :OutputStream    Writer

  2, 明确数据是不是纯文本数据

  • 源   :是纯文本  :Reader

           非纯文本  :InputStream

  • 目的:是纯文本  :Writer

           非纯文本  :OutputStream

  到这里就能够明确需求中具体要用哪一个体系。

  3, 明确具体的设备。

  • 源设备:

        硬盘:File

        键盘:System.in

        内存:数组

        网络:Socket流

  • 目的设备:

        硬盘:File

        控制台:System.out

        内存:数组

        网络:Socket流

  4,是否须要其余额外功能。

a) 是否须要高效(缓冲区)?

是,就加上buffer。

b) 是否须要转换?

  • 源:InputStreamReader 字节流->字符流
  • 目的:OutputStreamWriter 字符流->字节流

  在这里源为硬盘,目的也为硬盘,数据类型为情书,多是文字的情书,也多是小A唱的歌《情书》,所以使用字节流比较好。所以分析下来源是文件File+字节流InputStream->FileInputStream,目的是文件File+字节流OutputStream->FileOutputStream,  接下来即是数据如何从输入流到输出流的问题。

  两个流之间没有直接关系,须要使用缓冲区来做为中转,为了将读入流与缓冲区关联,首先自定义一个缓冲区数组byte[1024]。为了将读入流与缓冲区关联,使用fis.read(buf);为了将写出流与缓冲区关联,使用fos.write(buf,0,len)。为了将流中的文件写出到输出源中,要使用fos.flush或者fos.close。flush能够屡次刷新,而close只能使用一次。

  代码以下,其中读写中会遇到的异常为了程序的清晰阅读,直接抛出,建议实际使用时利用try,catch处理。

 1 public class IODemo {
 2     /**
 3      * 需求:将指定文件从D盘目录d:\1下移动到d:\2下
 4      * @param args
 5      * @throws IOException 
 6      */
 7     public static void main(String[] args) throws IOException {
 8         //1,明确源和目的,创建输入流和输出流
 9         //注意路径须要使用\\,将\转义
10         FileInputStream fis = new FileInputStream("d:\\1\\1.png");//源为d盘1目录下文件1.png
11         FileOutputStream fos = new FileOutputStream("d:\\2\\2.png");//目的为d盘2目录下文件2.png
12         //2,使用自定义缓冲区将输入流和输出流关联起来
13         byte[] buf = new byte[1024];//定义1024byte的缓冲区
14         int len = 0;//输入流读到缓冲区中的长度
15         //3,将数据从输入流读入缓冲区
16         //循环读入,当读到文件最后,会获得值-1
17         while((len=fis.read(buf))!=-1){
18             fos.write(buf,0,len);//将读到长度部分写入输出流
19         }
20         //4,关流,须要关闭底层资源
21         fis.close();
22         fos.close();
23     }
24 }

   这样小A就能够本身给本身发送情书啦,接下来怎么利用网络给小A和小B前线搭桥呢?

2、网络编程

  在I/O技术中,网络的源设备都是Socket流,所以网络能够简单理解为将I/O中的设备换成了Socket。

  首先要明确的是传输协议使用UDP仍是TCP。这里直接使用TCP传输。

TCP

  TCP是传输控制协议,具体的特色有如下几点:

  • 创建链接,造成传输数据的通道
  • 在链接中进行大数据量传输
  • 经过三次握手完成链接,是可靠协议
  • 必须创建链接,效率会稍低

Socket套接字

  无论使用UDP仍是TCP,都须要使用Socket套接字,Socket就是为网络服务提供的一种机制。通讯的两端都有Socket,网络通讯其实就是Socket间的通讯,数据在两个Socket间经过I/O传输

TCP传输

  TCP传输的两端分别为客户端与服务端,java中对应的对象为Socket与ServerSocket。须要分别创建客户端与服务端,在创建链接后经过Socket中的IO流进行数据的传输,而后关闭Socket。

  一样,客户端与服务器端是两个独立的应用程序。

  Socket类实现客户端套接字,ServerSocket类实现服务器套接字。

  客户端向服务端发送信息创建通道,通道创建后服务器端向客户端发送信息。

  客户端通常初始化时要指定对方的IP地址和端口,IP地址能够是IP对象,也能够是IP对象字符串表现形式。

  创建通道后,信息传输经过Socket流,为底层创建好的,又有输入和输出,想要获取输入或输出流对象,找Socket来获取。为字节流。getInputStream()和getOutputStream()方法来获取输入流和输出流。

  服务端获取到客户端Socket对象,经过其对象与Cilent进行通信。

  客户端的输出对应服务端的输入,服务端的输出对应客户端的输入。

  下面将以前的功能复杂化,变成将客户端硬盘上的文件发送至服务端。

客户端与服务端的演示

客户端

 1 //客户端发数据到服务端
 2         /*
 3          * TCP传输,客户端创建的过程
 4          * 1,建立TCP客户端Socket服务,使用的是Socket对象。
 5          *         建议该对象一建立就明确目的地。要链接的主机。
 6          * 2,若是链接创建成功,说明数据传输通道已创建。
 7          *         该通道就是Socket流,是底层创建好的。既然是流,说明这里既有输入,又有输出。
 8          * 3,使用输出流,将数据写出。
 9          * 4,关闭资源。
10          */
11         // 创建客户端Socket
12         Socket s = new Socket(InetAddress.getLocalHost(), 9003);
13         // 得到输出流
14         OutputStream out = s.getOutputStream();
15         // 得到输入流
16         FileInputStream fis = new FileInputStream("d:\\1\\1.png");
17         // 发送文件信息
18         byte[] buf = new byte[1024];
19         int len = 0;
20         while ((len = fis.read(buf)) != -1) {
21             // 写入到Socket输出流
22             out.write(buf, 0, len);
23         }
24         s.shutdownOutput();
25         // 关流
26         out.close();
27         fis.close();
28         s.close();

   注意:在创建客户端Socket服务的时候,须要指定服务端的IP地址和端口号,此处在实如今一台电脑上演示,所以服务端的地址是本机的IP地址。

服务端

 1         // 创建服务端
 2         ServerSocket ss = new ServerSocket(9003);// 须要指定端口,客户端与服务端相同,通常在1000-65535之间
 3         //服务端通常一直开启来接收客户端的信息。
 4         while (true) {
 5             // 获取客户端Socket
 6             Socket s = ss.accept();
 7             // 获取输入流与输出流
 8             InputStream in = s.getInputStream();// 输入流
 9             FileOutputStream fos = new FileOutputStream("d:\\3\\3.png");
10             // 建立缓冲区关联输入流与输出流
11             byte[] buf = new byte[1024];
12             int len = 0;
13             // 数据的写入
14             while ((len = in.read(buf)) != -1) {
15                 fos.write(buf, 0, len);
16             }
17             // 关流
18             fos.close();
19             s.close();
20         }

  由于此时尚未用到File类,所以与流关联的文件夹必须被提早建立,不然没办法成功写入。因此建议后续使用File对象来完成文件与流的关联。

3、传输任意类型后缀的文件

  由于只有一次通讯的过程,所以服务端事先不知道客户端所传输文件的类型,所以可让服务端与客户端进行简单的交互,这里只考虑成功传输的状况。

  具体实现过程为:1、客户端向服务端发送文件完整名称;2、服务端接收到完整名称,提取文件后缀名发送给客户端;3、客户端接收到服务端发送的后缀名进行校验,不一样则关闭客户端Socket流,结束客户端进程;4、若是正确,则发送文件信息。5、服务端根据接收到的文件名称和客户端ip地址创建相应的文件夹(若是不存在,则创立文件夹),将客户端Socket输入流信息写入文件,关闭客户端流。这样由于多了一次传输文件后缀名的过程,所以能够传输任意类型的文件,便于以后的拓展,如能够加入图形界面,选择任意想要传输的文件。

  这样基础功能已经大部分完成,可是此时一次只能链接一个客户端,这样若是机器人小B有若干追求者,也只能乖乖等小A将文件传输完毕,为了解决能够同时接收多个客户端的信息,须要用到多线程的技术。

4、多线程

  多线程的实现有两种方法,一种是继承Thread类,另外一种是实现Runnable接口而后做为线程任务传递给Thread对象,这里选择第二种实现Runnable接口。须要覆写此接口的run()方法,在以前的基础之上改动,将获取到的客户端Socket对象传入线程任务的run()方法,线程任务类须要持有Socket的引用,利用构造函数对此引用进行初始化。将读取输入流相当闭客户端流的操做封装至run()方法。须要注意的是,此过程当中代码会抛出异常,而实现接口类不能throw异常,只能进行try,catch处理(接口中无此异常声明,所以不能抛出)。在服务器类中,新建Thread对象,将线程任务类对象传入,调用Thread类的start()方法开启线程。

5、总结

  以上便基本实现了此任务的核心功能,即经过TCP协议,实现了多台客户端与主机间任意类型文件的传输,其中最核心的知识点在于I/O流,即须要弄清输入流与输出流,利用缓冲区进行两者的关联;在此基础上,加入了网络技术编程,将输入输出流更改成Socket套接字;为了增长拓展性,引入文件对象,实现客户端与服务端的交互;为了实现多台电脑与主机的文件传输,引入了多线程。程序中为了尽可能简化与抽象最核心的内容,一些代码与逻辑不免有纰漏,但愿你们多多指正与交流。固然此过程彻底能够由UDP协议完成,在某些场景下UDP也更有优点,此处再也不赘述。

完整代码

客户端

 1 import java.io.File;
 2 import java.io.FileInputStream;
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 import java.io.OutputStream;
 6 import java.net.InetAddress;
 7 import java.net.Socket;
 8 import java.net.UnknownHostException;
 9 
10 public class Client {
11     public static void main(String[] args) throws UnknownHostException, IOException {
12         /*
13          * 客户端先向服务端发送一个文件名,服务端接收到后给客户端一个反馈,而后客户端开始发送文件
14          */
15         //创建客户端Socket
16         Socket s = new Socket(InetAddress.getLocalHost(), 9001);//修改成服务器IP地址
17         //得到输出流
18         OutputStream out = s.getOutputStream();
19         //关联发送文件
20         File file = new File("D:\\1.png");
21         String name = file.getName();//获取文件完整名称
22         String[] fileName = name.split("\\.");//将文件名按照.来分割,由于.是正则表达式中的特殊字符,所以须要转义
23         String fileLast = fileName[fileName.length-1];//后缀名
24         //写入信息到输出流
25         out.write(name.getBytes());
26         //读取服务端的反馈信息
27         InputStream in = s.getInputStream();
28         byte[] names = new byte[50];
29         int len = in.read(names);
30         String nameIn = new String(names, 0, len);
31         if(!fileLast.equals(nameIn)){
32             //结束输出,并结束当前线程
33             s.close();
34             System.exit(1);
35         }
36         //若是正确,则发送文件信息
37         //读取文件信息
38         FileInputStream fr = new FileInputStream(file);
39         //发送文件信息
40         byte[] buf = new byte[1024];
41         while((len=fr.read(buf))!=-1){
42             //写入到Socket输出流
43             out.write(buf,0,len);
44         }
45         //关流
46         out.close();
47         fr.close();
48         s.close();
49     }
50 }

服务端

任务类

 1 import java.io.File;
 2 import java.io.FileOutputStream;
 3 import java.io.InputStream;
 4 import java.io.OutputStream;
 5 import java.net.Socket;
 6 
 7 public class Task implements Runnable {
 8     private Socket s;
 9     public Task(Socket s){
10         this.s = s;
11     }
12     @Override
13     public void run() {
14         String ip = s.getInetAddress().getHostAddress();
15         try{
16             //获取客户端输入流
17             InputStream in = s.getInputStream();
18             //读取信息
19             byte[] names = new byte[100];
20             int len = in.read(names);
21             String fileName = new String(names, 0, len);
22             String[] fileNames = fileName.split("\\.");
23             String fileLast = fileNames[fileNames.length-1];
24             //而后将后缀名发给客户端
25             OutputStream out = s.getOutputStream();
26             out.write(fileLast.getBytes());
27             //新建文件
28             File dir = new File("d:\\server\\"+ip);
29             if(!dir.exists())
30                 dir.mkdirs();
31             File file = new File(dir,fileNames[0]+"."+fileLast);
32             FileOutputStream fos = new FileOutputStream(file);
33             //将Socket输入流中的信息读入到文件
34             byte[] bufIn = new byte[1024];
35             while((len = in.read(bufIn))!=-1){
36                 //写入文件
37                 fos.write(bufIn, 0, len);
38             }
39             fos.close();
40             s.close();
41         }catch(Exception e){
42             e.printStackTrace();
43         }
44     }
45 }

服务器类

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    public static void main(String[] args) throws IOException {
        /*
         * 服务端先接收客户端传过来的信息,而后向客户端发送接收成功,新建文件,接收客户端信息
         */
        //创建服务端
        ServerSocket ss = new ServerSocket(9001);//客户端端口须要与服务端一致
        while(true){
            //获取客户端Socket
            Socket s = ss.accept();
            new Thread(new Task(s)).start();
        }
    }
}

  以上内容就到这里,若有错误和不清晰的地方,请你们指正!

相关文章
相关标签/搜索