1、网络编程
1.1 软件结构和常用协议
软件结构:
C/S:通过客户端访问服务器
B/S:通过浏览器访问服务器
无论哪种结构,都离不开网络
网络编程:在一定协议下,实现两台计算机的通信的程序。
网络通信协议:位于同一网络的计算机进行连接和通信时需要遵守一定的协议,对数据的传输格式、速率、步骤等进行了统一规定,双方遵守该协议才可以实现数据交换。
常用协议:
TCP/IP协议:
传输控制协议/因特网互联协议
是因特网最基本、最广泛的协议
定义了计算机如何连入因特网,数据如何在它们之间传输的标准
分为数据链路层、网络层、传输层和应用层
数据链路层:用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对网线、光纤提供的驱动。
网络层:TCP/IP协议的核心,主要用于对传输的数据进行分组,将分组的数据发送到目标计算机或网络。
传输层:主要使网络程序进行通信,在进行网络通信时,可以使用TCP协议或UDP协议。
应用层:主要负责应用程序的协议。例如HTTP协议、FTP协议等。
1.2 网络通信协议分类
UDP 用户数据报协议
面向无连接(传输数据时发送端和接收端不建立逻辑连接)
不保证数据的完整性 不能用于传输重要数据
消耗资源少 通信效率高
通常用于音频、视频和普通数据的传输
数据限制在64KB以内 超出不能一次发送
TCP 传输控制协议
面向连接(传输数据之前发送端和接收端建立逻辑连接之后再传输数据)
可靠无差错的数据传输
在TCP中要明确客户端和服务器端,由客户端向服务器端发起请求,每次建立连接都要经过三次握手
三次握手:在TCP协议中,在发送数据的准备阶段,客户端与服务器端的三次交互,保证连接的可靠
第一次:客户端向服务器端发送连接请求,等待服务器端确认
第二次:服务器端向客户端回送一个响应,通知客户端收到了连接请求
第三次:客户端再次向服务器端发送确认信息,确认连接
可以保证数据传输的安全性,应用广泛,如下载文件、浏览网页等
1.3 网络编程三要素
协议、IP地址、端口号
协议:
计算机网络通信必须遵守的规则
IP地址:
互联网协议地址。给一个网络中的每一台计算机的唯一编号。
IPv4:4个字节,32位的二进制数。表示为a.b.c.d,如192.168.65.100。其中abcd都是0-255之间的十进制数,最多可以表示42亿个(2的32次方)。
IPv6:16个字节,128位的二进制数。
常用命令:ipconfig 查看ip设置
ping IP地址 查看是否可以与该IP地址进行通信
本机IP地址:127.0.0.1
本地IP地址的域名:localhost
端口号:
是一个逻辑端口,无法直接看到,可以通过一些软件查看端口号
使用网络软件时,一打开该软件,OS就会为其分配一个随机端口号或一打开软件就向OS要指定的端口号
2个字节,取值在0-65535之间。1024之前不能使用,已被系统分配给已知软件。端口号不能重复。
常用端口号:网络端口 80 www.baidu.com:80
数据库端口 MySQL 3306 Oracle 1521
Tomcat服务器 8080
使用IP地址+端口号,就可以保证数据准确无误地发送到对方计算机的指定软件上。
1.4 TCP通信程序
使用TCP进行通信的Java程序
TCP通信能实现两台计算机之间的数据交互,通信两端严格区分为客户端和服务器端。
通信步骤:
(1)服务器端先启动,等待客户端的连接
(2)客户端主动连接服务器端,连接成功才能通信。服务器端不能主动连接客户端。
(3)客户端和服务器端建立逻辑连接。连接中包含一个IO对象,因为通信数据不仅是字符,所以是字节流对象。
(4)客户端和服务器端使用这个IO对象进行通信。
客户端
IP地址:端口号 Socket类
服务器端
IP地址:端口号 ServerSocket类
客户端和服务器端进行一次数据交互,需要4个IO流对象。客户端发送OutputStream,服务器接收InputStream,服务器回送OutputStream,客户端接收InputStream。
服务端需要明确两件事:
(1)与哪个客户端进行的交互。服务器端有一个accept方法可以获取到请求的客户端对象。Socket s1 = server.accept();
(2)交互需要多个IO流对象,服务器端没有IO流,服务器可以获取请求的客户端对象,使用每个客户端提供的IO流与每个客户端进行交互。
服务器使用客户端的字节输入流读取客户端发送的数据。
服务器使用客户端的字节输出流给客户端回写数据。
1.5 示例
TCP通信的客户端
向服务器端发送连接请求,发送数据,读取服务器端回写的数据
使用java.net包里的Socket类,实现客户端套接字(套接字就是包含了IP地址和端口号的网络单位)
构造方法:Socket(String host, int port);host服务器的主机名称/IP地址 port服务器的端口号
成员方法:OutputStream getOutputStream();返回此套接字的输出流
InputStream getInputStream();返回此套接字的输入流
close();关闭此套接字
使用步骤:
(1)创建Socket对象,绑定服务器的IP地址和端口号
(2)调用Socket对象的getOutputStream方法,获取网络字节输出流对象
(3)调用OutputStream对象的write方法,给服务器发送数据
(4)调用Socket对象的getInputStream方法,获取网络字节输入流对象
(5)调用InputStream对象的read方法,读取服务器回写的数据
(6)调用Socket对象的close方法,关闭此客户端,释放资源
注意:
(1)客户端和服务器端进行交互,必须使用Socket对象提供的流对象,不能使用自己创建的流对象
(2)创建Socket对象时就会向服务器发起请求经过三次握手建立连接,若此时服务器没有启动就会抛出异常
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 os = socket.getOutputStream();
//3、调用OutputStream对象的write方法,给服务器发送数据
os.write(“我是客户端”.getBytes());
//4、调用Socket对象的getInputStream方法,获取网络字节输入流对象
InputStream is = socket.getInputStream();
int len = 0;
byte[] bytes = new byte[1024];
len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
//5、调用InputStream对象的read方法,读取服务器回写的数据
// while ((len = is.read(bytes)) != -1) {
// System.out.println(new String(bytes, 0, len));
// }
//6、调用Socket对象的close方法,关闭此客户端,释放资源
socket.close();
}
}
TCP通信的服务器端
接收客户端请求,读取客户端发送的数据,回写数据给客户端
使用java.net包里的ServerSocket类,实现服务器端套接字(套接字就是包含了IP地址和端口号的网络单位)
构造方法:ServerSocket(int port);因为客户端创建对象时绑定了服务器端指定的端口号,所以这里创建服务器对象时也要向系统请求指定的端口号,否则客户端会找不到该服务器
成员方法:Socket accept();监听发送请求的客户端对象并连接
实现步骤:
(1)创建ServerSocket对象,向系统请求指定的端口号
(2)调用ServerSocket对象的accept方法,获取到请求的客户端对象Socket
(3)调用Socket对象的getInputStream方法,获取网络字节输入流对象
(4)调用InputStream对象的read方法,读取客户端发送的数据
(5)调用Socket对象的getOutputStream方法,获取网络字节输出流对象
(6)调用OutputStream对象的write方法,给客户端回写数据
(7)调用Socket对象和ServerSocket对象的close方法,关闭此客户端,释放资源
public class TCPServer {
public static void main(String[] args) throws IOException {
//1、创建ServerSocket对象,向系统请求指定的端口号
ServerSocket serverSocket = new ServerSocket(8888);
//2、调用ServerSocket对象的accept方法,获取到请求的客户端对象Socket
Socket socket = serverSocket.accept();
//3、调用Socket对象的getInputStream方法,获取网络字节输入流对象
InputStream is = socket.getInputStream();
int len = 0;
byte[] bytes = new byte[1024];
len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
//4、调用InputStream对象的read方法,读取客户端发送的数据
// while ((len = is.read(bytes)) != -1) {
// System.out.println(len);
// System.out.println(new String(bytes, 0, len));
// }
//5、调用Socket对象的getOutputStream方法,获取网络字节输出流对象
OutputStream os = socket.getOutputStream();
//6、调用OutputStream对象的write方法,给客户端回写数据
os.write(“我是服务器”.getBytes());
//7、调用Socket对象和ServerSocket对象的close方法,关闭此客户端,释放资源
socket.close();
serverSocket.close();
}
}
2、文件上传案例
TCP通信的文件上传案例
客户端读取本地文件,上传到服务器,服务器将上传的文件存到服务器的硬盘
步骤:
(1)客户端使用本地的字节输入流,读取要上传的文件
(2)客户端使用网络字节输出流,将读取到的文件上传到服务器
(3)服务器使用网络字节输入流,读取上传的文件
(4)服务器使用本地的字节输出流,将文件存入服务器的硬盘
(5)服务器使用网络字节输出流,给客户端回写“上传成功”
(6)客户端使用网络字节输入流读取服务器回写的“上传成功”
(7)关闭本地流对象,释放资源
(8)关闭客户端和服务器,释放资源
注意:客户端或服务器对本地硬盘进行读写,使用自己创建的字节流对象(本地流)
客户端和服务器之间进行交互,使用Socket提供的网络字节流对象
文件上传客户端
(1)客户端使用本地的字节输入流,读取要上传的文件
(2)客户端使用网络字节输出流,将读取到的文件上传到服务器
(6)客户端使用网络字节输入流读取服务器回写的“上传成功”
(7)关闭本地流对象,释放资源
(8)关闭客户端和服务器,释放资源
public class TCPClient {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(“H:\图片\壁纸.jpeg”);
Socket socket = new Socket(“127.0.0.1”, 8888);
OutputStream os = socket.getOutputStream();
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1) {
//读取到-1就跳出了循环,不会将-1写入文件,所以服务器读取不到-1,服务器的循环会阻塞,我们需要在文件的内容写完之后手动写入一个结束标记
os.write(bytes, 0, len);
}
socket.shutdownOutput();
InputStream is = socket.getInputStream();
while ((len = is.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, len));
}
fis.close();
socket.close();
}
}
文件上传服务器端
(3)服务器使用网络字节输入流,读取上传的文件
(4)服务器使用本地的字节输出流,将文件存入服务器的硬盘
(5)服务器使用网络字节输出流,给客户端回写“上传成功”
(7)关闭本地流对象,释放资源
(8)关闭客户端和服务器,释放资源
改进:
(1)使用毫秒值对文件名命名,防止文件重名被覆盖
(2)使用死循环让服务器持续监听客户端请求
(3)使用多线程提高效率 一个客户端上传文件就开启一个线程
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
while (true) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Socket socket = server.accept();
File file = new File(“G:\有的没的\upload”);
String filename = “壁纸” + System.currentTimeMillis() + “.jpeg”;
if (!file.exists()) {
file.mkdir();
}
FileOutputStream fos = new FileOutputStream(file + “\” + filename);
InputStream is = socket.getInputStream();
int len = 0;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
OutputStream os = socket.getOutputStream();
os.write(“上传成功”.getBytes());
fos.close();
socket.close();
}catch (IOException e) {
System.out.println(e);
}
}
}).start();
}
//server.close();
}
}
3、B/S架构
模拟B/S服务器
浏览器解析服务器回写的html页面,如果页面中含有图片,浏览器就会单独地开启一个线程,读取服务器的图片
让服务器保持监听,浏览器请求一次,服务器回写一次,一张图片一个线程
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8080);
Socket socket = server.accept();
InputStream is = socket.getInputStream();
//把网络字节输入流对象is转换为缓冲字符输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//读取一行
String getText = br.readLine();
//将字符串按空格分割为字符串数组
String[] getTextArr = getText.split(" “);
//字符串数组的索引1是html文件的相对地址
String htmlAddress = getTextArr[1];
System.out.println(htmlAddress);
//将最前面的/去掉
htmlAddress = htmlAddress.substring(1);
System.out.println(htmlAddress);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(htmlAddress);
int len = 0;
byte[] bytes = new byte[1024];
//写入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());
while ((len = fis.read(bytes)) != -1) {
os.write(bytes, 0, len);
}
// int len = 0;
// byte[] bytes = new byte[1024];
// while ((len = is.read(bytes)) != -1) {
// System.out.println(new String(bytes, 0, len));
// }
fis.close();
socket.close();
server.close();
/*
启动服务器后使用浏览器访问http://127.0.0.1:8080/day09-code/web/index.html
下面是服务器读取的客户端的请求信息:
/
//GET /day09-code/web/index.html HTTP/1.1
//Host: 127.0.0.1:8080
//Connection: keep-alive
//Upgrade-Insecure-Requests: 1
//User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
//Sec-Fetch-User: ?1
//Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3
//Sec-Fetch-Site: none
//Sec-Fetch-Mode: navigate
//Accept-Encoding: gzip, deflate, br
//Accept-Language: zh-CN,zh;q=0.9
/
服务器要给客户端回写一个html页面
读取index.html页面文件,文件地址就是上面请求信息的第一行
*/
}
}
服务器线程
public class TCPServerThread {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8080);
while (true) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Socket socket = server.accept();
InputStream is = socket.getInputStream();
//把网络字节输入流对象is转换为缓冲字符输入流
BufferedReader br = new BufferedReader(
new InputStreamReader(is));
//读取一行
String getText = br.readLine();
//将字符串按空格分割为字符串数组
String[] getTextArr = getText.split(" “);
//字符串数组的索引1是html文件的相对地址
String htmlAddress = getTextArr[1];
System.out.println(htmlAddress);
//将最前面的/去掉
htmlAddress = htmlAddress.substring(1);
System.out.println(htmlAddress);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(htmlAddress);
int len = 0;
byte[] bytes = new byte[1024];
//写入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());
while ((len = fis.read(bytes)) != -1) {
os.write(bytes, 0, len);
}
fis.close();
socket.close();
} catch (IOException e) {
System.out.println(e);
}
}
}).start();
}
//server.close();
}
}