网络编程可以让程序与网络上的其他设备中的程序进行数据交互。
常见的通信模式有两种方式:Client-Server(CS)、Browser/Server(BS)
实现网络编程关键的三要素
IP地址:设备在网络中的地址,是唯一的标识。
端口:应用程序在设备中唯一的标识。
协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。
IP地址形式
公网地址、和私有地址(局域网使用)。
192.168.开头的就是常见的局域网地址,范围即为192.168.0.0–192.128.255.255,专门为组织机构内部使用。
IP常用命令
ipconfig:查看本机IP地址,ping IP 地址:检查网络是否连通。
本机IP:127.0.0.1或localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本地。
InetAddress的使用
此类表示Internet协议地址。
public static InetAddress getLocalHost()
返回本主机的地址对象
public static InetAddress getByName(String host)
得到指定主机的IP地址对象,参数是域名或者IP地址
public static getHostName()
获取此IP地址的主机名
public static getHostAddress()
返回IP地址字符串
public boolean isReachable(int timeout)
在指定毫秒内连通该IP地址对应的主机,连通返回true
public static void main(String[] args) throws Exception {
// 1.获取本机地址对象。
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
// 2.获取域名ip对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
// 3.获取公网IP对象。
InetAddress ip3 = InetAddress.getByName("112.80.248.76");
System.out.println(ip3.getHostName());
System.out.println(ip3.getHostAddress());
// 4.判断是否能通: ping 5s之前测试是否可通
System.out.println(ip3.isReachable(5000));
}
端口号
标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是0-65535
端口类型
周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
注册端口:1024~49151,分配给用户进程或者某些应用程序(如:TomCat占用8080,MySQL占用3306)
动态端口:49152到65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配
我们自己开发的选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错
网络通信协议
TCP/IP
UDP
UDP是一种无连接、不可靠传输的协议
将数据源IP,目的地IP和端口封装成数据包,不需要建立连接
每个数据包的大小限制在64KB内
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送,发送数据结束时无需释放资源,开销小,速度快
TCP
TCP协议是“面向连接”的可靠通信协议
传输前,采用三次握手方式建立连接,所以是可靠的
在连接中可进行大数据量的传输
连接、发送数据都需要确认,且 传输完毕后,还需要释放已建立的连接,通信效率较低。
TCP协议对使用在对信息安全要求较高的场景,例如,文件下载,金融等数据通信。
三次握手
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
第三次握手,客户端再次向服务器端发送确认信息,确认连接。
四次挥手
客户端向服务器 发出取消连接请求
服务器向客户端返回一个响应,表示收到客户端 取消请求
服务器向客户端发出确认取消信息
客户端再次发送确认消息,连接取消
UDP通信
DatagramPacket:数据包对象
public DatagramPacket(byte[ ] buf,int length,InetAddress address,int port)
创建发送端数据包对象
buf:要发送的内容,字节数组、length:要发送内容的字节长度、address:接收端的IP地址对象、port:接收端的端口号
public DatagramPacket(byte[ ] buf,int length)
创建接收端的数据包对象
buf:用来存储接收的内容,length:能够接收内容的长度
DatagramPacket常用方法
public int getLength()
获得实际接收到的字节个数
DatagramSocket:发送端和接收端对象
public DatagramSocket()
创建发送端socket对象,系统回随机分配一个端口号
public DatagramSocket(int port)
创建接收端的socket对象并指定端口号
DatagramSocket类成员方法
public void send(DatagramPacket do)
发送数据包
public void receicve(DatagramPacket p)
接收数据包
使用UDP通信实现:发送消息、接收消息
客户端:
public static void main(String[] args) throws Exception{
System.out.println("=======客户端启动=======");
//1、创建发送端对象:发送端自带默认的端口
DatagramSocket socket = new DatagramSocket(6666);
//2、创建一个数据包对象封装数据
/*public DatagramPacket(byte buf[],int length,InteAddress address,int port)
参数一:封装要发送的数据*
参数二:发送数据的大小
参数三:服务端的主机IP和地址
参数四:服务端的端口/
*/
byte[] buffert = "我是一颗快乐的星星".getBytes();
DatagramPacket packet = new DatagramPacket(buffert, buffert.length, InetAddress.getLocalHost(), 8888);
//3、发送数据
socket.send(packet);
socket.close();
}
服务端:
public static void main(String[] args) throws Exception {
System.out.println("====服务端启动====");
//1、创建接收端对象:注册端口
DatagramSocket socket = new DatagramSocket(8888);
//2、创建一个数据包对象接收数据
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
//3、等待接收数据
socket.receive(packet);
//4、取出数据即可,读取多少取出多少
int len = packet.getLength();
String rs = new String(buffer, 0, len);
System.out.println("收到了:" + rs);//收到了:我是一颗快乐的星星
//获取发送端的ip和端口
String ip = packet.getSocketAddress().toString();
System.out.println("对方地址:" + ip);//对方地址:/192.168.137.1:6666
int port = packet.getPort();
System.out.println("对方端口:" + port);//对方端口:6666
socket.close();
}
使用UDP通信实现:多发多收消息
发送端:多发、多收
public static void main(String[] args) throws Exception {
System.out.println("=====客户端启动======");
// 1、创建发送端对象:发送端自带默认的端口号(人)
DatagramSocket socket = new DatagramSocket(7777);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("离线成功!");
socket.close();
break;
}
// 2、创建一个数据包对象封装数据(韭菜盘子)
byte[] buffer = msg.getBytes();
DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
InetAddress.getLocalHost() , 8888);
// 3、发送数据出去
socket.send(packet);
}
}
接收端
public static void main(String[] args) throws Exception {
System.out.println("=====服务端启动======");
// 1、创建接收端对象:注册端口(人)
DatagramSocket socket = new DatagramSocket(8888);
// 2、创建一个数据包对象接收数据(韭菜盘子)
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
// 3、等待接收数据。
socket.receive(packet);
// 4、取出数据即可
// 读取多少倒出多少
int len = packet.getLength();
String rs = new String(buffer,0, len);
System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs);
}
}
UDP的三种通信方式
单播:单台主机与单台主机之间的通信
广播:当前主机与所在网络中的所有主机通信
组播:当前主机与选定的一组主机的通信
TCP通信的客户端
TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据
表示客户端的类:
java.net.Socke
t:此类实现客户端套接字(也可以叫“套接字”)套接字是两台机器间通信的端点
套接字:包含了IP地址和端口号的网络单位
构造方法:Socket(String host,int port)
创建一个流套接字并将其连接到指定主机的指定端口号
参数:String host:服务器主机的名称/服务器的IP地址
int port:服务器的端口号
成员方法:OutputStream getOutputStream()
返回此套接字的输出流
InputStream getInputStream()
返回此套接字的输入流
void close()
关闭此套接字
实现步骤:
1、创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
2、使用Socket对象的方法getOutputStream()获取网络字节输出流OutputStream对象
3、使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
4、使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
5、使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
6、释放资源
注意:
1、客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的对象
2、当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过3次握手建立连接通路
这是如果服务器没有启动,那么就会抛出异常
如果服务器已经启动,那么就可以进行交互了
客户端原版
public class TCPClient {
public static void main(String[] args) throws Exception {
//1、创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1",8888);
//2、使用Socket对象的方法getOutputStream()获取网络字节输出流OutputStream对象
OutputStream os= socket.getOutputStream();
//3、使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
os.write("你好服务器".getBytes(StandardCharsets.UTF_8));
//4、使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//5、使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
//6、释放资源
socket.close();
}
}
客户端改进版
public static void main(String[] args) throws Exception{
//1、创建Socket通信管道请求有服务端的连接
//参数一:服务端的IP地址,参数二:服务端的端口
Socket socket = new Socket("127.0.0.1",7777);
//2、从socket通信管道中得到一个字节输出流,负责发送数据
OutputStream os = socket.getOutputStream();
//3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
//4、发送消息
ps.println("我是TCP客户端,我已经与你对接");
ps.flush();
//关闭资源
//socket.clost();
}
TCP通信的服务器端
TCP通信的服务器端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据
表示服务器的类
java.net.ServerSocket:此类实现服务器套接字
构造方法:
ServerSocket(int port)
创建绑定到特定端口的服务器套接字
服务器端必须明确一件事,必须得知道是哪个客户端请求的服务器
所以可以使用accept方法获取到请求的客户端对象Socket
成员方法:Socket accept()侦听并接受到此套接字的连接
服务器实现步骤:
1、创建服务器ServerSocket对象和系统要指定的端口号
2、使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
3、使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
4、使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
5、使用Socket对象的方法getOutputStream()获取网络字节输出流OutputStream对象
6、使用网络字节输出流OutputStream对象中的方法writer,给客户端回写数据
7、释放资源(Socket,ServerSocket)
服务端原版
public class TCPServer {
public static void main(String[] args) throws IOException {
//1、创建服务器ServerSocket对象和系统要指定的端口号
ServerSocket server = new ServerSocket(8888);
//2、使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
Socket socket = server.accept();
//3、使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//4、使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
// 5、使用Socket对象的方法getOutputStream()获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
//6、使用网络字节输出流OutputStream对象中的方法writer,给客户端回写数据
os.write("收到谢谢".getBytes(StandardCharsets.UTF_8));
//7、释放资源(Socket,ServerSocket
socket.close();
server.close();
}
}
服务端改进
public static void main(String[] args) throws Exception {
System.out.println("===服务端启动成功===");
//1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
//2、必须调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
//3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
//4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//5、按照行读取消息
String msg;
if((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
}
}
TCP通信-多发多收消息
客户端
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
服务端
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
while (true) {
// 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
TCP通信-同时接受多个客户端消息
之前我们通信不可以同事与多个客户端通信,因为单线程每次只能处理一个客户端的Socket通信。因此我们需要引入多线程来处理多个客户端的通信需求。
客户端
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
服务端
主线程定义了循环负责接收客户端Socket管道连接
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 3、开始创建独立线程处理socket
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
使用多线程处理
没接到一个Socket通信管道后分配一个独立的线程负责处理它。
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
使用线程池优化
客户端
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 6666);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
服务端
服务端可以复用线程处理多个客户端,可以避免系统瘫痪
适合客户端通信时长较短的场景
// 使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(300,
1500, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2)
, Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(6666);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 任务对象负责读取消息。
Runnable target = new ServerReaderRunnable(socket);
pool.execute(target);
}
} catch (Exception e) {
e.printStackTrace();
}
}
引入线程池
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
即时通信
即时通信:是指一个客户端的消息发出去,其他客户端可以接收到
之前我们的消息都是发给服务端的。
即时通信需要进行端口转发的设计思想。
服务端需要把在线的Socket管道存储起来,一旦收到一个消息要推送给其他管道。
综合案例
文件上传原理
客户端
文件上传案例的客户端:读取本地文件,上传到服务器,读取服务器回写的数据
明确:数据源头:D:\java\ceshi\day01
目的地:服务器
实现步骤:1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
2、创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
3、使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象
4、使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
5、使用网络字节输出流OutputStream对象中的方法writer,把读取到的文件上传到服务器
6、使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象
7、使用网络字节输入流InputStream对象中的方法read读取服务器回写的数据
8、释放资源(FileInputStream,Socket)
public class TCPClient {
public static void main(String[] args) throws IOException {
//1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("D:\\java\\ceshi\\day01\\1.jpg");
// 2、创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1",8888);
//3、使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
// 4、使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
int len = 0;
byte[] bytes = new byte[1024];
while((len = fis.read(bytes))!=-1){
// 5、使用网络字节输出流OutputStream对象中的方法writer,把读取到的文件上传到服务器
os.write(bytes,0,len);
}
//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();
}
}
服务器端
文件上传案例服务器端:读取客户端上传的文件,保存到服务器的硬盘,给客户端回写“上传成功”
明确:数据源:客户端上传的文件
目的地:服务器的硬盘D:\java\ceshi\day01\aaa\upload\1.jpg
实现步骤:
1、创建一个服务器ServerSocket对象,和系统要指定的端口号
2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
4、判断D:\java\ceshi\day01\aaa\upload文件夹是否存在,不存在则创建
5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
6、使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
7、使用本地字节输出流FileOutputStream对象中的方法writer,把读取到的文件保存到服务器的硬盘上
8、使用Socket对象中的方法getOutputStream,获取到网络字节输出流outPutStream对象
9、使用网络字节输出流OutPutStream对象中的方法writer,给客户端回写“上传成功”
10、释放资源(FileOutputStream,Socket,ServerSocket)
public class TCPServer {
public static void main(String[] args) throws IOException {
//1、创建一个服务器ServerSocket对象,和系统要指定的端口号
ServerSocket server = new ServerSocket(8888);
//2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
Socket socket = server.accept();
// 3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
// 4、判断D:\java\ceshi\day01\aaa\upload文件夹是否存在,不存在则创建
File file = new File("D:\\java\\ceshi\\day01\\aaa\\upload");
if(!file.exists()){
file.mkdirs();
}
//5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
FileOutputStream fos = new FileOutputStream(file+"\\1.jpg");
//6、使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
int len = 0;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes))!=-1){
// 7、使用本地字节输出流FileOutputStream对象中的方法writer,把读取到的文件保存到服务器的硬盘上
fos.write(bytes,0,len);
}
//8、使用Socket对象中的方法getOutputStream,获取到网络字节输出流outPutStream对象
// 9、使用网络字节输出流OutPutStream对象中的方法writer,给客户端回写“上传成功”
socket.getOutputStream().write("上传成功".getBytes(StandardCharsets.UTF_8));
//10、释放资源(FileOutputStream,Socket,ServerSocket)
fos.close();
socket.close();
server.close();
}
}
阻塞问题
在read方法中输入流读取一个数据字节,如果没有输入可用,则此方法将阻塞。
解决阻塞状态
//解决因为阻塞状态
//上传完文件,给服务器写一个结束标记
//void shutdownOutput()禁用此套接字的输出流
//对于TCP套接字,任何以写入的数据都将被发送,并且后跟TCP的正常终止序列
socket.shutdownOutput();
文件上传优化
上传文件名称随机
上传过程中服务器不停止
使用多线程技术,提高程序的效率
例如:浏览器解析服务器回写html页面,页面中如果有图片,那么浏览器就会单独的开启一个线程,读取服务器的图片。我们就得让服务器一致处于监听状态,客户端请求一次,服务器就回写一次。
public class TCPServer {
public static void main(String[] args) throws IOException {
//1、创建一个服务器ServerSocket对象,和系统要指定的端口号
ServerSocket server = new ServerSocket(8888);
//让服务器一直处于监听状态(死循环accept方法)
//有一个客户端文件上传就保存一个文件
while(true) {
//2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
Socket socket = server.accept();
//使用多线程技术,提高程序的效率
//有一个客户端上传文件,就开启一个线程,完成文件的上传
new Thread(new Runnable() {
@Override
//完成文件的上传
public void run() {
try{
// 3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
File file = new File("D:\\java\\ceshi\\day01\\aaa\\upload");
if (!file.exists()) {
file.mkdirs();
}
//自定义一个文件的命名规则,防止同名的文件被覆盖
//规则:域名+毫秒值+随机数
String fileName = "zgDaren" + System.currentTimeMillis() + new Random().nextInt(999999) + ".jpg";
//5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
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对象中的方法writer,把读取到的文件保存到服务器的硬盘上
fos.write(bytes, 0, len);
}
//8、使用Socket对象中的方法getOutputStream,获取到网络字节输出流outPutStream对象
// 9、使用网络字节输出流OutPutStream对象中的方法writer,给客户端回写“上传成功”
socket.getOutputStream().write("上传成功".getBytes(StandardCharsets.UTF_8));
//10、释放资源(FileOutputStream,Socket,ServerSocket)
fos.close();
socket.close();
}catch(IOException e){
System.out.println(e);
}
}
}).start();
}
//既然服务器不停,就不用释放资源
//server.close();
}
}
模拟BS服务器案例
public class TCPServer {
public static void main(String[] args) throws IOException {
//1、创建一个服务器ServerSocket对象,和系统要指定的端口号
ServerSocket server = new ServerSocket(8888);
//2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
Socket socket = server.accept();
// 3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//4、使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
/* int len = 0;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes))!=-1){
// 5、使用本地字节输出流FileOutputStream对象中的方法writer,把读取到的文件保存到服务器的硬盘上
System.out.println(new String(bytes,0,len));
}*/
//把is网络字节输入流对象,转换为字符缓冲输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//把客户端请求信息的第一行读取出来
String line = br.readLine();
//把读取的信息进行切割,只要中间部分
String[] arr = line.split(" ");
//把路径前边的/去掉,进行截取
String htmlpath = arr[1].substring(1);
//创建一个本地的字节输入流,构造方法中绑定要读取的html路径
FileInputStream fis = new FileInputStream(htmlpath);
//使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
//写入HTTP协议响应头固定写法
os.write("HTTP/1.1 200 OK\r\n".getBytes());
os.write("Content-Type:txt/html\r\n".getBytes());
//必须写空行,否则浏览器不解析
os.write("\r\n".getBytes());
//一读一写复制文件,把服务器读取的html文件回写到客户端
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes))!=-1) {
os.write(bytes, 0, len);
}
//释放资源
fis.close();
socket.close();
server.close();
}
}