目录
一、网络编程
1.网络编程概述
-
在网络通信协议下,不同计算机上运行的程序,可以进行数据传输
2.电脑之间数据传输需要的条件(网络编程三要素)
-
确认接收端在网络中的位置-------------------------------------------------IP地址(设备在网络中的地址,是唯一的标识)
-
确定接收端中某接收输数据软件的入口----------------------------------端口号(应用程序在设备中唯一的标识)
-
确定网络中传输数据的规则--------------------------------------------------协议(数据在网络中传输的规则,常见的协议有UDP协议和TCP协议)
(1)IP
全称”互联网协议地址,也称IP地址。是分配给上网设备的数字标签。常见的IP分类为:ipv4和ipv6,
-
ipv4:32bit----------------------------->11000000 10101000 00000001 01000010-------------------------->为了好读使用点分十进制表示法------------------->192.168.1.66
-
ipv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,而IPv4的模式下IP的总数是有限的。采用128位地址长度,分成8组------------>位数太多不表示了---------> 为了好读使用冒分十六进制表示法----------------------->2001:0DB8:0000:0023:0008:0800:200C:417A----------------->省略前面的0------------>2001:DB8:0:23:8:800:200C:417A
ipv6特殊情况:如果计算出的16进制表示形式中间有多个连续的0------------------>FF01:0:0:0:0:0:0:1101----------------------->0位压缩表示法---------->FF01::1101
(2)端口
应用程序在设备中唯一的标识
端口号:用两个字节表示的整数,它的取值范围是0~65535,其中0~1023之间的端口号用于一些知名的网络服务或者应用,我们自己使用1024以上的端口号就可以了
注意:一个端口号只能被一个应用程序使用.
(3)协议
计算机网络中,连接和通信的规则被称为网络通信协议
①UDP协议
-
用户数据报协议(User Datagram Protocol)
-
面向无连接通信协议
特点
-
速度快
-
有大小限制,一次最多发送64K
-
不安全,易丢失数据
②TCP协议
-
传输控制协议(Transmission Control Protocol)
-
TCP协议是面向连接的通信协议。
特点
-
速度慢
-
没有大小限制
-
数据安全
3.常用命令
(1)IP相关
-
ipconfig:查看本机IP地址
-
ping IP地址:检查网络是否通畅
特殊IP地址127.0.0.1
是回送地址也称本地回环地址,可以代表本机的IP地址,一般用来测试使用
4.InetAddress类
为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress供我们使用
主要方法
方法名 | 说明 |
---|---|
static InetAddress getByName(String host) | 根据提供的主机名和IP地址创建InetAddress |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress() | 返回IP地址字符串 |
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
//获取InetAddress对象,可以传递主机名或IP地址
InetAddress address = InetAddress.getByName("DESKTOP-03DC35L");
//获得主机名
final String hostName = address.getHostName();
//获得IP地址
final String hostAddress = address.getHostAddress();
System.out.println(hostName);
System.out.println(hostAddress);
}
}
5.UDP通信程序
(1)发送数据
-
找发送端------------------------创建发送端的DatagramSocket对象
-
封装信息------------------------创建数据,并把数据打包(DatagramPacket)
-
发送端发送信息---------------调用DatagramSocket对象的方法发送数据
-
关闭发送端----------------------释放资源
代码实现:
public class SendingClient {
public static void main(String[] args) throws IOException {
//创建发送端
DatagramSocket datagramSocket = new DatagramSocket();
byte[] bytes = "发送消息".getBytes(StandardCharsets.UTF_8);
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
int port = 10000;
//打包消息
DatagramPacket datagramPacket = datagramPacket(bytes, bytes.length, inetAddress, port);
//发送消息
datagramSocket.send(datagramPacket);
//释放资源
datagramSocket.close();
}
/**
* 消息打包
* @param bytes 数据
* @param length 数据大小,全部则为bytes.length
* @param inetAddress 接收端
* @param port 接收端的端口号
*/
private static DatagramPacket datagramPacket(byte[] bytes, int length, InetAddress inetAddress,int port) {
//打包消息
return new DatagramPacket(bytes,length,inetAddress,port);;
}
}
(2)接收数据
-
找接收端----------------------------------------------------------------创建接收端的DatagramSocket对象
-
创建一个容器,用来封装接收的数据--------------------------创建容器,用于接收数据
-
接收数据,并将数据放入到上一步创建的容器中----------调用DatagramSocket的方法接收数据并将数据放入箱子中
-
从容器中获取数据---------------------------------------------------解析数据
-
关闭接收端------------------------------------------------------------释放资源
代码实现:
public class ReceivingClient {
public static void main(String[] args) throws IOException {
//创建接收端,传入端口号,
//无参数则为从随机端口接收数据
DatagramSocket receive = new DatagramSocket(10000);
//创建新的容器,用于接收数据
//DatagramPacket(byte[] buf, int length)
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = getDatagramPacket(bytes);;
//接收数据
receive.receive(datagramPacket);
//获得数据并解析
byte[] data = datagramPacket.getData();
int length = datagramPacket.getLength();
System.out.println(new String(data,0,length));
//释放资源
receive.close();
}
private static DatagramPacket getDatagramPacket(byte[] bytes){
return new DatagramPacket(bytes,bytes.length);
}
}
(3)练习
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收不知道发送端什么时候停止发送,故采用死循环接收
发送端:
public class SendClient {
public static void main(String[] args) throws IOException {
//固定的数据放上面
//2.4 接收端ip地址
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
//2.5 接收端端口号
int port = 10001;
//面向对象化
//1.创建发送对象
DatagramSocket send = createSendClient();
//2.数据准备
//2.1 创建Scanner对象
Scanner scanner = createScanner();
while (true){
//2.2 输入要发送的消息
String message = message(scanner);
//2.3 数据处理str转byte[]
byte[] bytes = strToBytes(message);
//4.封装数据
//4.1 创建封装对象
DatagramPacket dataPacket = createDataPacket(bytes, bytes.length, inetAddress, port);
//5.发送数据
send.send(dataPacket);
if ("886".equals(message)){
break;
}
}
//6.释放资源
send.close();
System.out.println("发送端已关闭");
}
private static DatagramPacket createDataPacket(byte[] bytes,int length,InetAddress inetAddress,int port) {
return new DatagramPacket(bytes,bytes.length,inetAddress,port);
}
private static Scanner createScanner() {
return new Scanner(System.in);
}
private static DatagramSocket createSendClient() throws SocketException {
//创建发送端
DatagramSocket send = new DatagramSocket();
return send;
}
private static String message(Scanner scanner){
System.out.println("请输入要发送的信息");
String s = scanner.nextLine();
return s;
}
private static byte[] strToBytes(String message){
return message.getBytes();
}
}
接收端:
public class ReceivingClient {
public static void main(String[] args) throws IOException {
//准备接收端对象,设置端口号
DatagramSocket receiveClient = createReceivedClient(10001);
//准备新容器,用来接收数据
//1.接收数据大小
DatagramPacket dataBox = getDataBox();
while (true){
//接收数据
receiveClient.receive(dataBox);
//从容器中拿数据,输出到控制台
byte[] data = dataBox.getData();
//实际接受到的数据大小
int length = dataBox.getLength();
if ("886".equals(new String(data,0,length))){
System.out.println("发送端不再发送消息,接收端准备关闭");
break;
}else {
System.out.println("实际接收到的数据大小:"+length);
System.out.println(new String(data,0,length));
}
}
//关闭接收端
receiveClient.close();
System.out.println("接收端已关闭");
}
private static DatagramPacket getDataBox() {
byte[] bytes = new byte[1024];
DatagramPacket dataBox = new DatagramPacket(bytes,bytes.length);
return dataBox;
}
private static DatagramSocket createReceivedClient(int port) throws SocketException {
DatagramSocket receiveClient = new DatagramSocket(port);
return receiveClient;
}
}
(4)UDP的三种通信方式
-
单播----------------------------------1对1,单发送单接收
-
组播(ipv4)----------------------1对多,单发送对一组接收
多播(ipv6)
-
广播----------------------------------1对所有,单发送对所有接收
(5)组播代码实现
组播地址:224.0.0.0~239.255.25.255
其中224.0.0.0~224.0.0.255为预留的组播地址
①.组播发送
和上边发送数据步骤基本一致,但发送数据略有不同
在单播中,是发给指定IP的电脑,但是在组播当中,是发给组播地址
public class SendClient {
public static void main(String[] args) throws IOException {
//创建对象
DatagramSocket datagramSocket = new DatagramSocket();
//数据准备
String s = "组播测试";
byte[] bytes = s.getBytes();
InetAddress inetAddress = InetAddress.getByName("224.0.1.0");
//封装数据
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,inetAddress,10002);
datagramSocket.send(datagramPacket);
datagramSocket.close();
}
}
②组播接收(主要写区别了,大体一致)
-
组播接收端创建的是new MulticastSocket(10086);
-
在创建容器和接收数据之间,添加一步:把当前电脑添加到这个组中
public class ReserveClient {
public static void main(String[] args) throws IOException {
MulticastSocket multicastSocket = new MulticastSocket(10002);
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
//添加电脑到组中
multicastSocket.joinGroup(InetAddress.getByName("224.0.1.0"));
multicastSocket.receive(packet);
byte[] data = packet.getData();
int length = packet.getLength();
System.out.println(new String(data,0,length));
multicastSocket.close();
}
}
(6)UDP广播代码实现
发送
-
在单播中,是发给指定IP的电脑,但是在广播当中,是发给广播地址
-
广播地址:255.255.255.255
public class SendClient {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket = new DatagramSocket();
String message = "广播测试";
final byte[] bytes = message.getBytes();
InetAddress inetAddress = InetAddress.getByName("255.255.255.255");
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,inetAddress,10002);
datagramSocket.send(datagramPacket);
datagramSocket.close();
}
}
接收
-
跟接收一模一样,啥都不用改
6.TCP通信程序
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象。通信之前要保证连接已经建立
通过Socket产生IO流来进行网络通信
(1)发送数据
-
创建客户端的socket对象Socket(String host,int port),与指定服务端连接
-
获得输出流,写数据
-
释放资源
public class SendClient {
public static void main(String[] args) throws IOException {
//创建一个Socket对象,两个参数
//服务器ip地址
//端口号
Socket socket = new Socket("127.0.0.1",10003);
//获取输出流,写数据
OutputStream outputStream = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(outputStream);
//写数据
String s = "tcp发送消息";
osw.write(s);
//释放资源
socket.close();
}
}
(2)接收数据
-
创建服务端的socket对象ServerSocket(int port)
-
监听客户端连接,返回一个socket对象,accept()等待连接,死等(阻塞)
-
获得输入流,读取数据,并把数据显示或解析
-
释放资源
public class ServiceClient {
public static void main(String[] args) throws IOException {
//创建服务端的socket对象ServerSocket
ServerSocket serverSocket = new ServerSocket(10003);
//等待客户端连接,连接后会返回一个socket对象
Socket accept = serverSocket.accept();
//获取输入流,读取数据
InputStream inputStream = accept.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream);
int n;
while ((n = isr.read()) != -1){
System.out.print((char) n);
}
//释放资源
isr.close();
serverSocket.close();
}
}
注意点:
-
accept()方法是阻塞的,作用就是等待客户端连接
-
客户端创建对象运行后会连接服务器,如果服务器没有启动则会报ConnectException异常
-
与服务器连接是通过三次握手协议保证的
-
对客户端来讲,是往外写的,所以是输出流
-
对服务端来讲,是往里读的,所以是输入流
-
IO流中read()方法也是阻塞的
-
客户端在关流的时候,还有一个往服务器写结束标记符的动作
-
最后与服务端断开连接,是通过四次挥手协议保证连接终止的
(3)三次握手
-
第一次:客户端向服务器发出连接请求,等待服务器确认
-
第二次:服务器向客户端返回一个响应,告诉客户端收到了请求
-
第三次:客户端再次发出请求确认信息,连接建立
(4)四次挥手
-
第一次:客户端向服务端发出取消连接的请求
-
第二次:服务器向客户端返回一个响应,表示收到客户端请求
-
第三次:服务器会将最后的数据进行处理,处理完成后服务器会向客户端发出确认取消的消息
-
第四次:客户端再次发送确认取消连接的消息,服务器取消连接
(5)练习
客户端:发送数据,接收服务器反馈
服务器:接收数据,给出反馈
public class SendClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",10005);
OutputStream outputStream = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(outputStream);
osw.write("客户端请求:hello world");
//需要写一个结束标记
//注意:不能把关流操作放到这,关流完成后会导致整个socket关闭
//shutdownOutput() 仅仅关闭输出流,并写一个结束标记
osw.flush();
socket.shutdownOutput();
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int n;
while ((n = isr.read()) != -1){
System.out.print((char) n);
}
isr.close();
osw.close();
socket.close();
}
}
public class ServerClient {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(10005);
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream);
int n;
while ((n = isr.read()) != -1){
System.out.print((char) n);
}
OutputStream outputStream = accept.getOutputStream();
outputStream.write("服务端响应:你是谁?".getBytes());
outputStream.close();
isr.close();
accept.close();
serverSocket.close();
}
}
(6)练习02
客户端:将本地文件上传到服务器。接收服务器的反馈
服务器:接收客户端上传的文件并保存,保存完毕之后给出反馈
public class SendClient {
public static void main(String[] args) throws IOException {
//创建socket对象
//传入地址和端口号
Socket socket = new Socket("127.0.0.1",10005);
//优化输入流的变量
byte[] bytes = new byte[1024];
int length;
//创建一个File对象,获取文件名和文件路径
File file = new File("src\\tcp\\socket_tcp03_2\\music");
//创建一个ArrayList数组,准备保存文件名
ArrayList<String> fileNameList = new ArrayList<>();
//查看该目录下所有文件
File[] files = file.listFiles();
if (files.length != 0){
for (File listFile : files) {
fileNameList.add(listFile.getName());
}
}
//创建一个字节输入流,准备从本地文件中读取一个作为数据写出
//传入文件路径
FileInputStream fis = new FileInputStream(new File(file, fileNameList.get(0)));
//优化输入流
BufferedInputStream bis = new BufferedInputStream(fis);
//创建一个集合,用来保存读取到的数据
ArrayList<byte[]> dataList = new ArrayList<>();
while ((length = bis.read(bytes)) != -1){
dataList.add(Arrays.copyOfRange(bytes,0,length));
}
//获取输出流,准备写出数据
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
for (byte[] bytes1 : dataList) {
bos.write(bytes1);
bos.flush();
}
//不传输数据后,单独关闭socket的输出流
socket.shutdownOutput();
//获取输入流,准备接收服务端的反馈
InputStream is = socket.getInputStream();
bis = new BufferedInputStream(is);
while ((length = bis.read(bytes))!= -1){
System.out.print(new String(bytes,0,length));
}
socket.close();
}
}
Server类,用来实现服务端的具体操作
public class Server {
/**
* 遍历数组并写出
* @param listBytes 保存数据的集合
* @param osw 输出流
* @throws IOException 会抛出IO异常
*/
public void traverseAndWrite(ArrayList<byte[]> listBytes, BufferedOutputStream osw) throws IOException {
for (byte[] listByte : listBytes) {
osw.write(listByte);
}
}
/**
* 输出流优化,缓冲字节输出流
* @param fos 需要优化的字节流
* @return 缓冲字节输出流对象
*/
public BufferedOutputStream getBufferedOutputStream(FileOutputStream fos) {
return new BufferedOutputStream(fos);
}
/**
* 创建输出流
* @param file 要写出的文件(路径+文件名)
* @return 返回字节输出流对象
* @throws FileNotFoundException 抛出FileNotFoundException异常
*/
public FileOutputStream getFileOutputStream(File file) throws FileNotFoundException {
return new FileOutputStream(file);
}
/**
* 把内容写入到哪
* @param folder 目录(目标文件夹)
* @param fileName 文件名(目标文件名)
* @param nameSuffix 文件后缀名(格式.jpg/.mp3等)
* @return 返回File对象
*/
public File getFile(File folder, String fileName,String nameSuffix) {
return new File(folder, fileName +nameSuffix);
}
/**
* 获取唯一文件名
* @return 返回唯一文件名(String)
*/
public String getFileName() {
return UUID.randomUUID().toString().replace("-","");
}
/**
* 检查目标文件夹是否存在
* @param folder 目标文件夹
*/
public void inspectFolder(File folder) {
if (!folder.exists()){
if (folder.mkdir()){
System.out.println("文件夹创建成功");
}
}
}
/**
* 设置写出路径
* @param path 目标位置
* @return 返回File对象
*/
public File getFolder(String path) {
return new File(path);
}
/**
* 读取数据
* @param bis 缓冲字节输入流对象
* @param listBytes 存储数据的集合
* @throws IOException 会抛出IOException
*/
public void readData(BufferedInputStream bis, ArrayList<byte[]> listBytes) throws IOException {
byte[] bytes = new byte[1024];
int length;
while ((length = bis.read(bytes)) != -1){
listBytes.add(Arrays.copyOfRange(bytes,0,length));
}
}
/**
* 创建集合
* @return ArrayList<byte[]>
*/
public ArrayList<byte[]> getArrayList() {
return new ArrayList<>();
}
/**
* 优化输入流
* @param inputStream 字节输入流
* @return 返回缓冲字节输入流对象
*/
public BufferedInputStream getBufferedInputStream(InputStream inputStream) {
return new BufferedInputStream(inputStream);
}
/**
* 获取服务端Socket的输入流
* @param serverSocket 服务端的socket对象
* @return 返回服务端Socket的字节输入流对象
* @throws IOException 会抛出IOException
*/
public InputStream getInputStream(Socket serverSocket) throws IOException {
return serverSocket.getInputStream();
}
/**
* 获取服务端Socket对象
* @param ss ServerSocket
* @return 返回服务端的socket对象
* @throws IOException 会抛出IOException
*/
public Socket getServerSocket(ServerSocket ss) throws IOException {
return ss.accept();
}
}
服务端:
public class ServerClient {
public static void main(String[] args) throws IOException {
Server server = new Server();
//创建ServerSocket对象,设置好端口号
ServerSocket ss = new ServerSocket(10005);
//获取服务端的socket对象
Socket serverSocket = server.getServerSocket(ss);
//获取输入流,准备读取数据
InputStream inputStream = server.getInputStream(serverSocket);
//输入流优化
BufferedInputStream bis = server.getBufferedInputStream(inputStream);
//创建一个集合(可选)保存接收到的数据
ArrayList<byte[]> listBytes = server.getArrayList();
//读取数据并保存到listBytes集合中
server.readData(bis, listBytes);
//设置写出路径
String s = "src\\tcp\\socket_tcp03_2\\serverClient_file";
File folder = server.getFolder(s);
//检查文件夹是否存在
server.inspectFolder(folder);
//获取唯一文件名
final String fileName = server.getFileName();
//设置文件后缀名
String nameSuffix = ".mp3";
//在设置好的路径下创建名为【fileName】的空文件
File file = server.getFile(folder, fileName,nameSuffix);
//创建输出流,准备写出数据
FileOutputStream fos = server.getFileOutputStream(file);
//优化输出流
BufferedOutputStream osw = server.getBufferedOutputStream(fos);
//遍历集合并写出数据
server.traverseAndWrite(listBytes, osw);
//获取输出流,准备给反馈
OutputStream os = serverSocket.getOutputStream();
//优化
BufferedOutputStream bos = new BufferedOutputStream(os);
//写反馈
bos.write("保存成功".getBytes());
bos.flush();
//释放资源
serverSocket.close();
ss.close();
}
}
(7)服务端优化
①可多次接收客户端
使用循环虽然可以让服务器处理多个客户端请求。但是无法同时跟多个客户端进行通信。
while (true){
//获取服务端的socket对象
Socket serverSocket = getServerSocket(ss);
//获取输入流,准备读取数据
InputStream inputStream = getInputStream(serverSocket);
//输入流优化
BufferedInputStream bis = getBufferedInputStream(inputStream);
//创建一个集合(可选)保存接收到的数据
ArrayList<byte[]> listBytes = getArrayList();
//读取数据并保存到listBytes集合中
readData(bis, listBytes);
//设置写出路径
String s = "src\\tcp\\socket_tcp03\\serverClient_file";
File folder = getFolder(s);
//检查文件夹是否存在
inspectFolder(folder);
//获取唯一文件名
final String fileName = getFileName();
//设置文件后缀名
String nameSuffix = ".mp3";
//在设置好的路径下创建名为【fileName】的空文件
File file = getFile(folder, fileName,nameSuffix);
//创建输出流,准备写出数据
FileOutputStream fos = getFileOutputStream(file);
//优化输出流
BufferedOutputStream osw = getBufferedOutputStream(fos);
//遍历集合并写出数据
traverseAndWrite(listBytes, osw);
//获取输出流,准备给反馈
OutputStream os = serverSocket.getOutputStream();
//优化
BufferedOutputStream bos = new BufferedOutputStream(os);
//写反馈
bos.write("保存成功".getBytes());
//释放资源
serverSocket.close();
}
②UUID类
主要方法
方法名 | 说明 |
---|---|
static UUID randomUUID() | 静态工厂检索一个类型4《伪随机生成》的UUID。 |
String toString() | 返回表示此 UUID 的 String 对象 |
③多线程版
使用多线程虽然可以让服务器同时处理多个客户端请求。但是资源消耗太大。
public class ServerClient {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象,设置好端口号
ServerSocket ss = new ServerSocket(10005);
while (true){
Socket accept = ss.accept();
ThreadSocket threadSocket = new ThreadSocket(accept);
new Thread(threadSocket).start();
}
}
}
public class ThreadSocket implements Runnable{
Socket socket;
public ThreadSocket(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//获取输入流,准备读取数据
InputStream inputStream = null;
try {
inputStream = getInputStream(socket);
} catch (IOException e) {
e.printStackTrace();
}
//输入流优化
BufferedInputStream bis = getBufferedInputStream(inputStream);
//创建一个集合(可选)保存接收到的数据
ArrayList<byte[]> listBytes = getArrayList();
//读取数据并保存到listBytes集合中
try {
readData(bis, listBytes);
} catch (IOException e) {
e.printStackTrace();
}
//设置写出路径
String s = "src\\tcp\\socket_tcp04\\serverClient_file";
File folder = getFolder(s);
//检查文件夹是否存在
inspectFolder(folder);
//获取唯一文件名
final String fileName = getFileName();
//设置文件后缀名
String nameSuffix = ".mp3";
//在设置好的路径下创建名为【fileName】的空文件
File file = getFile(folder, fileName,nameSuffix);
//创建输出流,准备写出数据
FileOutputStream fos = null;
try {
fos = getFileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//优化输出流
BufferedOutputStream osw = getBufferedOutputStream(fos);
//遍历集合并写出数据
try {
traverseAndWrite(listBytes, osw);
} catch (IOException e) {
e.printStackTrace();
}
//获取输出流,准备给反馈
OutputStream os = null;
try {
os = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
//优化
BufferedOutputStream bos = null;
if (os != null){
bos = new BufferedOutputStream(os);
}
//写反馈
try {
if (bos!=null){
bos.write("保存成功".getBytes());
bos.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
//释放资源
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 遍历数组并写出
* @param listBytes 保存数据的集合
* @param osw 输出流
* @throws IOException 会抛出IO异常
*/
private void traverseAndWrite(ArrayList<byte[]> listBytes, BufferedOutputStream osw) throws IOException {
for (byte[] listByte : listBytes) {
osw.write(listByte);
osw.flush();
}
}
/**
* 输出流优化,缓冲字节输出流
* @param fos 需要优化的字节流
* @return 缓冲字节输出流对象
*/
private BufferedOutputStream getBufferedOutputStream(FileOutputStream fos) {
return new BufferedOutputStream(fos);
}
/**
* 创建输出流
* @param file 要写出的文件(路径+文件名)
* @return 返回字节输出流对象
* @throws FileNotFoundException 抛出FileNotFoundException异常
*/
private FileOutputStream getFileOutputStream(File file) throws FileNotFoundException {
return new FileOutputStream(file);
}
/**
* 把内容写入到哪
* @param folder 目录(目标文件夹)
* @param fileName 文件名(目标文件名)
* @param nameSuffix 文件后缀名(格式.jpg/.mp3等)
* @return 返回File对象
*/
private File getFile(File folder, String fileName,String nameSuffix) {
return new File(folder, fileName +nameSuffix);
}
/**
* 获取唯一文件名
* @return 返回唯一文件名(String)
*/
private String getFileName() {
return UUID.randomUUID().toString().replace("-","");
}
/**
* 检查目标文件夹是否存在
* @param folder 目标文件夹
*/
private void inspectFolder(File folder) {
if (!folder.exists()){
if (folder.mkdir()){
System.out.println("文件夹创建成功");
}
}
}
/**
* 设置写出路径
* @param path 目标位置
* @return 返回File对象
*/
private File getFolder(String path) {
return new File(path);
}
/**
* 读取数据
* @param bis 缓冲字节输入流对象
* @param listBytes 存储数据的集合
* @throws IOException 会抛出IOException
*/
private void readData(BufferedInputStream bis, ArrayList<byte[]> listBytes) throws IOException {
byte[] bytes = new byte[1024];
int length;
while ((length = bis.read(bytes)) != -1){
listBytes.add(Arrays.copyOfRange(bytes,0,length));
}
}
/**
* 创建集合
* @return ArrayList<byte[]>
*/
private ArrayList<byte[]> getArrayList() {
return new ArrayList<>();
}
/**
* 优化输入流
* @param inputStream 字节输入流
* @return 返回缓冲字节输入流对象
*/
private BufferedInputStream getBufferedInputStream(InputStream inputStream) {
return new BufferedInputStream(inputStream);
}
/**
* 获取服务端Socket的输入流
* @param serverSocket 服务端的socket对象
* @return 返回服务端Socket的字节输入流对象
* @throws IOException 会抛出IOException
*/
private InputStream getInputStream(Socket serverSocket) throws IOException {
return serverSocket.getInputStream();
}
}
⑤多线程版优化-线程池
public class ServerClient {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象,设置好端口号
ServerSocket ss = new ServerSocket(10005);
//创建线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
//核心线程数量
5,
//线程池的总数量
10,
//临时线程空闲时间:多长时间没有任务就销毁
60,
//空闲时间单位
TimeUnit.MINUTES,
//阻塞队列,允许多少个线程排队
new ArrayBlockingQueue<>(5),
//创建线程的方式
Executors.defaultThreadFactory(),
//任务拒绝策略
new ThreadPoolExecutor.AbortPolicy()
);
while (true){
Socket accept = ss.accept();
ThreadSocket threadSocket = new ThreadSocket(accept);
poolExecutor.submit(threadSocket);
final int activeCount = poolExecutor.getActiveCount();
System.out.println(activeCount);
}
}
}
public class ThreadSocket implements Runnable{
Socket socket;
public ThreadSocket(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//获取输入流,准备读取数据
InputStream inputStream = null;
try {
inputStream = getInputStream(socket);
} catch (IOException e) {
e.printStackTrace();
}
//输入流优化
BufferedInputStream bis = getBufferedInputStream(inputStream);
//创建一个集合(可选)保存接收到的数据
ArrayList<byte[]> listBytes = getArrayList();
//读取数据并保存到listBytes集合中
try {
readData(bis, listBytes);
} catch (IOException e) {
e.printStackTrace();
}
//设置写出路径
String s = "src\\tcp\\socket_tcp04\\serverClient_file";
File folder = getFolder(s);
//检查文件夹是否存在
inspectFolder(folder);
//获取唯一文件名
final String fileName = getFileName();
//设置文件后缀名
String nameSuffix = ".mp3";
//在设置好的路径下创建名为【fileName】的空文件
File file = getFile(folder, fileName,nameSuffix);
//创建输出流,准备写出数据
FileOutputStream fos = null;
try {
fos = getFileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//优化输出流
BufferedOutputStream osw = getBufferedOutputStream(fos);
//遍历集合并写出数据
try {
traverseAndWrite(listBytes, osw);
} catch (IOException e) {
e.printStackTrace();
}
//获取输出流,准备给反馈
OutputStream os = null;
try {
os = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
//优化
BufferedOutputStream bos = null;
if (os != null){
bos = new BufferedOutputStream(os);
}
//写反馈
try {
if (bos!=null){
bos.write("保存成功".getBytes());
bos.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
//释放资源
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 遍历数组并写出
* @param listBytes 保存数据的集合
* @param osw 输出流
* @throws IOException 会抛出IO异常
*/
private void traverseAndWrite(ArrayList<byte[]> listBytes, BufferedOutputStream osw) throws IOException {
for (byte[] listByte : listBytes) {
osw.write(listByte);
osw.flush();
}
}
/**
* 输出流优化,缓冲字节输出流
* @param fos 需要优化的字节流
* @return 缓冲字节输出流对象
*/
private BufferedOutputStream getBufferedOutputStream(FileOutputStream fos) {
return new BufferedOutputStream(fos);
}
/**
* 创建输出流
* @param file 要写出的文件(路径+文件名)
* @return 返回字节输出流对象
* @throws FileNotFoundException 抛出FileNotFoundException异常
*/
private FileOutputStream getFileOutputStream(File file) throws FileNotFoundException {
return new FileOutputStream(file);
}
/**
* 把内容写入到哪
* @param folder 目录(目标文件夹)
* @param fileName 文件名(目标文件名)
* @param nameSuffix 文件后缀名(格式.jpg/.mp3等)
* @return 返回File对象
*/
private File getFile(File folder, String fileName,String nameSuffix) {
return new File(folder, fileName +nameSuffix);
}
/**
* 获取唯一文件名
* @return 返回唯一文件名(String)
*/
private String getFileName() {
return UUID.randomUUID().toString().replace("-","");
}
/**
* 检查目标文件夹是否存在
* @param folder 目标文件夹
*/
private void inspectFolder(File folder) {
if (!folder.exists()){
if (folder.mkdir()){
System.out.println("文件夹创建成功");
}
}
}
/**
* 设置写出路径
* @param path 目标位置
* @return 返回File对象
*/
private File getFolder(String path) {
return new File(path);
}
/**
* 读取数据
* @param bis 缓冲字节输入流对象
* @param listBytes 存储数据的集合
* @throws IOException 会抛出IOException
*/
private void readData(BufferedInputStream bis, ArrayList<byte[]> listBytes) throws IOException {
byte[] bytes = new byte[1024];
int length;
while ((length = bis.read(bytes)) != -1){
listBytes.add(Arrays.copyOfRange(bytes,0,length));
}
}
/**
* 创建集合
* @return ArrayList<byte[]>
*/
private ArrayList<byte[]> getArrayList() {
return new ArrayList<>();
}
/**
* 优化输入流
* @param inputStream 字节输入流
* @return 返回缓冲字节输入流对象
*/
private BufferedInputStream getBufferedInputStream(InputStream inputStream) {
return new BufferedInputStream(inputStream);
}
/**
* 获取服务端Socket的输入流
* @param serverSocket 服务端的socket对象
* @return 返回服务端Socket的字节输入流对象
* @throws IOException 会抛出IOException
*/
private InputStream getInputStream(Socket serverSocket) throws IOException {
return serverSocket.getInputStream();
}
}
(8)问题【待解决】
虽然实现了多线程和重复接收的问题,但在测试的时候还有个问题,记录一下
文件写好之后,由于服务端的ServerSocket没有关闭,在服务端读取完数据并写出后,其状态是被java虚拟机使用的,故此文件是无法删除的,但能正常访问~
如图:
我传了三个文件
然后我去文件夹看一下状态
无法删除
但是可以正常听
关闭服务器
文件会正常