1.网络编程
1.1 网络编程三要素
1. 协议
两个在于网络情况下的计算机数据传递,都需要对应的协议来完成。常见的协议有TCP协议和UDP协议
UDP协议:
用户数据报协议(User Datagram Protocol)
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在接收到数据后,也不会向发送端反馈是否收到数据
由于使用UDP协议消耗资源小,通信效率高,所以通常用于音频,视频和普通数据的传输
例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响,但是在使用UDP传输数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
TCP协议:
传输控制协议(Transmission Control Protocol)
TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输,在TCP连接中要明确客户端和服务器端,由客户端向服务器端发出连接请求,每次连接的创建都需要经过三次握手
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器端的三次交互,以保证连接的可靠
第一次握手:客户端向服务器端发送连接请求,等待服务器确认
第二次握手:服务器端向客户端回送一个响应,通知客户端接收到了连接请求
第三次握手:客户端再次向服务器端发送确认消息,确认连接
完成三次握手,连接建立后,客户端和服务器端就可以开始数据传输了。由于这种面向连接的特性,TCP协议可以保证数据传输的安全,所以应用十分广泛,例如文件上传,下载文件,浏览网页。
2. IP地址
Internet Protocol Address
当前计算机在网络中的一个地址编号,类似于手机号号码
IP地址有IPv4协议和IPv6协议
IPv4是一个32位的二进制数,
IPv4:是给每个链接在网络上的主机分配一个32bit地址。通常展示效果是a.b.c.d 例如 192.168.1.1,a.b.c.d 各代表0 ~ 255的数字,目前已经消耗殆尽 42亿个。按照TCP/IP协议规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是"11000000 10101000 00000001 01000010",这么长的地址,处理起来很费劲,为了方便使用,IP地址经常被写成十进制的形式,中间使用符号"."分隔不同的字节。于是上面的地址可以表示成"192.168.1.66"。IP地址的这种表示法叫做"点分十进制表示法",这显然比1和0容易记忆得多。
IPv6
IPv6是能够保证地球上的每一粒沙子都有一个IP地址。
128位地址长度,16字节一组
8组 0x0 ~ 0xFFFF
.DOS常用命令:
ipconfig:查看本机ip地址
ping IP地址:检查网络是否连通
.特殊IP地址:
127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
3. 端口号
端口号是当前应用程序在计算机中的一个编号。可以让计算机明确知道,当前的数据是给予那一个程序使用,或者数据从哪一个程序出现的。
端口号是一个short类型 0 ~ 65535
0~1024不能用于自定义端口号使用,特定的系统端口号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WfRq2ltP-1594732603930)(C:\Users\86182\Pictures\Saved Pictures\网络协议示意图.png)]
1.2 InetAddress
InetAddress:此类表示Internet协议(IP)地址,是SUN公司提供给开发者得IP类
- 相关方法
方法名 | 说明 |
---|---|
InetAddress getLocalhost(); | 获取本机IP地址类对象 |
InetAddress getByName(String str); | 根据指定的主机名获取对应的IP地址对象 |
InetAddress[] getAllByName(String str); | 获取指定主机名,或者域名对应的所有IP地址类对象 |
String getHostName(); | 获取此IP地址得主机名 |
String getHostAddress(); | 返回文本显示中得IP地址字符串 |
- 代码演示:
package com.tao;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @Classname TestInetAddress
* @Description 测试IP类
* @Date 2020/7/13 14:36
* @Author Anonymous
*/
public class TestInetAddress {
public static void main(String[] args) throws UnknownHostException {
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
InetAddress byName = InetAddress.getByName("DESKTOP-3KH4ONS");
System.out.println(byName);
InetAddress byName1 = InetAddress.getByName("192.168.0.104");
System.out.println(byName1);
String hostName = byName1.getHostName();
String hostAddress = byName1.getHostAddress();
System.out.println(hostName + "---" + hostAddress);
InetAddress byName2 = InetAddress.getByName("www.4399.com");
System.out.println(byName2);
InetAddress[] allByName = InetAddress.getAllByName("www.jd.com");
for (InetAddress inetAddress : allByName) {
System.out.println(inetAddress);
}
InetAddress[] allByName1 = InetAddress.getAllByName("www.taobao.com");
for (InetAddress inetAddress : allByName1) {
System.out.println(inetAddress);
}
}
}
/**
DESKTOP-3KH4ONS/192.168.0.104
DESKTOP-3KH4ONS/192.168.0.104
/192.168.0.104
DESKTOP-3KH4ONS---192.168.0.104
www.4399.com/219.157.114.132
www.jd.com/42.225.97.3
www.taobao.com/42.236.122.9
www.taobao.com/42.236.122.10
1.3 UDP和TCP/IP的区别
UDP
1. 面向无连接,数据传递不算特别安全
2. 因为面向无连接,传输速度快
3. 因为面向无连接,数据传递存在丢包问题
4. UDP没有客户端和服务器区别,都可以作为发送端和接收端
UDP协议使用场景
直播,网络游戏
TCP/IP
1. 面向连接,数据传递较为安全
2. 因为面向连接,所有传递速度较慢
3. 面向连接,数据传递有保障
4. TCP/IP协议是有明确的服务器和客户端概念
TCP/IP协议使用场景
客户端登陆,数据下载,文件传输
2.UDP通信程序
2.1 UDP数据传输方式
User Datagram Protocol
数据传递采用数据包方式传递,所有的数据要进行打包操作,并且没有对应的客户端服务器概念,有且只有发送段和接收端
Socket 套接字
数据需要进行传递操作,在数据传递的两台计算机当中必须有对应的Socket。这里采用UDP协议,那么必须有一个UDP协议的Socket
DatagramSocket();
创建一个发送端UDP协议Socket对象
DatagramSocket(int port);
创建一个接收端UDP协议的Socket对象,这里需要【监听】指定端口
发送端数据包的打包方法:
DatagramPacket DatagramPacket(byte[] buf, int length, InetAddress address, int port);
buf: 需要传递数据的字节数组
length:是当前字节数组中数据容量字节数
address:接收端IP地址对象
port: 接收端对应的端口号
接收端数据包接收方式
这里需要准备一个空的数据包
DatagramPacket DatagramPacket(byte[] buf, int length);
buf: 字节缓冲数组,通常是1024整数倍
length: 当前字节缓冲数组的容量
2.2 发送端
流程:
1. 创建UDP服务器对应的发送端Socket
2. 准备对应数据包,需要带有指定数据
3. 发送数据 send
4. 关闭UDP发送端
package com.tao;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/*
流程:
1. 创建UDP服务器对应的发送端Socket
2. 准备对应数据包,需要带有指定数据
3. 发送数据 send
4. 关闭UDP发送端
*/
public class SenderDemo1 {
public static void main(String[] args) throws IOException {
System.out.println("发送端启动");
// 创建对应的Socket
DatagramSocket socket = new DatagramSocket();
// 准备数据包
byte[] bytes = "今天中午吃蒸羊羔...".getBytes();
DatagramPacket packet = new DatagramPacket(bytes, // 字节数组数据
bytes.length, // 字节数组数据长度
InetAddress.getLocalHost(), // 指定接收端IP地址
8848); // 8848对应端口号
// 发送数据包
socket.send(packet);
// 关闭UDP发送端
socket.close();
}
}
2.3 接收端
流程:
1. 打开UDP服务,并且监听指定端口
2. 创建新的空数据包
3. 通过Socket接收数据
4. 关闭UDP服务接收端
package com.qfedu.b_udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/*
流程:
1. 打开UDP服务,并且监听指定端口
2. 创建新的空数据包
3. 通过Socket接收数据 receive
4. 关闭UDP服务接收端
*/
public class ReceiveDemo1 {
public static void main(String[] args) throws IOException {
// 创建Socket监听端口
DatagramSocket socket = new DatagramSocket(8848);
// 准备空数据包
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
// 接收数据
socket.receive(packet);
// 确定接收到的字节长度
int length = packet.getLength();
//返回数据缓冲区
byte[] b = packet.getData();
System.out.println(new String(b, 0, length));
// 关闭socket
socket.close();
}
}
2.4 UDP通信练习
案例需求:
UDP发送数据:数据来自键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
package com.tao;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
/**
* @Classname Send
* @Description 发送端
* @Date 2020/7/13 16:43
* @Author Anonymous
*/
public class Send {
public static void main(String[] args) throws IOException {
//创建发送端的Socket对象
DatagramSocket datagramSocket = new DatagramSocket();
//自己封装录入键盘数据
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = bufferedReader.readLine())!=null){
//输入的数据是886,停止发送
if ("886".equals(line)){
break;
}
//创建数据并把数据打包
byte[] bytes = line.getBytes();
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),9999);
datagramSocket.send(datagramPacket);
}
//关闭发送端
datagramSocket.close();
}
}
package com.tao;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* @Classname Receive
* @Description 接收端
* @Date 2020/7/13 16:43
* @Author Anonymous
*/
public class Receive {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket = new DatagramSocket(9999);
while (true){
//创建一个数据包用于接收数据
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);
datagramSocket.receive(datagramPacket);
//解析数据包并在控制台打印
System.out.println(new String(datagramPacket.getData(),0,datagramPacket.getLength()));
}
//因为一直在接收,所以不需要关闭
// datagramSocket.close();
}
}
上面的发送端也可以用Scanner:
package com.tao;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
import java.util.Scanner;
/**
* @Classname Send
* @Description 发送端
* @Date 2020/7/13 16:43
* @Author Anonymous
*/
public class Send {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket = new DatagramSocket();
while (true){
Scanner sc = new Scanner(System.in);
String next = sc.next();
if ("886".equals(next)){
break;
}
byte[] bytes = next.getBytes();
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),9999);
datagramSocket.send(datagramPacket);
}
datagramSocket.close();
}
}
3. TCP通信程序
3.1 TCP概述
TCP相对于UDP比较稳定的传输协议,这里存在三次握手,保证连接状态,同时有明确的客户端和服务端之分
TCP服务中需要服务器端先启动,需要监听指定端口,等待客户端连接。
客户端主动连接服务器,和服务器连接之后,才可以进行数据交互,服务器不能主动连接客户端的。
TCP操作而言,Java中提供了两个Socket
1. 服务端Socket
java.net.ServerSocket;
创建对应的ServerScoket开启服务器,等待客户端连接
2. 客户端Socket
java.net.Socket
创建客户端Scoket,并且连接服务器,同时将Socket发送给服务器绑定注册。
3.2 客户端Socket
给客户端提供数据传输的符合TCP/IP要求的Socket对象
构造方法 Constructor
Socket(String host, int port);
host是服务器IP地址,port对应服务器程序的端口号
通过指定的服务器IP地址和端口号,获取TCP连接对象
成员方法 Method
InputStream getInputStream();
获取Socket对象输入字节流,可以从服务器获取对应的数据
InputStream是一个资源,需要在程序退出是关闭
Read
OutputStream getOutputStream();
获取Sokcet对象输出字节流,可以发送数据到服务器
OutputStream是一个资源,需要在程序退出是关闭
Write
void close();
关闭客户端Socket
void shutdownOutput();
禁止当前Socket发送数据
TCP/IP协议对应的Socket是给予IO流实现的。
3.3 服务器端ServerSocket
在服务端开启Socket服务器
构造方法 Constructor:
ServerSocket(int port);
开启ServerSocket服务器,并且明确当前服务端口是谁
成员方法 Method:
Socket accept();
监听并且连接,得到一个Socket对象,同时该方法是一个阻塞方法,会处于一个始终
的监听状态
返回的是Socket,也就是客户端Socket对象,获取到当前Socket对象,相对于获取到
客户端连接,同时使用的Socket和客户端一致。
3.4 代码演示
/**
*案例需求
*客户端:发送数据,接收服务器端反馈
*服务器端:收到消息后给出反馈
*/
package com.tcp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* @Classname TestSocket
* @Description TODO
* @Date 2020/7/13 21:41
* @Author Anonymous
*/
public class TestSocket {
public static void main(String[] args) throws IOException {
//创建客户端的Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
//获取输出流,写出数据
OutputStream outputStream = socket.getOutputStream();
byte[] bytes = "你好,西南交通大学".getBytes();
outputStream.write(bytes);
//接收服务器端反馈
InputStream inputStream = socket.getInputStream();
byte[] b = new byte[1024];
inputStream.read(b);
System.out.println(new String(b,0,b.length));
//释放资源
// inputStream.close();
// outputStream.close();
socket.close();
}
}
package com.tcp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Classname TestServerSocket
* @Description TODO
* @Date 2020/7/13 21:41
* @Author Anonymous
*/
public class TestServerSocket {
public static void main(String[] args) throws IOException {
//创建服务器端的Socket对象
ServerSocket serverSocket = new ServerSocket(9999);
//监听客户端连接,返回一个Socket对象
Socket socket = serverSocket.accept();
//获取输入流读取数据并打印在控制台上
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
inputStream.read(bytes);
System.out.println(new String(bytes,0,bytes.length));
//给出反馈
OutputStream outputStream = socket.getOutputStream();
outputStream.write("数据已经收到".getBytes());
}
}
3.5练习
/**
*案例需求
*客户端数据来自键盘录入,直到输入的数据是886,发送数据结束
*服务端:接到数据后在控制台输出
*/
package com.tao;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
/**
* @Classname Send1
* @Description TODO
* @Date 2020/7/14 11:07
* @Author Anonymous
*/
public class Send1 {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
//数据来自键盘录入,直到输入的数据是886,停止发送
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//封装输出流对象
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String line;
while ((line = br.readLine())!=null){
if ("886".equals(line)){
break;
}
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
socket.close();
}
}
package com.tao;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Classname Receive1
* @Description TODO
* @Date 2020/7/14 11:07
* @Author Anonymous
*/
public class Receive1 {
public static void main(String[] args) throws IOException {
//创建服务器Socket对象
ServerSocket serverSocket = new ServerSocket(9999);
//监听客户端的连接,返回一个相应的Socket对象
Socket socket = serverSocket.accept();
//获取输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while ((line = bufferedReader.readLine())!=null){
System.out.println(line);
}
//释放资源
serverSocket.close();
}
}
3.6文件上传
3.6.1 文件上传案例分析
TCP通信文件上传案例:
原理:客户端读取本地的文件,把文件上传到服务器,服务器把上传的文件保存到服务器的硬盘上
步骤:
1.客户端使用本地的字节输入流读取要上传的文件
2.客户端使用网络字节输出流,把读取的文件上传到服务器
3.服务器使用网络字节输入流,读取客户端上传的文件
4.服务器使用本地的字节输出流,把读取的文件保存到服务器的硬盘上
5.服务器要使用网络字节输出流,给客户端回写一个"上传成功"
6.客户端使用网络字节输入流,读取服务器回写的数据
7.释放资源
注意:客户端和服务器对硬盘进行读写,需要使用自己创建的字节流对象(本地流)
客户端和服务器之间进行读写,必须使用Socket中提供的字节流对象(网络流)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E2H144ZL-1594732603931)(C:\Users\86182\Pictures\Saved Pictures\文件上传细节图例.png)]
3.6.2 代码实现
package com.tcp;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* @Classname TestFileUploadSocket
* @Description 文件上传之客户端
* @Date 2020/7/14 13:42
* @Author Anonymous
*/
public class TestFileUploadSocket {
public static void main(String[] args) throws IOException {
//创建一个本地流用来读取硬盘文件
InputStream localInputStream = new FileInputStream(new File("E:\\btn.jpg"));
//创建一个客户端的Socket对象并指定服务器的IP地址和端口号
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
//创建一个输出流用于向服务器端写数据
OutputStream outputStream = socket.getOutputStream();
//定义一个缓冲区用于往里面读取数据
byte[] bytes = new byte[1024];
int len;
while ((len=localInputStream.read(bytes))!=-1){
outputStream.write(bytes,0,len);
}
/**
* 解决阻塞问题:
* void shutdownOutput()
* 对于TCP套接字,任何以前写入的数据都会被发送,并且后跟TCP的正常连接终止序列
**/
socket.shutdownOutput();
//创建一个网络输入流用来读取服务器端回写的数据
InputStream internetInputStream = socket.getInputStream();
byte[] b = new byte[1024];
int length;
while ((length=internetInputStream.read(b))!=-1){
System.out.println(new String(b,0,length));
}
//释放资源
outputStream.close();
socket.close();
localInputStream.close();
}
}
package com.tcp;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
/**
* @Classname TestFileUploadServerSocket
* @Description 文件上传之服务器端
* @Date 2020/7/14 13:43
* @Author Anonymous
*/
public class TestFileUploadServerSocket {
public static void main(String[] args) throws IOException {
//创建服务器端的Socket对象
ServerSocket serverSocket = new ServerSocket(9999);
/*
让服务器一直处于监听状态(死循环accept方法)
有一个客户端上传文件,就保存一个文件
*/
while (true){
//监听客户端连接,返回一个Socket对象
Socket socket = serverSocket.accept();
//获取网络输入流用于读取客户端发送的数据
InputStream internetInputStream = socket.getInputStream();
//判断E盘下的文件夹是否存在,不存在则创建一个
File file = new File("E:\\upload");
if (!file.exists()){
file.mkdir();
}
/*
自定义文件的命名规则:防止同名的文件被覆盖
规则:域名+毫秒值+随机数
*/
String fileName = "tao" +
System.currentTimeMillis() +
new Random().nextInt(999999) + ".jpg";
//创建本地输出流用于写到服务器端的硬盘上
//OutputStream localOutputStream = new FileOutputStream(file + "\\1.jpg");
OutputStream localOutputStream = new FileOutputStream(file + "\\" + fileName);
//定义一个字节缓冲区用于读取数据
byte[] bytes = new byte[1024];
int len;
while ((len=internetInputStream.read(bytes))!=-1){
localOutputStream.write(bytes,0,len);
}
//创建一个网络输出流,当上传成功后给客户端一个反馈
OutputStream internetOutputStream = socket.getOutputStream();
byte[] b = "上传成功".getBytes();
internetOutputStream.write(b);
//释放资源
localOutputStream.close();
internetInputStream.close();
}
//服务器不需要再关闭
//serverSocket.close();
}
}
阻塞问题分析:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DSNUMtlm-1594732603932)(E:\Java百度网盘解压\Java SE\09网络编程\22.【网络编程】-笔记\resource\04_文件上传案例的阻塞问题.bmp)]
改用多线程代码实现:
package com.tcp;
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 TestFileUploadServerSocketThread {
public static void main(String[] args) throws IOException {
//1.创建一个服务器ServerSocket对象,和系统要指定的端口号
ServerSocket server = new ServerSocket(9999);
//2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
/*
让服务器一直处于监听状态(死循环accept方法)
有一个客户端上传文件,就保存一个文件
*/
while(true){
Socket socket = server.accept();
/*
使用多线程技术,提高程序的效率
有一个客户端上传文件,就开启一个线程,完成文件的上传
*/
new Thread(new Runnable() {
//完成文件的上传
@Override
public void run() {
try {
//3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//4.判断d:\\upload文件夹是否存在,不存在则创建
File file = new File("E:\\upload");
if(!file.exists()){
file.mkdirs();
}
/*
自定义一个文件的命名规则:防止同名的文件被覆盖
规则:域名+毫秒值+随机数
*/
String fileName = "itcast"+System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
//5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
//FileOutputStream fos = new FileOutputStream(file+"\\1.jpg");
FileOutputStream fos = new FileOutputStream(file+"\\"+fileName);
//6.使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
int len =0;
byte[] bytes = new byte[1024];
while((len = is.read(bytes))!=-1){
//7.使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
fos.write(bytes,0,len);
}
//8.使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
//9.使用网络字节输出流OutputStream对象中的方法write,给客户端回写"上传成功"
socket.getOutputStream().write("上传成功".getBytes());
//10.释放资源(FileOutputStream,Socket,ServerSocket)
fos.close();
socket.close();
}catch (IOException e){
System.out.println(e);
}
}
}).start();
}
//服务器就不用关闭
//server.close();
}
}
//使用lambda表达式
package com.tcp;
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 TestFileUploadServerSocketThread {
public static void main(String[] args) throws IOException {
//1.创建一个服务器ServerSocket对象,和系统要指定的端口号
ServerSocket server = new ServerSocket(9999);
//2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
/*
让服务器一直处于监听状态(死循环accept方法)
有一个客户端上传文件,就保存一个文件
*/
while(true){
Socket socket = server.accept();
/*
使用多线程技术,提高程序的效率
有一个客户端上传文件,就开启一个线程,完成文件的上传
*/
new Thread(()-> {
FileOutputStream fos = null;
//完成文件的上传
try {
//3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//4.判断E:\\upload文件夹是否存在,不存在则创建
File file = new File("E:\\upload");
if(!file.exists()){
file.mkdirs();
}
/*
自定义一个文件的命名规则:防止同名的文件被覆盖
规则:域名+毫秒值+随机数
*/
String fileName = "tao"+System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
//5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
//FileOutputStream fos = new FileOutputStream(file+"\\1.jpg");
fos = new FileOutputStream(file+"\\"+fileName);
//6.使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
int len =0;
byte[] bytes = new byte[1024];
while((len = is.read(bytes))!=-1){
//7.使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
fos.write(bytes,0,len);
}
//8.使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
//9.使用网络字节输出流OutputStream对象中的方法write,给客户端回写"上传成功"
socket.getOutputStream().write("上传成功".getBytes());
//10.释放资源(FileOutputStream,Socket,ServerSocket)
}catch (IOException e){
System.out.println(e);
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
//服务器就不用关闭
//server.close();
}
}