网络发展史
独立模式
网络互连
随着时代的发展,越来越需要计算机之间相互通信,共享软件和数据,即多个计算机协同工作来完成任务,就有了网络互联。本质上就是将多个计算机连接到一起 ,完成数据共享 。数据共享的本质是网络数据传输 ,即计算机之间通过网络来传输数据 ,也称为网络通信。
局域网
Local Area Network简称LAN ,Local表示局域网是本地的,局部组件的一种私有网络 。局域网内的主机之间能方便的进行网络通信,又称为内网。局域网和局域网在没有链接的情况下是无法进行通信的。
局域网组建网络的方式:
基于网线直连
基于集线器组件
基于交换机组件
基于交换机和路由器组件
广域网
Wide Area Network 简称WAN 。通过路由器 将多个局域网连接起来 ,在物理上组成更大范围的网络,就形成了广域网 。广域网内部的局域网都属于其子网 。最大的广域网就是**"互联网"**
网络通信基础
IP地址
用于定位主机的网络地址。
格式
IP地址是一个32位的二进制数 ,通常被分割成4个"八位二进制数",也就是四个字节
使用"点分十进制"的方式来表示,即a.b.c.d的形式。a b c d都是0~255之间的十进制数
01100100.00000100.00000101.00000110 - > 100.4.5.6
端口号
IP地址用于标识主机网络地址 ,端口号用于标识主机中发送数据、接收数据的进程 。简单来说就是端口号用于定位主机中的进程
格式
0~65536 的整数,其中0~1023为系统占用
80: 负责处理未加密的普通网页浏览数据 HTTP
443: 专门负责加密的网页浏览HTTPS
21: 负责网络上上传或下载文件FTP
22: 用于远程加密控制服务器SSH
协议
协议,网络协议的简称 。网络协议是网络数据传输锁经过的所有网络设备都必须共同遵守的一组约定、规则 。如怎样建立连接,怎样互相识别。只有遵守这个约定,计算机之间才能相互通信交流。协议的最终体现为在网络传输的数据包的形式
五元组
在TCP/IP协议 中,用五元组来标识一个网络通信
1. 源IP
2. 源端口号
3. 目的IP
4. 目的端口号
5. 协议号
可以在cmd中输入 netstat -ano查看网络数据传输中的五元组信息

协议分层
协议分层, 类似于面向接口编程:定义好两层之间的接口规范,让双方都遵循这个规范来对接。类似于定义好一个接口,一方为接口的实现类,另一方为接口的使用类
对于使用方来说,不用管提供方是怎么实现的
对于提供方 来说,利用封装的特性,隐藏了实现的细节,只需要开放接口即可
对应到网络协议分层中:
只有相邻两层协议之间可以进行交互
上层协议 可以调用下层协议 ,下层协议可以给上层协议提供服务
协议之间的交互,不能跨层进行
TCP/IP五层(或四层)模型
TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇
应用层: 负责应用程序之间的沟通 ,也就是拿到数据包后如何使用
传输层: 负责任意两个设备之间的通信 ,标明两个设备之间的端口号以及传输协议
网络层: 负责任意两个设备之间的通信。通过IP地址 来标识一台主机,并通过路由表 的方式规划出两台主机之间的数据传输的线路。路由器工作在网络层
数据链路层: 将数据打包成(以太网数据帧 )上面标注源MAC地址,和目标MAC地址 。注意这里的目标MAC地址不是指数据传输的终点,而是下一个中转站的地址
物理层: 规定网络通信中的一些硬件设施符合的要求。现在的光纤、wifi无线网使用的电磁波都属于物理层的概念。物理层的能力决定了最大 传输效率、传输距离、抗干扰性等。集线器工作在物理层。
网络设备所在的分层
主机: 工作过程涉及到物理层 -> 应用层。通过应用层满足网络通信的需求
路由器: 工作过程涉及到物理层 -> 网络层 组建局域网,进行网络数据包的转发
交换机: 工作过程涉及到物理层 -> 数据链路层 对路由器接口的扩展,不需要考虑组网的问题
集线器: 只涉及到物理层
这里说的是传统意义上的交换机和路由器,也称为二层交换机、三层交换机。
所在网络设备技术的发展,也出现了很多三层或者四层交换机,交换机集成了路由器的功能
封装和分用
应用数据通过协议栈发到网络上时,每层协议都要加上数据首部 ,称为封装。首部信息中包含了一些类似于首部有多长,载荷有多长,上层协议是什么等信息。数据分装成帧后发送到传输介质上,到达目的主机后,每层协议再剥夺相应的首部,分层进行处理。
不同协议层对数据包有不同称谓,在传输层 叫段segment ,在网络层 叫数据包datagram ,在数据链路层 叫帧frame。


Socket套接字
Socket套接字,是由系统提供用于网络通信的技术 ,是基于TCP/IP协议 的网络通信基本操作单元 。基于Socket套接字的网络程序 开发就是网络编程。Socket套接字主要针对传输层协议划分为以下两类
UDP数据报套接字编程
UDP:用户数据报协议,传输层协议
无连接:通信双方不保存对方信息
**不可靠传输:**数据发送之后就不管了
面向数据报: 读写数据以一个数据报 为单位,一次必须读写一个数据报,不存在粘包问题。但是每个数据报有长度限制(一次最多传输64K)
有接收缓冲区,无发送缓冲区
API介绍
DatagramSocket:
DatagramSocket用于发送和接收UDP数据报 ,直接操控网卡
DatagramSocket(): 创建一个UDP数据报套接字的Socket ,绑定到本机任意一个接口
DatagramSocket(int port): 创建一个UDP数据报套接字的Socket,绑定到本机指定端口
void receive(DatagramPacket p): 从此套接字接收数据报 (如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p): 从此套接字发送数据报包(不会阻塞等待,直接发送)
**void close():**关闭此数据报套接字
DatagramPacket:
DatagramPacket是发送和接收的数据报
DatagramPacket(byte[] buf,int length): 构造一个用以接收数据的数据报 ,接收的数据报保存在字节数组 ,并指定长度
DatagramPacket(byte[] buf,int offset,int length,SocketAddress address): 构造一个用以发送的数据报 ,发送到数据为字节数组 ,从offset到指定长度。address指定目的主机的IP和端口号(也可以分开传入)
InetAddress getAddress(): 从接收的数据报中,获取发送端主机IP地址 ;或从发送的数据报中,获取接收端的IP地址 。通过InetAddress.getByName(IP) 可以返回一个InetAddress对象
**int getPort():**从接收的数据报中,获取发送端主机端口号;或从发送的数据报中,获取接收端的端口号
SocketAddress getSocketAddress(): 获取端口和IP,用于返回响应
**byte[] getData() :**获取数据报中的数据
int getLength(): 获取实际接收到的字节数
InetSocketAddress
SocketAddress 的子类,用于构造UDP发送的数据报
InetSocketAddress(InetAddress addr,int port): 创建一个Socket地址,包含IP和端口号
实现一个简单的客户端/服务器
服务器:
java
public class UdpEchoServer {
private static DatagramSocket socket= null;
public UdpEchoServer(int port) throws SocketException {
socket=new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器开启成功");
while(true){//循环一次相当于处理一次请求
//获取响应,数据报由报头+载荷(byte[])组成
DatagramPacket requestPacket =new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);//接收不到响应则等待
//提取有效信息
String request=new String(requestPacket.getData(),0,
requestPacket.getLength());
//处理响应
String responce=process(request);
// 构建响应的数据报
DatagramPacket responcePacket=new DatagramPacket(responce.getBytes()
,responce.getBytes().length
,requestPacket.getSocketAddress());
// 返回响应
socket.send(responcePacket);
//打印日志
System.out.printf([%s:%d],req:%s,resp:%s\n",
requestPacket.getAddress(),
requestPacket.getPort(),
request,responce);
}
}
//处理请求
private String process(String request){
return request;
}
}
客户端:
java
public class UdpEchoClient {
private static DatagramSocket socket =null;
private String IP;
private int Port;
public UdpEchoClient(String IP, int port) throws SocketException {
this.IP = IP;
Port = port;
//这里不能指定端口号,因为对于客户端来说,
// 端口号是随机分配的空闲端口号
socket=new DatagramSocket();
}
public void start() throws IOException {
Scanner sc=new Scanner(System.in);
while(true){
System.out.print("输入内容:");
if(!sc.hasNext()){
break;
}
//读取用户输入,并构建请求数据报
String request=sc.next();
DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),
request.getBytes().length,
InetAddress.getByName(IP),Port);
//发送数据报
socket.send(requestPacket);
//读取服务器的响应
DatagramPacket responsePacket =new DatagramPacket(new byte[40960],40960);
socket.receive(responsePacket);
//提取输出结果
String response=new String(responsePacket.getData(),0,
responsePacket.getLength());
System.out.println("输出:"+response);
}
}
}
TCP流套接字编程
TCP:传输控制协议,传输层协议
**有连接:**通信双方保存对方的信息
**可靠传输:**发生丢包能够感知到
面向字节流: 读写数据以一个字节 为单位,支持任意长度 ,但容易出现粘包问题
有接收和发送缓冲区
API介绍
ServerSocket:
**ServerSocket(int port):**创建一个服务端流套接字Socket,并绑定到指定端口
Socket accept(): 开始监听指定端口 。有客户端连接后,返回一个服务端Socket对象 ,并基于该Socket建立与客户端的连接,否则阻塞等待。
**void close():**关闭此套接字
Socket:
客户端Socket,或服务端中接收到客户端建立连接的请求后,返回的服务端Socket。不管是服务端还是客户端Socket,都是双方建立连接以后,保存的对端信息,以及用来与对方收发数据的。
**Socket(String host,int port):**创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接
InetAddress getInetAddress(): 返回套接字所连接的IP
InputStream getInputStream(): 返回此套接字的输入流
OutputStream getOutputStream(): 返回此套接字的输出流
实现一个简单的客户端/服务器
服务器:
1. 主线程负责接收accept()客户端
2. 线程池中的子线程负责处理客户端的请求
java
public class TcpEchoServer {
private static ServerSocket socket=null;
public TcpEchoServer(int port) throws IOException {
socket=new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动成功");
// 创建线程池,每个线程负责一个客户端连接
ExecutorService pool= Executors.newCachedThreadPool();
while(true){
//接收客户端连接,接收不到则阻塞等待
Socket clientSocket=socket.accept();
System.out.printf("客户端[%s:%d]上线\n",
clientSocket.getInetAddress(),
clientSocket.getPort());
pool.submit(()->{
try {
//处理客户端请求
processClient(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
public void processClient(Socket clientSocket) throws IOException {
//打开输入输出流
try(InputStream input=clientSocket.getInputStream();
OutputStream output=clientSocket.getOutputStream()){
//用于接收数据
Scanner sc=new Scanner(input);
//用于发送数据
PrintWriter writer=new PrintWriter(output);
//客户端不发送请求,则会阻塞在这里
while(true){
if(!sc.hasNext()){
System.out.println("客户端下线");
break;
}
//接收请求
String request=sc.next();
//处理请求
String response =process(request);
//返回响应
writer.println(response);
//刷新缓冲区
writer.flush();
System.out.printf("[%s:%d],req:%s,resp:%s\n",
clientSocket.getInetAddress(),
clientSocket.getPort(),
request,response);
}
}finally{
clientSocket.close();
}
}
//处理请求
public String process(String request){
return request;
}
}
由于要为每个客户端都创建一个线程,这样的开销很大,可以通过IO多路复用来解决这个问题。
IO多路复用: 因为每个客户端进行请求的时间的时间在总时间的占比是很小的,所以我们完全可以让一个线程去处理多个客户端
客户端:
java
public class TcpEchoClient {
private static Socket clientSocket=null;
public TcpEchoClient(String host,int port) throws IOException {
clientSocket=new Socket(host,port);
}
public void start() throws IOException {
//打开输入输出流
try(InputStream input= clientSocket.getInputStream();
OutputStream output= clientSocket.getOutputStream()){
//接收用户输入
Scanner sc=new Scanner(System.in);
//用于接收服务器响应
Scanner scannerNet =new Scanner(input);
//用于给服务器发送请求
PrintWriter writer=new PrintWriter(output);
while(true){
System.out.println("请输入:");
if(!sc.hasNext()){
break;
}
String request=sc.next();
//向服务器发送请求
writer.println(request);
//刷新缓冲区
writer.flush();
//接收服务器响应
String response= scannerNet.next();
System.out.println("输出"+response);
}
}
}
}