一、计算机网络五层模型
1、应用层:为用户为用户的应用进程提供网络通信服务
协议——DNS协议、HTTP协议、HTTPS协议
2、传输层:负责两台主机之间的数据传输,将数据从发送端传输到接收端
协议——TCP协议、UDP协议
3、网络层:负责传输的地址管理和路由选择,在众多复杂的网络环境中确定一条合适的路径
协议——IP协议
4、数据链路层:负责设备之间数据帧的传送和识别,将网络层传递的数据报封装成帧,在处于同一个数据数据链路节点的两个设备之间传输
5、协议——ARP协议、MTU协议
物理层:负责光电信号的传递方式,实现相邻计算机节点之间比特流的透明传输
二、应用层协议
应用层协议主要负责各个程序间的通信,发生网络传输一个数据时,先由应用层对数据按照对应的协议封装,然后交给下一层传输层,当经过一系列网络传输,数据达到接收端时,一层层的分用,最后一层再由应用层分用,最终得到数据。
DNS协议:
DNS协议是一个应用层协议,建立在TCP和UDP的基础之上,使用默认端口为53,其默认通过UDP协议通信,但如果报文过大是则会切换成TCP协议。
域名系统 (DNS) 的作用是将人类可读的域名 (如,www.baidu.com) 转换为机器可读的 IP 地址 (如,192.0.2.44),本质是通过DNS域名和IP地址的对应关系转换,而这种对应关系则保存在DNS服务器中
域名的解析过程:
域名的解析工作大体上可以分为两个步骤:第一步客户端向本地DNS服务器发起一个DNS请求报文,报文里携带需要查询的域名,第二步本地DNS服务器向本机回应一个DNS响应报文,报文里携带查询域名所对应的IP地址
HTTP协议
HTTP协议是一个简单的请求——响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。
同其他应用层协议一样,是为了实现某一类具体应用的协议,并由某一运行在用户空间的应用程序来实现其功能。HTTP是一种协议规范,这种规范记录在文档上,为真正通过HTTP进行通信的HTTP的实现程序。
1、HTTP是基于TCP协议,且面向连接的。典型的HTTP事务处理有如下的过程:
2、客户端与服务器建立连接;
3、客户端向服务器提出请求;
4、服务器接受请求,并根据请求返回相应的数据作为应答响应;
5、客户端与服务器关闭连接。
HTTP协议报文格式
HTTP报文由从客户机到服务器的请求(Request)和从服务器到客户机的响应(Respone)构成
请求由请求行,请求头,请求体组:请求行中包含请求方法、路径、版本号,请求头为多个key-value数据,请求正文包含一些请求的数据
响应由响应行,响应头,响应体组成:响应行中包含状态码,状态码描述,版本号,响应头为多个key-value数据,响应正文包含一些响应的数据
三、常见的两种传输协议
UDP
将数据及源和目的封装成数据包中,不需要建立连接
每个数据报的大小在限制在64k内
因无连接,是不可靠协议。
不需要建立连接,速度快
应用案例: QQ、FeiQ聊天、在线视频用的都是UDP传输协议.
TCP
建立连接,形成传输数据的通道
在连接中进行大数据量传输。
通过三次握手完成连接,是可靠协议。
必须建立连接,效率会稍低
应用案例: FT,File Transfer Protocol( 文件传输协议)。
3.1 UDP 发送端--接收端
Socket
Socket就是为网络服务提供的一种机制。
通信的两端都有Socket。
网络通信其实就是Socket间的通信。
数据在两个Socket间通过IO传输
UDP传输
DatagramSocket ( 用来发送和接收数据报包的套接字)与DatagramPacket ( 数据报包)
建立发送端,接收端。
建立数据包。
调用Socket的发送接收方法
关闭Socket。
发送端与接收端是两个独立的运行程序.
由于UDP协议传输数据,只管发送数据,而不管接收端是否能够接收到数据。因此,应该首先启动接收端程序,再启动发送端程序
接收端
package com.example.demo.netWork;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpGet {
public static void main(String[] args) throws Exception {
System.out.println("接收端启动......");
/*
* 建立UDP接收端的思路。
* 思路:
* 1. 建立udp的socket服务,因为是要接收数据,必须要明确一个端口号。
* 2. 创建数据包,用于存储接收到的数据,方便用数据包对象的方法解析这些数
* 3. 使用socket服务的receive方法将接收的数据存储到数据包中。
* 4. 通过数据包的方法解析数据包中的数据。
* 5. 关闭资源。
*/
//1. 建立udpsocket服务。
DatagramSocket ds = new DatagramSocket(10000);
//2. 创建数据包。
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//3. 使用接收方法将数据存储到数据包中。
ds.receive(dp);//阻塞式的。
//4. 通过数据包对象的方法,解析其中的数据,比如:地址,端口,数据内容。
String ip = dp.getAddress().getHostAddress();
//获取的端口号是发送端的端口号。
int port = dp.getPort();
String text = new String(dp.getData(),0,dp.getLength());
System.out.println(ip + ":" + port + ":" + text);
//5. 关闭资源
ds.close();
}
}
发送端
package com.example.demo.netWork;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpSend {
public static void main(String[] args) throws Exception {
System.out.println("发送端启动......");
/*
* 创建UDP传输的发送端。
* 思路:
* 1. 建立udp的socket服务。
* 2. 将要发送的数据封装到数据包中。
* 3. 通过udp的socket服务将数据包发送出去。
* 4. 关闭socket服务。
*/
//1. udpsocket服务。使用DatagramSocket对象。
//如果发送端端口未指定,就会随机分配未被使用的端口。
DatagramSocket ds = new DatagramSocket(8888);
//2. 将要发送的数据封装到数据包中。
String str = "udp传输演示,传输了一个汉堡!!";
//使用DatagramPacket将数据封装到该对象包中。
byte[] buf = str.getBytes();
DatagramPacket dp = new
DatagramPacket(buf,buf.length, InetAddress.getByName("192.168.92.1"),10000);
//3. 通过udp的socket服务将数据包发送出去,使用send方法。
ds.send(dp);
//4. 关闭资源
ds.close();
}
}
结果
基于UDP的双聊天窗口实现(一对一聊天)
接收端
package com.example.demo.netWork;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpGet1 {
public static void main(String[] args) throws Exception {
System.out.println("接收端启动......");
/*
* 建立UDP接收端的思路。
* 思路:
* 1. 建立udp的socket服务,因为是要接收数据,必须要明确一个端口号。
* 2. 创建数据包,用于存储接收到的数据,方便用数据包对象的方法解析这些数
* 3. 使用socket服务的receive方法将接收的数据存储到数据包中。
* 4. 通过数据包的方法解析数据包中的数据。
* 5. 关闭资源。
*/
//1. 建立udpsocket服务。
DatagramSocket ds = new DatagramSocket(10000);
//因为udp传输协议只管发送数据,所以接收端要一直处于开启状态
while (true){
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//3. 使用接收方法将数据存储到数据包中。
ds.receive(dp);//阻塞式的。
//4. 通过数据包对象的方法,解析其中的数据,比如:地址,端口,数据内容。
String ip = dp.getAddress().getHostAddress();
//获取的端口号是发送端的端口号。
int port = dp.getPort();
String text = new String(dp.getData(),0,dp.getLength());
System.out.println(ip + "端口:" + port + "发来消息:" + text);
}
}
}
发送端
package com.example.demo.netWork;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpSend1 {
public static void main(String[] args) throws Exception {
System.out.println("发送端启动......");
/*
* 创建UDP传输的发送端。
* 思路:
* 1. 建立udp的socket服务。
* 2. 将要发送的数据封装到数据包中。
* 3. 通过udp的socket服务将数据包发送出去。
* 4. 关闭socket服务。
*/
//1. udpsocket服务。使用DatagramSocket对象。
//如果发送端端口未指定,就会随机分配未被使用的端口。
DatagramSocket ds = new DatagramSocket(8888);
//2. 建立一个缓冲区,将要用户从键盘输入想发送的数据封装到一个缓冲区内。最后一起发送
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while ((line = bufr.readLine()) != null){
// 如果缓冲区中读到数据,
byte[] buf = line.getBytes();
// 封装为byte流并通过 DatagramPacket 包装 并指定端口后
DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("192.168.92.1"), 10000);
//3. 通过udp的socket服务将数据包发送出去,使用send方法。
ds.send(dp);
//4.如果检测到结束语句,则结束会话
if (line.equals("over"))
break;
}
//4. 关闭资源
ds.close();
}
}
结果
基于UDP的单聊天窗口实现(群聊)
接收端
发送端
3.2 TCP
3.2.1 客户端-服务端
客户端(Client)首先与服务端(Server)建立连接,形成通道(其实就是IO流),然后,数据就可以在通道之间进行传输,并且单个5erver端可以同时与多个Client端建立连接
Socket和ServerSocket,建立客户端和服务器端建立连接后,通过Socket中的IO流进行数据的传输,关闭socket.同样,客户端与服务器端是两个独立的应用程序,
TCP客户端
客户端需要明确服务器的ip地址以及端口,这样才可以去试着建立连接,如果连接失败,会出现异常,连接成功,说明客户端与服务端建立了通道,那么通过I0流就可以进行数据的传输,而Socket对象已经提供了输入流和输出流对象,通过getInputStream()getOutputstream()获取即可,与服务端通讯结束后,关闭Socket.
TCP服务端
服务端需要明确它要处理的数据是从哪个端口进入的,当有客户端访问时,要明确是哪个客户端,可通过accept()获取已连接的客户端对象,并通过该对象与客户端通过IO流进行数据传输,当该客户端访问结束,关闭该客户端.
!!!!TCP协议中传输数据必须先开启服务端,在开启客户端,否则客户端无法通过接口连接上服务端!!!!!
客户端与服务端的数据交互
客户端
package com.example.demo.netWork.TCP;
import java.io.*;
import java.net.Socket;
public class KeyboardDemo_Client {
public static void main(String[] args) throws IOException {
// *思路
// *客户端
// *1.需要先有socket端点。
// *2.客户端的数据源:键盘.
// *3.客户端的目的:socket。
// *4.接收服务端的数据,源:socket。
// *5.将数据显示再打印出来。目的:控制台。
// *6.在这些流中操作的数据,都是文本数据。
// *转换客户端
// *1.创建Socket客户端对象。
// *2.获取键盘录入。
// *3.将录入的信息发送给socket输出流。
Socket socket = new Socket("你的主机ip", 10086);//自定义空闲端口号
//键盘输入写给服务器端数据,并封装在容器内
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
//从服务器端接收返回的数据,并封装在 bufIn 容器内
BufferedReader bufIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while (((line = bufr.readLine())) != null) {
if (line.equals("over"))
break;
//将数据传入服务器端,
out.println(line);
//获取服务器端返回的数据
String getInfo = bufIn.readLine();
System.out.println("服务器返回的数据: " + getInfo);
}
//关闭传输
socket.close();
}
}
服务端
package com.example.demo.netWork.TCP;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class KeyboardDemo_Serve1 {
public static void main(String[] args) throws IOException {
// *转换服务器。
// *分析
// *1.serversocket服务。
// *2.获取socket对象。
// *3.源:socket,读取客户端发过来需要转换的数据。
// *4.目的:显示在控制台上。
// *5.将数据转换成大写发给客户端
//创建服务器
ServerSocket serverSocket = new ServerSocket(10086);
//客户端进行连接获取客户端对象
Socket mySocket = serverSocket.accept();
System.out.println(mySocket.getInetAddress().getHostAddress()+"。。。。。。接入服务器");
// 创建读取客户端的数据的容器,并将读取到的InputStream内容写入buf
InputStream input = mySocket.getInputStream();
BufferedReader buf = new BufferedReader(new InputStreamReader(input));
// 创建需要传回客户端的数据的容器,由mySocket.getOutputStream()构造
PrintWriter out = new PrintWriter(mySocket.getOutputStream(),true);
String line =null;
while ((line = buf.readLine()) != null){
if (line.equals("over"))
break;
//服务端接收的数据
System.out.println(line);
//将数据转化为大写并返回客户端
String upperCase = line.toUpperCase();
// 通过构造的容器返回客户端
out.println(upperCase);
}
//关闭传输
mySocket.close();
serverSocket.close();
}
}
结果
3.2.4 三次握手协议
TCP在连接时会进行三次握手
首先客户端和服务端均关闭
第一次握手:
客户端——请求(发送请求SYN+数据包当前序列号seq,无需应答)
客户端创建传输控制块TCB,进入监听LISTEN状态。
设置SYN=1,表示这是握手报文,并发送给服务器
设置发送的数据包序列号seq=x
此时客户端处于同步已发送SYN-SENT状态
第二次握手:
服务器——确认(发送应答ACK+请求SYN+确认收到上一个数据包的确认号ack+ 当前数据包序列号seq)
设置ACK=1,表示确认应答。
设置ack=x+1,表示已收到客户端x之前的数据,希望下次数据从x+1开始
设置SYN=1,表示握手报文,并发送给客户端
设置发送的数据包序列号seq=y
此时服务器处于同步已接收SYN-RCVD状态
第三次握手:
客户端——确认服务器的确认(发送应答ACK+确认收到上一个数据包的确认号ack+ 当前数据包序列号seq ,连接已建立,无需请求)
设置ACK=1,表示确认应答。
设置ack=y+1,表示收到服务器发来的序列号为seq=y的数据包,希望下次数据从y+1开始
设置seq=x+1,表示接着上一个数据包seq=x继续发送
至此三次握手结束,连接建立
为什么要使用三次握手机制?
1.为了阻止历史连接
若网络状况不好的时候,客户端发送的SYN信号未能及时传输给服务器。当网络恢复时已经失效的SYN信号又到达服务器。如果只有两次握手便可建立连接,那么此时客户端就不知道这个连接是不是已经失效了的历史连接,从而导致错误的发生。三次握手时,客户端便可以根据上下文来判断此次连接是否为历史连接,避免错误的发生。
2. 为了避免服务器开启无用连接增加服务器开销
客户端设置了一个超时时间,超过了就重新发送一个TCP连接请求。如果没有第三次握手的话,服务端是不知道客户端是否收到服务返回的信息的,这样没有给服务器端一个创建还是关闭连接端口的请求,服务器端的端口就一直开着,等到客户端因超时重新发出请求时,服务器就会重新开启一个端口连接。那么服务器端上没有接收到请求数据的上一个端口就一直开着,长此以往,这样的端口多了,就会造成服务器端开销的严重浪费。
四、网络架构
1. C/S client/server
缺点:
该结构的软件,客户端和服务端都需要编写。
开发成本较高,维护较为麻烦。
优点:
客户端在本地可以分担一部分任务。例如,杀毒软件直接对本机文件进行杀毒
2. B/S browser/server
优点:
该结构的软件,只开发服务器端,不开发客户端,因为客户端直接由浏览器取代
开发成本相对低,维护更为简单.
缺点:
所有运算都要在服务端完成