原理:客户端读取本地的文件,把文件上传到服务器,服务器在把上传的文件保存到服务器的硬盘上
1、客户端使用【本地的字节输入流】,读取要上传的文件
2、客户端使用【网络字节输出流】,把读取到的文件上传到服务器
3、服务器使用【网络字节输出流】,读取客户端上传的文件
4、服务器使用【本地的字节输出流】,把读取到的文件,保存到服务器的硬盘上
5、服务器使用【网络字节输出流】,给客户端回写一个“上传成功”
6、客户端使用【网络字节输入流】,读取服务器回写的数据
7、释放资源
注意:
1、客户端和服务器和本地硬盘进行读写,需要使用自己创建的字节流对象(本地流)
2、客户端和服务器之间进行读写,必须使用Socket中提供的字节流对象(网络流)
文件上传的原理
* 就是文件的复制
* 明确:数据源 、 数据目的地
具体代码如下:
/* 服务端 代码*/
public class TCPServer {
public static void main(String[] args) {
String filePath = TCPClient.class.getResource("").getPath();
Socket socket = null;
FileOutputStream fos = null;
try {
// 解决路径中含有空格的情况
filePath = filePath.replaceAll("%20","");
// 1、创建一个服务器 ServerSocket 对象,和系统要指定的端口号
ServerSocket server = new ServerSocket(9999);
// 2、使用 ServerSocket对象中的方法 accept,获取到请求的客户端 Socket 对象
socket = server.accept();
// 3、使用 Socket 对象中的方法 getInputStream,获取到网络字节输入流对象
InputStream inputStream = socket.getInputStream();
// 4、判断 文件是否存在,不存在则创建
File file = new File(filePath);
if (!file.exists()){
file.mkdirs();
}
// 自定义文件名
String fileName = System.currentTimeMillis()+ new Random().nextInt(999999)+".text";
// 5、创建一个本地字节输出流 FileOutputStream 对象,构造方法中绑定要输出的目的地
fos = new FileOutputStream(file+"/"+fileName);
int len = 0;
byte[] bytes = new byte[1024];
// 6、使用网络字节输入流 InputStream 对象中的方法 read ,读取客户端上传的文件
while((len = inputStream.read(bytes)) != -1) {
// 7、使用本地字节输出流 FileOutputStream 对象中的方法 write,把读取到的文件保存到服务器的硬盘中
fos.write(bytes,0,len);
}
/*
注意上面这个 while 循环的地方
* 由于上面 while 循环,判断 是否读取到文件结束标记。 但实际上这边的 read 方法是读取不到这个结束标记的,
因为客户端就没有将这个结束标记告诉服务端
* api 给出的解释:从此输入流中将最多 bytes.length 个字节的数据读入一个 byte 数组中。在某些输入可用之前,此方法将阻塞。
由此可见 这边的read 方法是被阻塞了
* 解决办法使 在客户端给出 结束标志
*/
// 8、使用 Socket 对象中的方法 getOutputStream,获取到网络字节输出流 OutputStream 对象
// 9、使用网络字节输出流OutputStream对象中的方法 write,给客户端回写"上传成功"
OutputStream outputStream = socket.getOutputStream();
outputStream.write("文件保存成功".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 10、释放资源(FileOutputStream,Socket,Server)
if(socket != null && fos != null) {
try {
socket.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/* 客户端 代码*/
public class TCPClient {
public static void main(String[] args) {
// String filePath = TCPClient.class.getResource("技术AB题库(更新至3.11号).docx").getPath();
String filePath = TCPClient.class.getResource("acitviti.txt").getPath();
FileInputStream fis = null;
Socket socket = null;
try {
// 解决路径中含有空格的情况
filePath = filePath.replaceAll("%20","");
// 解决路径包含中文的情况
filePath = java.net.URLDecoder.decode(filePath,"utf-8");
// 1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
fis = new FileInputStream(filePath);
// 2、创建一个客户端 Socket 对象,构造方法中绑定服务器的IP地址和端口
socket = new Socket(InetAddress.getByName("localhost").getHostAddress(),9999);
// 3、使用 Socket 中的方法 getOutputStream,获取网络字节输出流 OutputStream 对象
OutputStream outputStream = socket.getOutputStream();
// 4、使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
// 读取本地的流是一个重复的过程
int len = 0;
byte[] bytes = new byte[1024];// 提高效率
// System.out.println((len = fis.read(bytes)));
while((len = fis.read(bytes))!= -1){
// 5、使用网络字节输出流 OutputStream 对象中的方法 write ,把读取到的文件上传到服务器
outputStream.write(bytes,0,len);
}
// 给出读取结束标志
socket.shutdownOutput();
// 6、使用 Socket 中的方法 getInputStream,获取网络字节输入流 InputStream对象
InputStream inputStream = socket.getInputStream();
// 7、使用网络字节输入流 InputStream 对象中的方法read 读取服务器回写的数据
while((len = inputStream.read(bytes))!= -1){
System.out.println(new String(bytes,0,len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 8、释放资源(FileInputStream,Socket)
if(fis != null && socket !=null){
try {
fis.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上面服务端的代码,还优化成 多线程 ,这样效率会更高,就是将处理上传文件的逻辑代码放到 run 方法里面执行。