外围概述:在互联网时代,咱们所利用的大部分软件都是基于网络为根底的,那么软件之间是如何通过网络通信的呢?为什么咱们通过浏览器输出网址就能够看到网页?本篇咱们将会学习网络编程,让咱们可能从编程角度更底层的更好的了解软件之间的通信流程。
第一章:网络编程根底
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
拜访
发表回复