Java【网络编程】
文章目录
一、网络编程入门
1.1 软件结构
-
C/S结构:全称为Client/Server结构,指客户端和服务结构,常见程序有QQ、迅雷等软件。
-
B/S结构:全称为Browser/Server结构,是指浏览器和服务器的结构。常见的浏览器有谷歌、火狐等。
两种架构都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。
1.2 网络通信协议
-
网络通信协议:计算机连接和通信需要遵守的规则。
-
TCP/IP协议:传输控制协议/因特网互联协议(Transmission Control Protocol/Internet Protocol)。
采用了4层的分层模型,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层。
1.3 协议分类
java.net
包中包含的类和接口,用来专注于网络程序开发。
java.net
包中提供了两种常见的网络协议的支持:
- UDP:用户数据报协议(User Datagram Protocol),无连接通信协议,特点是数据被限制在64kb以内,超出这个范围就不能发送了。数据报:网络传输的基本单位。
- TCP:传输控制协议(Transmission Control Protocol),面向连接的通信协议,每次连接的创建都要经过**”三次握手“**。
1.4 网络编程三要素
协议:计算机实现网络通信必须遵守的规则
IP地址:互联网协议地址(Internet Protocol Address)
- IPv4:一个32位的二进制数,通常被分为4个字节,表示为
a.b.c.d
的形式。最多42亿个。 - IPv6:128位二进制数,每16个字节一组,分为八组十六进制数,号称可以为全世界每一粒沙子编一个地址,解决了网络地址资源紧张的问题。
端口号:端口号标识了一个主机上进行通信的不同的应用程序。常见的端口号:
- 网络端口:80
- 数据库:mysql3306 oracle1521
- Tomcat服务器:8080
二、TCP通信程序
2.1 概述
TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据。表示客户端的类:java.net.Socket
TCP通信的服务器端:接受客户端的请求,读取客户端发送的数据,给客户端回写数据。表示服务器的类:
java.net.ServerSocket
2.2 Socket类
java.net.Socket
:此类实现客户端套接字(也可以叫”套接字“),套接字是两台机器间通信的端点。
套接字:包含了IP地址和端口号的网络单位。
-
构造方法
Socket(String host, int port)
创建一个套接字并将其连接到指定主机上的指定端口号 -
成员方法
OutputStream getOutputStream()
返回此套接字的输出流
InputStream getInputStream()
返回此套接字的输入流
void close()
关闭此套接字
-
注意
1)客户端和服务端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
2)当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过3次握手建立连接通路。如果服务器没有启动,就会抛出异常。
2.3 SocketServer类
java.net.ServerSocket
:此类实现服务器套接字。
-
构造方法
ServerSocket(int port)
创建绑定到特点端口的服务器套接字 -
成员方法
Socket accept()
监听并接受到此套接字的连接
2.3 简单的TCP网络程序
TCPClient
public class TCPClient {
public static void main(String[] args) throws IOException {
//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());
//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)
socket.close();
}
}
TCPServer
public class TCPServer {
public static void main(String[] args) throws IOException {
//1、创建服务器ServerSocket对象和系统要指定的端口号
ServerSocket ss = new ServerSocket(8888);
//2、使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
Socket socket = ss.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对象中的方法write(),给客户端回写数据
os.write("你好客户端,服务器收到".getBytes());
//7、释放资源(Socket,ServerSocket)
ss.close();
}
}
三、综合案例
3.1 文件上传案例
- 分析
明确数据源和目的地: 数据源:/Users/yanzhuang/Desktop/3.jpg 目的地:服务器
【客户端】输入流,从硬盘读取数据到程序中。
【客户端】输出流,写出文件数据到服务端。
【服务端】输入流,读取文件数据到服务端程序。
【服务端】输出流,写出文件数据到服务器硬盘中。
TCPClient
public class TCPClient {
public static void main(String[] args) throws IOException {
//1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("/Users/yanzhuang/Desktop/3.jpg");
//2、创建一个客户端Socket对象,绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1",8888);
//3、使用Socket中的方法getOutputStream()获取网络字节输出流对象
OutputStream os = socket.getOutputStream();
//4、使用本地字节输入流对象中的read(),读取本地文件
byte[] bytes = new byte[1024];
int len = 0;
while((len = fis.read(bytes)) != -1) {
//5、使用网络字节输出流对象中的write(),把读取的文件上传到服务器
// 结束标记不会传到服务器中,服务器会陷入死循环
os.write(bytes);
}
/*
public void shutdownOutput() throws IOException
使用socket的shutdownOutput方法结束输入流,将结束标记发送给服务器
*/
socket.shutdownOutput();
//6、使用Socket中的方法getInputStream,获取网络字节输入流对象
InputStream is = socket.getInputStream();
//7、使用网络字节输入流对象中的read()读取服务器回写的数据
while ((len = is.read(bytes)) != -1){
System.out.println(new String(bytes,0,len));
}
//8、释放资源
fis.close();
socket.close();
}
}
TCPServer
public class TCPServer {
public static void main(String[] args) throws IOException {
//1、创建一个服务器SocketServer对象,和系统要制定的端口号
ServerSocket ss = new ServerSocket(8888);
//2、使用SocketServer对象中的accept方法,获取请求的客户端socket对象
Socket socket = ss.accept();
//3、使用Socket对象的getInputStream(),获取网络字节输入流对象
InputStream is = socket.getInputStream();
//4、判断/Users/yanzhang/Desktop/upload是否存在,不存在即创建目录
File file = new File("/Users/yanzhuang/Desktop/upload");
if(!file.exists()){
file.mkdirs();
}
//5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定需要输出的目的地
FileOutputStream fos = new FileOutputStream(file+"/a.jpg");
//6、使用网络字节输入流对象的read()方法,读取客户端上传的文件
int len = 0;
byte[] bytes = new byte[1024];
while((len = is.read(bytes)) != -1) {
System.out.println(new String(bytes,0,len));
//7、使用本地字节输出流对象的write()方法,把读到的文件保存到服务器的硬盘上
fos.write(bytes,0,len);
}
//8、使用Socket对象的getOutputStream(),获取网络字节输出流对象
OutputStream os = socket.getOutputStream();
//9、使用网络字节输出流对象的write()方法,回写给客户端"上传成功"
os.write("上传成功".getBytes());
//10、释放资源
fos.close();
socket.close();
ss.close();
}
}
- 服务器程序的优化
自定义一个命名规则:放置同名的文件被覆盖
使服务器始终处于监听状态:有一个客户端上传文件,就保存一个文件
多线程提高效率:使用多线程处理多个客户端套接字的请求
优化后服务器端代码:
public class TCPServer {
public static void main(String[] args) throws IOException {
//1、创建一个服务器SocketServer对象,和系统要制定的端口号
ServerSocket ss = new ServerSocket(8888);
/*
优化2:
使用死循环让服务器一直处于监听状态,有一个客户端上传文件,就保存一个文件
*/
while(true) {
Socket socket = ss.accept();
/*
优化3:
进一步提高效率,使用多线程处理多个客户端套接字的请求
*/
new Thread(new Runnable() {
@Override
public void run() {
// 提升变量的作用域
FileOutputStream fos = null;
try{
//3、使用Socket对象的getInputStream(),获取网络字节输入流对象
InputStream is = socket.getInputStream();
//4、判断/Users/yanzhang/Desktop/upload是否存在,不存在即创建目录
File file = new File("/Users/yanzhuang/Desktop/upload");
if(!file.exists()){
file.mkdirs();
}
/*
优化1:
自定义一个命名规则,防止同名的文件被覆盖
规则:域名 + 毫秒值 + 随机数
*/
String fileName = "zx" + System.currentTimeMillis() + new Random().nextInt(999999) + ".jpg";
//5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定需要输出的目的地
fos = new FileOutputStream(file+"/" + fileName);
//6、使用网络字节输入流对象的read()方法,读取客户端上传的文件
int len = 0;
byte[] bytes = new byte[1024];
while((len = is.read(bytes)) != -1) {
//7、使用本地字节输出流对象的write()方法,把读到的文件保存到服务器的硬盘上
fos.write(bytes,0,len);
}
//8、使用Socket对象的getOutputStream(),获取网络字节输出流对象
OutputStream os = socket.getOutputStream();
//9、使用网络字节输出流对象的write()方法,回写给客户端"上传成功"
os.write("上传成功".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//10、释放资源
try {
fos.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
// 服务器需要一直处于启动状态,进行监听
//ss.close();
}
}
3.2 模拟B/S服务器
浏览器服务器交互方式的服务器程序:
public class TCPServer {
public static void main(String[] args) throws IOException {
//创建一个服务器SocketServer对象,和系统要制定的端口号
ServerSocket ss = new ServerSocket(8080);
/*
浏览器解析服务器回写的html页面,如果页面中有图片,那么浏览器就会单独的开一个线程,读取服务器的图片
我们就让服务器一直处在监听状态,客户端请求一次,服务器就回写一次
*/
while (true) {
new Thread(new Runnable() {
@Override
public void run() {
try{
//使用SocketServer对象中的accept方法,获取请求的客户端socket对象(浏览器)
Socket socket = ss.accept();
//使用Socket对象的getInputStream(),获取网络字节输入流对象
InputStream is = socket.getInputStream();
//把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 os = socket.getOutputStream();
//写入HTTP协议的响应头,固定写法
os.write("HTTP/1.1 200 OK\r\n".getBytes());
os.write("Content-Type:text/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();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
注意:浏览器解析服务器回写的html
页面,如果页面中有图片,那么浏览器就会单独的开一个线程,读取服务器的图片,我们可以使用循环让服务器一直处在监听状态,客户端请求一次,服务器就回写一次。