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处理。
public class IODemo {
/**
* 需求:将指定文件从D盘目录d:\1下移动到d:\2下
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//1,明确源和目的,建立输入流和输出流
//注意路径需要使用\\,将\转义
FileInputStream fis = new FileInputStream("d:\\1\\1.png");//源为d盘1目录下文件1.png
FileOutputStream fos = new FileOutputStream("d:\\2\\2.png");//目的为d盘2目录下文件2.png
//2,使用自定义缓冲区将输入流和输出流关联起来
byte[] buf = new byte[1024];//定义1024byte的缓冲区
int len = 0;//输入流读到缓冲区中的长度
//3,将数据从输入流读入缓冲区
//循环读入,当读到文件最后,会得到值-1
while((len=fis.read(buf))!=-1){
fos.write(buf,0,len);//将读到长度部分写入输出流
}
//4,关流,需要关闭底层资源
fis.close();
fos.close();
}
}
这样小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进行通讯。
客户端的输出对应服务端的输入,服务端的输出对应客户端的输入。
下面将之前的功能复杂化,变成将客户端硬盘上的文件发送至服务端。
客户端与服务端的演示
客户端
//客户端发数据到服务端
/*
* TCP传输,客户端建立的过程
* 1,创建TCP客户端Socket服务,使用的是Socket对象。
* 建议该对象一创建就明确目的地。要连接的主机。
* 2,如果连接建立成功,说明数据传输通道已建立。
* 该通道就是Socket流,是底层建立好的。既然是流,说明这里既有输入,又有输出。
* 3,使用输出流,将数据写出。
* 4,关闭资源。
*/
// 建立客户端Socket
Socket s = new Socket(InetAddress.getLocalHost(), 9003);
// 获得输出流
OutputStream out = s.getOutputStream();
// 获得输入流
FileInputStream fis = new FileInputStream("d:\\1\\1.png");
// 发送文件信息
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
// 写入到Socket输出流
out.write(buf, 0, len);
}
s.shutdownOutput();
// 关流
out.close();
fis.close();
s.close();
注意:在建立客户端Socket服务的时候,需要指定服务端的IP地址和端口号,此处在实现在一台电脑上演示,因此服务端的地址是本机的IP地址。
服务端
// 建立服务端
ServerSocket ss = new ServerSocket(9003);// 需要指定端口,客户端与服务端相同,一般在1000-65535之间
3 //服务端一般一直开启来接收客户端的信息。
while (true) {
// 获取客户端Socket
Socket s = ss.accept();
// 获取输入流与输出流
InputStream in = s.getInputStream();// 输入流
FileOutputStream fos = new FileOutputStream("d:\\3\\3.png");
// 创建缓冲区关联输入流与输出流
byte[] buf = new byte[1024];
int len = 0;
// 数据的写入
while ((len = in.read(buf)) != -1) {
fos.write(buf, 0, len);
}
// 关流
fos.close();
s.close();
}
因为此时还没有用到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也更有优势,此处不再赘述。
完整代码
客户端
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException; public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
/*
* 客户端先向服务端发送一个文件名,服务端接收到后给客户端一个反馈,然后客户端开始发送文件
*/
//建立客户端Socket
Socket s = new Socket(InetAddress.getLocalHost(), 9001);//修改为服务器IP地址
//获得输出流
OutputStream out = s.getOutputStream();
//关联发送文件
File file = new File("D:\\1.png");
String name = file.getName();//获取文件完整名称
String[] fileName = name.split("\\.");//将文件名按照.来分割,因为.是正则表达式中的特殊字符,因此需要转义
String fileLast = fileName[fileName.length-1];//后缀名
//写入信息到输出流
out.write(name.getBytes());
//读取服务端的反馈信息
InputStream in = s.getInputStream();
byte[] names = new byte[50];
int len = in.read(names);
String nameIn = new String(names, 0, len);
if(!fileLast.equals(nameIn)){
//结束输出,并结束当前线程
s.close();
System.exit(1);
}
//如果正确,则发送文件信息
//读取文件信息
FileInputStream fr = new FileInputStream(file);
//发送文件信息
byte[] buf = new byte[1024];
while((len=fr.read(buf))!=-1){
//写入到Socket输出流
out.write(buf,0,len);
}
//关流
out.close();
fr.close();
s.close();
}
}
服务端
任务类
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket; public class Task implements Runnable {
private Socket s;
public Task(Socket s){
this.s = s;
}
@Override
public void run() {
String ip = s.getInetAddress().getHostAddress();
try{
//获取客户端输入流
InputStream in = s.getInputStream();
//读取信息
byte[] names = new byte[100];
int len = in.read(names);
String fileName = new String(names, 0, len);
String[] fileNames = fileName.split("\\.");
String fileLast = fileNames[fileNames.length-1];
//然后将后缀名发给客户端
OutputStream out = s.getOutputStream();
out.write(fileLast.getBytes());
//新建文件
File dir = new File("d:\\server\\"+ip);
if(!dir.exists())
dir.mkdirs();
File file = new File(dir,fileNames[0]+"."+fileLast);
FileOutputStream fos = new FileOutputStream(file);
//将Socket输入流中的信息读入到文件
byte[] bufIn = new byte[1024];
while((len = in.read(bufIn))!=-1){
//写入文件
fos.write(bufIn, 0, len);
}
fos.close();
s.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
服务器类
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();
}
}
}
以上内容就到这里,如有错误和不清晰的地方,请大家指正!
java实现两台电脑间TCP协议文件传输的更多相关文章
- 通过Git在本地局域网中的两台电脑间同步代码
0.前言 一般情况下同步代码可以通过在GitHub/GitLab等网站新建远程仓库,所有机器都向仓库推送或者从仓库下拉更新. 上述过程步骤也不算复杂,不过有时候我们考虑到仓库的安全性等因素,只想在局域 ...
- 两台Linux服务器之间的文件传输
最近工作中有这样一个需求,需要将A服务器上的文件传到B服务器. 本来想用Java开发,但一想Java开发周期长,应对这样一个小需求没必要用Java,最后选择了Shell脚本,相关代码如下: #!/bi ...
- 使用TCP在同一台电脑上可以建立连接,在两台电脑上却连接失败的原因分析
最近在用unity做联机游戏,在网络方面费了不少劲,总是在代码没问题的时候出一些莫名奇妙的BUG,不过后来都决定了.如果感觉代码没问题,八成就是防火墙的问题. 用unity发布后的游戏,如果涉及网络, ...
- 《java入门第一季》之tcp协议下的网络编程
tcp协议相对于udp更加安全. 首先看一下需求:服务器端开启,多个客户端同时向服务器发送数据,看哪个客户端先到达. 说明:这里我开启三个电脑实验,一台电脑写服务器端的程序,两台电脑开客户端的程序.服 ...
- 【Linux学习笔记】用nc实现两台主机间的文件传输(不需要输密码)
通常,可以用scp完成两台主机间的文件传输任务,但在主机间未建立信任关系的情况下,scp每次都需要输入密码,用起来感觉不是很方便,之前这篇笔记介绍过不用输入密码执行脚本或传输文件的方法,但对于一些临时 ...
- Python_架构、同一台电脑上两个py文件通信、两台电脑如何通信、几十台电脑如何通信、更多电脑之间的通信、库、端口号
1.架构 C/S架构(鼻祖) C:client 客户端 S:server 服务器 早期使用的一种架构,目前的各种app使用的就是这种架构,它的表现形式就是拥有专门的app. B/S架构(隶属于C/ ...
- 《java入门第一季》之tcp协议下的网络编程c/s实现通信交互
需求:客户端向服务器发送数据,服务器端收到数据后向客户端返回数据: 还是使用两台电脑,一台客户端,一台服务器. 客户端代码: import java.io.IOException; import ja ...
- 《java入门第一季》之tcp协议下的编程实现键盘录入数据不断地往服务器端发送数据案例
这里要封装通道流,封装键盘录入数据流. 同样地,还是两台电脑.一个客户端,一个服务器. 客户端代码: import java.io.BufferedReader; import java.io.Buf ...
- openStack 重新resize时会进行重新调度,可能在本机Resize 扩展资源,也可能存在的情况时 ,新扩展的资源在当前节点不足分配,整个虚拟机将进行迁移调度,进行异机迁移时需要迁移 的两台主机间能使用nova系统用户经passless登录
openStack 重新resize时会进行重新调度,可能在本机Resize 扩展资源,也可能存在的情况时 ,新扩展的资源在当前节点不足分配,整个虚拟机将进行迁移调度,进行异机迁移时需要迁移 的两台主 ...
随机推荐
- JDK各版本新特性总结
序言 北风潜入悄无声,未品浓秋已立冬. JDK1.1--1996 JDK1.2--1998 JDK1.3--2000 JDK1.4--2002 JDK5.0--2004 JDK6.0--2006 JD ...
- IOC轻量级框架之Unity
任何事物的出现,总有它独特的原因,Unity也是如此,在Unity产生之前,我们是这么做的 我们需要在一个类A中引用另一个类B的时候,总是将类B的实例放置到类A的构造函数中,以便在初始化类A的时候,得 ...
- iframe元素的学习(笔记)
什么是iframe:iframe元素即内联框架,iframe是内联的并且承前启后,对于外围的页面,iframe是一个普通的元素,对于iframe里面的内容,又是一个五脏俱全的页面.重下面的写法可以看出 ...
- 《PHP和MySQL Web开发》读书笔记(上篇)
最近过得太浮躁了,实在自己都看不下去了,看了PHP圣经之后,觉得非常有必要要总结一下. Chapter1.快速入门 ·PHP标记:总共有三种风格,常用的还是XML风格为主 <?php echo ...
- [转]天才计算机程序员 -- fabrice bellard
这位老兄就是用javascript写linux的那位,他的主页是:http://bellard.org/ 上面有他的几个作品,包括qemu,ffmpeg,tcc等. 这个世界从来不缺天才,只缺乏利用天 ...
- Session详解、ASP.NET核心知识(8)
介绍一下Session 1.作用 Cookie是存在客户端,Session是存在服务器端,目的是一样的:保存和当前客户端相关的数据(当前网站的任何一个页面都能取到Session). 在本篇博文的姊妹篇 ...
- HDU 1019 Least Common Multiple GCD
解题报告:求多个数的最小公倍数,其实还是一样,只需要一个一个求就行了,先将答案初始化为1,然后让这个数依次跟其他的每个数进行求最小公倍数,最后求出来的就是所有的数的最小公倍数.也就是多次GCD. #i ...
- MySQL防范SQL注入风险
MySQL防范SQL注入风险 0.导读 在MySQL里,如何识别并且避免发生SQL注入风险 1.关于SQL注入 互联网很危险,信息及数据安全很重要,SQL注入是最常见的入侵手段之一,其技术门槛低.成本 ...
- JSON数据生成树——(四)
1.页面中准备树的div <div class="user_left_tree_info"> <div class="user_left_tree_in ...
- 【CTF WEB】文件包含
文件包含 题目要求: 请找到题目中FLAG 漏洞源码 <meta charset='utf-8'> <center><h1>文件阅读器</h1>< ...