TCP通信 - 服务器开启多线程与read()导致服务器阻塞问题
TCP通信的文件上传案例
-
本地流:客户端和服务器和本地硬盘进行读写,需要使用自己创建的字节流
-
网络流:客户端和服务器之间读写,必须使用Socket中提供的字节流对象
客户端工作:读取本地文件,上传到服务器,读取服务器回写的数据
- 明确数据源
- 目的地:服务器
- 客户端代码:
package cn.learn.web;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class client {
public static void main(String[] args) throws IOException {
//创建一个本地的字节输入流,构造方法绑定数据源
FileInputStream fileIn = new FileInputStream("b.txt");
//*****发送数据******
//创建客户端对象Socket,构造方法绑定服务器IP地址和端口号
Socket socket = new Socket("127.0.0.1", 8020);
//使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
OutputStream socketOut = socket.getOutputStream();
//读入内存
int len = 0;
byte[] bytes = new byte[1024];
//使用流中的write方法给服务器发送数据
while ((len = fileIn.read(bytes)) != -1) {
socketOut.write(bytes, 0, len);
}
//已上传完文件,但read()导致服务器阻塞,给服务器写一个结束标记
socket.shutdownOutput();
//使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
InputStream clientIn = socket.getInputStream();
//使用InputStream对象中的read()方法,读取服务器回写的数据
while ((len = clientIn.read(bytes)) != -1) {
//打印看看
System.out.println(new String(bytes, 0, len));
}
//释放资源,只关闭Socket的IO流就行
fileIn.close();
socket.close();
}
}
服务器端:读取客户端上传的文件,保存到服务器的硬盘,给客户端回写“上传成功”
- 明确:
- 数据源:客户端上传的文件
- 目的地:服务器的硬盘 d:\\upload.txt
- 服务器代码:
package cn.learn.web;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
//设置通信端口号,不然系统随机分配
ServerSocket server = new ServerSocket(8020);
//使用serverSocket对象中的方法accept,获取到请求的客户端对象Socket(含地址和端口号)
Socket socket1 = server.accept();
//使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
InputStream serveIn = socket1.getInputStream();
//判断文件夹是否存在,若不存在新建
File file = new File("d:\\upload");
if(!file.exists()){
file.mkdir();
}
//创建服务器的本地字节输出流,构造方法绑定输出位置,注意****\\******
FileOutputStream localOut = new FileOutputStream(file+"\\b.txt");
//获取读取的数据有效长度,循环读取和写入服务器硬盘
byte[] bytes = new byte[1024];
int len = 0;
//使用serveIn的方法read,读取客户端发送的数据
while ((len = serveIn.read(bytes)) != -1) {
localOut.write(bytes,0,len);
//打印到控制台看看
System.out.println(new String(bytes, 0, len));
}
//使用Socket对象中的方法getOutputStream()获取网络字节输入流OutputStream对象
OutputStream serverOut = socket1.getOutputStream();
//6.使用serverOut中的write方法回写给客户端
serverOut.write("我收到了,上传成功".getBytes());
//7.释放socket1与server的流
server.close();
socket1.close();
}
}
注:服务器一直开启,等待客户端上传
-
TCP通信完成后服务器一直未停止原因
- read()方法,若没有输入可用,方法阻塞
- 具体原因是read()读取时用的循环,读不到结束标记,服务器一直等待进入阻塞状态
//已上传完文件,但read()导致阻塞,给服务器写一个结束标记
socket.shutdownOutput();
上述代码优化
- 优化服务器存储文件名称
/*
自定义服务器本地存储文件名称,防止冲突覆盖
规则:域名+毫秒值+随机数
*/
String filename = "learn" + System.currentTimeMillis()+ (new Random().nextInt(10)+1)+".txt";
//创建服务器的本地字节输出流,构造方法绑定输出位置,注意****\\******
FileOutputStream localOut = new FileOutputStream(file+"\\"+filename);
-
使服务器一直处于监听状态,循环accept接受的套接字操作(而不是阻塞),最后不用关闭服务器了
-
在循环中使用多线程,提高效率,有一个客户端上传文件,就开启一个线程。将代码块复制到run()方法中,注意使用try catch抛出异常
服务器代码优化,开启多线程
package cn.learn.web;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
public class Server {
public static void main(String[] args) throws IOException {
//设置通信端口号,不然系统随机分配
ServerSocket server = new ServerSocket(8020);
/*
循环,使得服务器一直处于监听状态,有客户端上传文件,就存入
*/
while (true) {
//使用serverSocket对象中的方法accept,获取到请求的客户端对象Socket(含地址和端口号)
Socket socket1 = server.accept();
/******
客户端每上传一个文件,就开启一个多线程
******/
new Thread(new Runnable() {
@Override
public void run() {
try {
//使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
InputStream serveIn = socket1.getInputStream();
//判断文件夹是否存在,若不存在新建
File file = new File("d:\\upload");
if (!file.exists()) {
file.mkdir();
}
/*
自定义服务器本地存储文件名称,防止冲突覆盖
规则:域名+毫秒值+随机数
*/
String filename = "learn" + System.currentTimeMillis() + (new Random().nextInt(10) + 1) + ".txt";
//创建服务器的本地字节输出流,构造方法绑定输出位置,注意****\\******
FileOutputStream localOut = new FileOutputStream(file + "\\" + filename);
//获取读取的数据有效长度,循环读取和写入服务器硬盘
byte[] bytes = new byte[1024];
int len = 0;
//使用serveIn的方法read,读取客户端发送的数据
while ((len = serveIn.read(bytes)) != -1) {
localOut.write(bytes, 0, len);
}
//使用Socket对象中的方法getOutputStream()获取网络字节输入流OutputStream对象
OutputStream serverOut = socket1.getOutputStream();
//6.使用serverOut中的write方法回写给客户端
serverOut.write("我收到了,上传成功".getBytes());
//7.释放socket1与server的流
socket1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}