使用Socket完成文件上传案例
一、文件上传原理图解
二、思路
- 创建TCPClient的class类
客户端:读取本地文件,上传到服务器,读取服务器回写的数据
数据源:c:\\1.jpg
目的地:服务器
实现步骤:
1.创建一个本地字节输入流FileInputStream,构造方法中要绑定读取的数据
2.创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
3.使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
4.使用本地字节输出流FilePutStream对象中的方法read,读取本地文件
5.使用字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
6.使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象
7.使用网络字节输入流InputStream对象中的方法read读取服务器回写的数据
8.释放资源(FileInputStream,Socket)
- 创建TCPServer的class类
服务器端:读取客户端上传的文件,保存到服务器的硬盘,给客户端回写“上传成功”
数据源:客户端上传的文件
目的地:服务器的硬盘 d:\\upload\\1.jpg
实现步骤:
1.创建一个服务器ServerSocket对象,获取系统要指定的端口号
2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
4.判断d:\upload文件夹是否存在,不存在则创建
5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
6.使用字节输入流InputStream对象中的方法read,读取客户端上传的文件
7.使用Socket对象中的方法getOutputStream,获取网络字节输出流OutputStream对象
8.使用网络字节输出流OutputStream对象中的方法write,给客户端回写“上传成功”
9.释放资源(FileOutputStream,Socket,ServerSocket)
三、优化分析
1.客户端、服务器进入死循环无法终止问题:
由于代码中使用的while循环,fis.read(bytes)读取本地文件,结束标记读取到-1结束,但是while循环不能读取到-1,那么也不会把结束标记写给服务器。is.read读取不到服务器回写的数据,进入到阻塞状态。
Solution:使用shut downOutput():对于TCP套接字。
任何以前写入的数据都将被发送,并且后跟TCP的正常连接终止序列。如果在套接字上调用shut downOutput()后写入套接字输出流,则该将抛出IOException。
socket.shutdownOutput //放在客户端的while循环之后
2.上传的文件名称写死问题:
如果保存文件的名称写死,那么服务器硬盘只能保留一个文件。
Solution:使用系统时间优化+随机数保证文件命名唯一。
String fileName = "MyPhoto"+System.currentTimeMillis()+/*new Random().nextInt(99999)+*/".jpg";
3.循环接受问题:
服务器也不能只保存一个文件就关闭名之后的用户无法上传,这是不符合实际的
Solution:使用循环改进,可以不断的接受不用用户的文件,让服务器一直处于监听状态。服务器就不用关闭了。
while (true){
Socket socket = server.accept();
...
}
4.效率问题:
服务器端在接受大文件时,可能耗费几秒钟的时间,此时不能接受其他用户上传。
Solution:使用多线程技术优化
new Thread(new Runnable() {
//注:重写run方法完成文件的上传 ,但并不能重写throws IOException的异常,可以手动try...catch解决
@Override
public void run() {
...
}).start();
四、改进之后的代码
1.TCPClient
package UpdateFileUpload;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws IOException {
//1.创建一个本地字节输入流FileInputStream,构造方法中要绑定读取的数据
FileInputStream fis = new FileInputStream("d:\\1.jpg");
//2.创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1",8888);
//3.使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
//4.使用本地字节输出流FilePutStream对象中的方法read,读取本地文件
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1) {
//5.使用字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
os.write(bytes,0,len);
}
/*
解决由while发生的阻塞问题:上传完文件,给服务器一个结束标记
void shutdownOutput 禁止此套接字的输出流
对于TCP套接字,任何以前写入的数据都将被发送,并且以后跟TCP的正常连接终止序列。
*/
socket.shutdownOutput();
//6.使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//7.使用网络字节输入流InputStream对象中的方法read读取服务器回写的数据
while ((len = is.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
//8.释放资源(FileInputStream,Socket)
fis.close();
socket.close();
}
}
2.ServerClient
package UpdateFileUpload;
/*
文件上传案例的服务器端:读取客户端上传的文件,保存到服务器的硬盘,给客户端回写上传成功
明确:
数据源:客户端上传的文件
目的地:服务器的硬盘:d:\\upload\\1.jpg
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.创建一个服务器ServerSocket对象,获取系统要指定的端口号
ServerSocket server = new ServerSocket(8888);
//2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
/*
让服务器一直处于监听状态(死循环accept方法)
有一个客户端上传文件,就保存一个文件
*/
while (true){
/*
使用多线程技术提高程序的效率(多人同时上传)
有一个客户端上传文件那就开启一个线程,完成文件的上传
*/
new Thread(new Runnable() {
//重写run方法完成文件的上传 ,但自雷重写异常也不能声明抛出IOException异常,可以用try...catch
@Override
public void run() {
try{
Socket socket = server.accept();
//3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//4.判断d:\\upload文件夹是否存在,不存在则创建
File file = new File("d:\\upload");
if (!file.exists()){
file.mkdirs();
}
/*
自定义一个文件命名规则:防止同名的文件被覆盖
规则:域名+毫秒值+随机数
*/
String fileName = "itcast"+System.currentTimeMillis()+/*new Random().nextInt(99999)+*/".jpg";
//5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
FileOutputStream fos = new FileOutputStream(file+"\\"+fileName);
//6.使用字节输入流InputStream对象中的方法read,读取客户端上传的文件
int len = 0;
byte[] bytes = new byte[1024];
while ((len = is.read()) != -1){
fos.write(bytes,0,len);
}
//8.使用Socket对象中的方法getOutputStream,获取网络字节输出流OutputStream对象
//9.使用网络字节输出流OutputStream对象中的方法write,给接护短回写“上传成功”
socket.getOutputStream().write("上传成功".getBytes());
//10.释放资源(FileOutputStream,Socket,ServerSocket)
fos.close(); //可以放在finally
socket.close();
}catch (IOException e){
System.out.println(e);
}
}
}).start();
}
//服务器就不用关闭了
//server.close();
}
}