外围概述:在互联网时代,咱们所利用的大部分软件都是基于网络为根底的,那么软件之间是如何通过网络通信的呢?为什么咱们通过浏览器输出网址就能够看到网页?本篇咱们将会学习网络编程,让咱们可能从编程角度更底层的更好的了解软件之间的通信流程。
第一章:网络编程根底
1.1- 软件结构(理解)
C/ S 构造
C/ S 构造:全称为 Client/Server 构造,是指客户端和服务器构造。常见程序有QQ、迅雷等软件。
B/ S 构造
B/ S 构造:全称为 Browser/Server 构造,是指浏览器和服务器构造。常见浏览器有谷歌、火狐等。
两种架构各有劣势,然而无论哪种架构,都离不开网络的反对。网络编程,就是在肯定的协定下,实现两台计算机的通信的程序。
1.2- 网络通信协定(理解)
网络通信协定:通信协议是对计算机必须恪守的规定,只有恪守这些规定,计算机之间能力进行通信。这就 好比在路线中行驶的汽车肯定要恪守交通规则一样,协定中对数据的传输格局、传输速率、传输步骤等做了对立规定,通信单方必须同时恪守,最终实现数据交换。
TCP/IP 协定: 传输控制协议 / 因特网互联协定(Transmission Control Protocol/Internet Protocol),是 Internet 最根本、最宽泛的协定。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的规范。它的外部蕴含一系列的用于解决数据通信的协定,并采纳了 4 层的分层模型,每一层都呼叫它的下一层所提供的协定来实现本人的需要。
1.3- 协定分类(理解)
通信的协定还是比较复杂的,java.net 包中蕴含的类和接口,它们提供低层次的通信细节。咱们能够间接应用这些类和接口,来专一于网络程序开发,而不必思考通信的细节。
TCP
TCP:传输控制协议 (Transmission Control Protocol)。TCP 协定是面向 连贯
的通信协议,即传输数据之前,在发送端和接收端建设逻辑连贯,而后再传输数据,它提供了两台计算机之间牢靠无差错的数据传输。
三次握手:TCP 协定中,在发送数据的筹备阶段,客户端与服务器之间的三次交互,以保障连贯的牢靠。
- 第一次握手,客户端向服务器端收回连贯申请,期待服务器确认。
- 第二次握手,服务器端向客户端回送一个响应,告诉客户端收到了连贯申请。
- 第三次握手,客户端再次向服务器端发送确认信息,确认连贯。整个交互过程如下图所示。
实现三次握手,连贯建设后,客户端和服务器就能够开始进行数据传输了。因为这种面向连贯的个性,TCP 协定能够保障传输数据的平安,所以利用非常宽泛,例如下载文件、浏览网页等。
UDP
用户数据报协定 (User Datagram Protocol)。UDP 协定是一个面向 无连贯
的协定。传输数据时,不须要建设连贯,不论对方端服务是否启动,间接将数据、数据源和目的地都封装在数据包中,间接发送。每个数据包的大小限度在 64k 以内。它是不牢靠协定,因为无连贯,所以传输速度快,然而容易失落数据。日常利用中, 例如视频会议、QQ 聊天等。
1.4- 网络编程三要素(理解)
协定
协定:计算机网络通信必须恪守的规定,曾经介绍过了,不再赘述。
IP 地址
什么是 IP 地址:指互联网协议地址(Internet Protocol Address),俗称 IP。IP 地址用来给一个网络中的计算机设备做惟一的编号。如果咱们把“个人电脑”比作“一台电话”的话,那么“IP 地址”就相当于“电话号码”。
IP 地址的分类:
- IPv4:是一个 32 位的二进制数,通常被分为 4 个字节,示意成 a.b.c.d 的模式,例如 192.168.65.100。其中 a、b、c、d 都是 0~255 之间的十进制整数,那么最多能够示意 42 亿个。
- IPv6:因为互联网的蓬勃发展,IP 地址的需求量愈来愈大,然而网络地址资源无限,使得 IP 的调配越发缓和。有材料显示,寰球 IPv4 地址在 2011 年 2 月调配结束。为了扩充地址空间,拟通过 IPv6 从新定义地址空间,采纳 128 位地址长度,每 16 个字节一组,分成 8 组十六进 制数,示意成 ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称能够为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。
常用命令
查看本机 IP 地址,在控制台输出:ipconfig
查看网络是否连通,在控制台输出:
ping 空格 IP 地址
ping 220.181.57.216【非凡的 IP 地址】本机 IP 地址:127.0.0.1、localhost。
端口号
网络的通信,实质上是两个过程(应用程序)的通信。每台计算机都有很多的过程,那么在网络通信时,如何辨别这些过程呢?
如果说 IP 地址能够惟一标识网络中的设施,那么端口号就能够惟一标识设施中的过程(应用程序)了。
端口号:用两个字节示意的整数,它的取值范畴是 0 -65535。其中,0~1023 之间的端口号用于一些出名的网络服务和利用,一般的应用程序须要应用 1024 以上的端口号。如果端口号被另外一个服务或利用所占用,会导致以后程序启动失败。
利用 协定 + IP 地址 + 端口号 三元组合,就能够标识网络中的过程了,那么过程间的通信就能够利用这个标识与其它过程进行交互。
第二章:TCP 通信程序
2.1-TCP 通信程序介绍(理解)
TCP 通信能实现两台计算机之间的数据交互,通信的两端,要严格辨别为 客户端(Client)与 服务端(Server)。
两端通信时步骤:
- 服务端程序,须要当时启动,期待客户端的连贯。
- 客户端被动连贯服务器端,连贯胜利能力通信。服务端不能够被动连贯客户端。
在 Java 中,提供了两个类用于实现 TCP 通信程序:
- 客户端:
java.net.Socket
类示意。创立Socket
对象,向服务端收回连贯申请,服务端响应申请,两者建设连贯开始通信。 - 服务端:
java.net.ServerSocket
类示意。创立ServerSocket
对象,相当于开启一个服务,并期待客户端的连贯。
2.2-Socket 类(重要)
Socket 类:该类实现客户端套接字,套接字指的是两台设施之间通信的端点。
构造方法
public Socket(String host, int port)
: 创立套接字对象并将其连贯到指定主机上的指定端口号。如果指定的 host 是 null,则相当于指定地址为回送地址。
回送地址(127.x.x.x) 是本机回送地址(Loopback Address),次要用于网络软件测试以及本地机过程间通信,无论什么程序,一旦应用回送地址发送数据,立刻返回,不进行任何网络传输。
Socket client = new Socket("127.0.0.1", 6666);
成员办法
-
public InputStream getInputStream()
:返回此套接字的输出流。- 如果此 Scoket 具备相关联的通道,则生成的 InputStream 的所有操作也关联该通道。
- 敞开生成的 InputStream 也将敞开相干的 Socket。
-
public OutputStream getOutputStream()
:返回此套接字的输入流。- 如果此 Scoket 具备相关联的通道,则生成的 OutputStream 的所有操作也关联该通道。
- 敞开生成的 OutputStream 也将敞开相干的 Socket。
-
public void close()
:敞开此套接字。- 一旦一个 socket 被敞开,它不可再应用。
- 敞开此 socket 也将敞开相干的 InputStream 和 OutputStream。
-
public void shutdownOutput()
:禁用此套接字的输入流。- 任何先前写出的数据将被发送,随后终止输入流
2.3-ServerSocket 类(重要)
ServerSocket 类:这个类实现了服务器套接字,该对象期待通过网络的申请。
构造方法
构造方法:public ServerSocket(int port)
:应用该构造方法在创立 ServerSocket 对象时,就能够将其绑定到一个指 定的端口号上,参数 port 就是端口号。
ServerSocket server = new ServerSocket(6666);
成员办法
成员办法:public Socket accept()
:侦听并承受连贯,返回一个新的 Socket 对象,用于和客户端实现通信。该办法 会始终阻塞直到建设连贯。
2.4- 简略的 TCP 网络程序(重要)
TCP 通信剖析
- 【服务端】启动, 创立 ServerSocket 对象,期待连贯。
- 【客户端】启动, 创立 Socket 对象,申请连贯。
- 【服务端】接管连贯, 调用 accept 办法,并返回一个 Socket 对象。
- 【客户端】Socket 对象,获取 OutputStream,向服务端写出数据。
- 【服务端】Scoket 对象,获取 InputStream,读取客户端发送的数据。
- 【服务端】Socket 对象,获取 OutputStream,向客户端回写数据。
- 【客户端】Scoket 对象,获取 InputStream,解析回写数据。
- 【客户端】开释资源,断开连接
客户端向服务端发送数据
客户端代码
public class ClientTCP {public static void main(String[] args) throws Exception {System.out.println("客户端 发送数据");
// 1. 创立 Socket (ip , port) , 确定连贯到哪里.
Socket client = new Socket("localhost", 6666);
// 2. 获取流对象 . 输入流
OutputStream os = client.getOutputStream();
// 3. 写出数据.
os.write("你好么? tcp , 我来了".getBytes());
// 4. 敞开资源 .
os.close();
client.close();}
}
服务端代码
public class ServerTCP {public static void main(String[] args) throws IOException {System.out.println("服务端启动 , 期待连贯 ....");
// 1. 创立 ServerSocket 对象,绑定端口,开始期待连贯
ServerSocket ss = new ServerSocket(6666);
// 2. 接管连贯 accept 办法, 返回 socket 对象.
Socket server = ss.accept();
// 3. 通过 socket 获取输出流
InputStream is = server.getInputStream();
// 4. 一次性读取数据
// 4.1 创立字节数组
byte[] b = new byte[1024];
// 4.2 据读取到字节数组中.
int len = is.read(b);// 4.3 解析数组, 打印字符串信息
String msg = new String(b, 0, len);
System.out.println(msg);
//5. 敞开资源.
is.close();
server.close();}
}
服务端向客户端回写数据
服务端代码
public class ClientTCP {public static void main(String[] args) throws Exception {System.out.println("客户端 发送数据");
// 1. 创立 Socket (ip , port) , 确定连贯到哪里.
Socket client = new Socket("localhost", 6666);
// 2. 通过 Scoket, 获取输入流对象
OutputStream os = client.getOutputStream();
// 3. 写出数据.
os.write("你好么? tcp , 我来了".getBytes());
// ============== 解析回写 =========================
// 4. 通过 Scoket, 获取 输出流对象
InputStream in = client.getInputStream();
// 5. 读取数据数据
byte[] b = new byte[100];
int len = in.read(b);
System.out.println(new String(b, 0, len));
// 6. 敞开资源 .
in.close();
os.close();
client.close();}
}
客户端代码
public class ServerTCP {public static void main(String[] args) throws IOException {System.out.println("服务端启动 , 期待连贯 ....");
// 1. 创立 ServerSocket 对象,绑定端口,开始期待连贯
ServerSocket ss = new ServerSocket(6666);
// 2. 接管连贯 accept 办法, 返回 socket 对象.
Socket server = ss.accept();
// 3. 通过 socket 获取输出流
InputStream is = server.getInputStream();
// 4. 一次性读取数据
// 4.1 创立字节数组
byte[] b = new byte[1024];
// 4.2 据读取到字节数组中.
int len = is.read(b);// 4.3 解析数组, 打印字符串信息
String msg = new String(b, 0, len);
System.out.println(msg);
// ================= 回写数据 =======================
// 5. 通过 socket 获取输入流
OutputStream out = server.getOutputStream();
// 6. 回写数据
out.write("我很好, 谢谢你".getBytes());
// 7. 敞开资源.
out.close();
is.close();
server.close();}
}
第三章:文件上传案例
3.1- 文件上传剖析
- 【客户端】输出流,从硬盘读取文件数据到程序中。
- 【客户端】输入流,写出文件数据到服务端。
- 【服务端】输出流,读取文件数据到服务端程序。
- 【服务端】输入流,写出文件数据到服务器硬盘中。
- 【服务端】获取输入流,回写数据。
- 【客户端】获取输出流,解析回写数据。
3.2- 客户端程序实现
public static void main(String[] args) throws IOException {
// 1. 创立流对象
// 1.1 创立输出流, 读取本地文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
// 1.2 创立输入流, 写到服务端
Socket socket = new Socket("localhost", 6666);
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//2. 写出数据.
byte[] b = new byte[1024 * 8];
int len ;
while ((len = bis.read(b))!=-1) {bos.write(b, 0, len);
}
// 敞开输入流, 告诉服务端, 写出数据结束
socket.shutdownOutput();
System.out.println("文件发送结束");
// 3. ===== 解析回写 ============
InputStream in = socket.getInputStream();
byte[] back = new byte[20];
in.read(back);
System.out.println(new String(back));
in.close();
// 4. 开释资源
socket.close();
bis.close();}
3.3- 文件上传单线程服务器实现
public static void main(String[] args) throws IOException {System.out.println("服务器 启动.....");
// 1. 创立服务端 ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
// 2. 循环接管, 建设连贯
Socket accept = serverSocket.accept();
/*
*3. socket 对象进行读写操作
*/
try {
//3.1 获取输出流对象
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//3.2 创立输入流对象, 保留到本地 .
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
BufferedOutputStream bos = new BufferedOutputStream(fis);
// 3.3 读写数据
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {bos.write(b, 0, len);
}
// 4.======= 信息回写 ===========================
System.out.println("back ........");
OutputStream out = accept.getOutputStream();
out.write("上传胜利".getBytes());
out.close();
//================================
//5. 敞开 资源
bos.close();
bis.close();
accept.close();
System.out.println("文件上传已保留");
} catch (IOException e) {e.printStackTrace();
}
}
}
3.4- 文件上传多线程服务器实现
文件上传的案例中,服务器只能为客户端服务器一次,之后服务器端程序就会完结。而咱们必须做到让服务器程序不能完结,时时刻刻都要为客户端服务。而且同时能够为多个客户端提供服务器,做到一个客户端就要开启一个信新的线程。
public static void main(String[] args) throws IOException{System.out.println("服务器 启动.....");
// 1. 创立服务端 ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
// 2. 循环接管, 建设连贯
while (true) {Socket accept = serverSocket.accept();
/*
3. socket 对象交给子线程解决, 进行读写操作
Runnable 接口中, 只有一个 run 办法, 应用 lambda 表达式简化格局
*/
new Thread(() -> {
try{
//3.1 获取输出流对象
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//3.2 创立输入流对象, 保留到本地 .
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
BufferedOutputStream bos = new BufferedOutputStream(fis);
// 3.3 读写数据
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {bos.write(b, 0, len);
}
// 4.======= 信息回写 ===========================
System.out.println("back ........");
OutputStream out = accept.getOutputStream();
out.write("上传胜利".getBytes());
out.close();
//================================
//5. 敞开 资源
bos.close();
bis.close();
accept.close();
System.out.println("文件上传已保留");
} catch (IOException e) {e.printStackTrace();
}
}).start();}
}
3.5- 文件上传服务器实现优化
频繁的创立线程会减少系统资源的开销,能够利用线程池进行再次优化。
public static void main(String[] args) throws IOException{System.out.println("服务器 启动.....");
ServerSocket serverSocket = new ServerSocket(6666);
// 创立 10 个线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
while (true) {Socket accept = serverSocket.accept();
// 提交线程执行的工作
executorService.submit(()->{
try{BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
BufferedOutputStream bos = new BufferedOutputStream(fis);
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {bos.write(b, 0, len);
}
System.out.println("back ........");
OutputStream out = accept.getOutputStream();
out.write("上传胜利".getBytes());
out.close();
bos.close();
bis.close();
accept.close();
System.out.println("文件上传已保留");
} catch (IOException e) {e.printStackTrace();
}
});
}
}
第四章:模仿 B / S 服务器
模仿网站服务器,应用浏览器拜访本人编写的服务端程序,查看网页成果。
4.1- 案例剖析
我的项目中有一个 web 我的项目,咱们写服务端代码,而后通过浏览器输出地址127.0.0.1:8888/web/index.html
-
筹备页面数据,web 文件夹。
- 蕴含网页 html 文件
- 蕴含图片
- 蕴含 css 样式表
- 咱们模仿服务器端,ServerSocket 类监听端口,应用浏览器拜访,查看网页成果
4.2-HTTP 协定
案例中需应用浏览器查看成果,浏览器和服务器之间是遵循 HTTP 协定的,咱们先对 HTTP 协定进行简略的介绍,在前期 JavaWeb 相干篇幅中再详解解释。
- HTTP 协定,称为超文本传输协定。
- 规定了客户端浏览器和服务器之间的协定。
- HTTP 协定是 TCP 网络通信模型中应用层的协定。
- 客户端浏览器被动向服务器发动申请,服务器收到后进行响应。
-
客户端申请
- 客户端在申请的信息的第一行中,携带了客户端想要申请的资源门路。
-
服务器端响应
- 响应中必须告知客户端响应的后果。
- 200 状态码示意响应胜利。
- Content-Type:text/html 告知浏览器响应的内容是文本 / 网页内容。
4.3- 代码实现
代码
public class WebSever {public static void main(String[] args) throws IOException {
// 创立 ServerSocket 对象
ServerSocket server = new ServerSocket(8888);
while (true){new Thread(new Runnable() {
@Override
public void run() {
try{
// 获取 socket 对象
Socket socket = server.accept();
// 读取接管的内容
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = reader.readLine(); // GET /web/index.html HTTP/1.1
String path = line.split(" ")[1].substring(1);
// 创立本地字节输出流
FileInputStream fis = new FileInputStream("day09_Socket\\" +path);
// 创立网络输入流
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());
int len = 0;
byte[]bts = new byte[1024];
while((len=fis.read(bts))!=-1){os.write(bts,0,len);
}
// 敞开资源
os.close();
fis.close();
reader.close();
socket.close();} catch (IOException e){e.printStackTrace();
}
}
}).start();}
}
}
成果
浏览器输出地址 127.0.0.1:8888/web/index.html
拜访