1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > java实现两台电脑间TCP协议文件传输

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

时间:2022-10-20 19:07:56

相关推荐

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

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

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

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

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

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

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

一、I/O流

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

流按操作数据分为两种:字节流与字符流。如果数据是文本类型,那么需要使用字符流;如果是其他类型,那么使用字节流。简单来说,字符流=字节流+编码表。

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

简单来说,想要将某文件传到目的地,需要将此文件关联输入流,然后将输入流中的信息写入到输出流中。将目的关联输出流,就可以将信息传输到目的地了。

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

流对象技巧

下提供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 args5* @throws IOException 6*/7public static void main(String[] args) throws IOException {8 //1,明确源和目的,建立输入流和输出流9 //注意路径需要使用\\,将\转义10 FileInputStream fis = new FileInputStream("d:\\1\\1.png");//源为d盘1目录下文件1.png11 FileOutputStream fos = new FileOutputStream("d:\\2\\2.png");//目的为d盘2目录下文件2.png12 //2,使用自定义缓冲区将输入流和输出流关联起来13 byte[] buf = new byte[1024];//定义1024byte的缓冲区14 int len = 0;//输入流读到缓冲区中的长度15 //3,将数据从输入流读入缓冲区16 //循环读入,当读到文件最后,会得到值-117 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前线搭桥呢?

二、网络编程

在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 // 建立客户端Socket12 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 // 获取客户端Socket6 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对象来完成文件与流的关联。

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

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

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

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

四、多线程

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

五、总结

以上便基本实现了此任务的核心功能,即通过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 .InetAddress;7 import .Socket;8 import .UnknownHostException;9 10 public class Client {11public static void main(String[] args) throws UnknownHostException, IOException {12 /*13* 客户端先向服务端发送一个文件名,服务端接收到后给客户端一个反馈,然后客户端开始发送文件14*/15 //建立客户端Socket16 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 }

服务端

任务类

按 Ctrl+C 复制代码

按 Ctrl+C 复制代码

服务器类

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

以上内容就到这里,如有错误和不清晰的地方,请大家指正!

作者:未*名

出处:/hughjava/

个性签名:少年回头望,笑我还不快跟上

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。