计算机网络视频 计算机网络-基础知识 计算机网络-应用层 计算机网络-传输层 计算机网络-网络层 计算机网络-数据链路层
应用层是提供不同主机之间进程和进程的通信,应用层的数据单位是报文。常见的应用层协议包括:
Web:HTTP、HTTPS
Email:SMTP、POP3、IMAP
File:FTP
网络应用的体系结构
C/S:客户端-服务器
P2P:没有明确客户端和服务器的角色
混合结构:比如文件传输使用 P2P 结构,文件搜索使用 C/S 结构
Web 应用
HTTP
多进程多线程浏览器
HTTP 协议是一个无状态的协议,如果设计成有状态的协议,维护状态需要进行非常复杂的处理。HTTP 默认是持久性连接。
非持久性连接:一个 TCP 连接只能处理一个 HTTP 请求
持久性连接:一个 TCP 连接能够处理多个 HTTP 请求
HTTP 包括请求消息和响应消息,首部包括通用首部字段、请求首部字段、响应首部字段和实体首部字段。
DNS 应用 域名解析系统 DNS 是将域名解析为 IP 地址的分布式分层数据库。DNS 提供以下几种服务:
将域名映射成 IP 地址
主机别名
邮件服务器别名
负载均衡:在进行域名向 IP 地址的映射时,可以提供多个 IP 地址
分布式 域名并不完全存储于一个服务器中,每个 DNS 服务器只保留它自己的数据。
分层
域名具有层次结构,从上到下依次是:根域名、顶级域名、二级域名…
根域名服务器:全球有 13 个根域名服务器
顶级域名服务器:负责 edu
、 com
、 org
等顶级域名
权威域名服务器:组织的域名解析服务器,负责组织内部的域名解析
本地域名服务器:每个 ISP 都有一个本地域名服务器。本地域名服务器是默认的域名解析服务器,当主机进行 DNS 查询时,查询被发送到本地域名服务器,本地域名服务器作为代理将查询转发给域名解析系统。
迭代查询 & 递归查询 域名查询分为两种:迭代查询和递归查询
若客户端想要查询 dzapathy.github.io
的 IP,迭代查询的流程如下:
首先主机将查询发送给本地域名服务器
本地域名服务器无法解析域名时,本地域名服务器作为代理转发查询访问根域名服务器
根域名服务器无法解析域名时,返回顶级域名服务器 io
本地域名服务器查询顶级域名解析服务器 io
顶级域名解析服务器无法解析域名时,返回权威域名服务器 github.io
本地域名服务器查询权威域名服务器 github.io
权威域名服务器 github.io
返回 dzapathy.github.io
的 IP 地址
本地域名服务器将 dzapathy.github.io
的 IP 地址返回给客户端
若客户端想要查询 dzapathy.github.io
的 IP,递归查询的流程如下:
首先主机将查询发送给本地域名服务器
本地域名服务器无法解析域名时,本地域名服务器作为代理转发查询访问根域名服务器
根域名服务器无法解析域名时,查询顶级域名服务器 io
顶级域名服务器无法解析域名时,查询权威域名服务器 github.io
权威域名服务器 github.io
返回 dzapathy.github.io
的 IP 地址给顶级域名服务器
顶级域名服务器返回给根域名服务器
根域名服务器返回给本地域名服务器
本地域名服务器将 dzapathy.github.io
的 IP 地址返回给客户端
查询区别
迭代查询本地服务器会发送多次查询请求,递归查询本地服务器会发送一次查询请求
递归查询域名解析系统需要维护并转发查询请求,而迭代查询只需要响应一次查询请求
一般主机和本地服务器之间使用递归查询,本地服务器和域名解析系统之间使用迭代查找
缓存 本地域名服务器一般会缓存顶级域名服务器的映射
DNS 记录 格式:$(name, value,type,ttl)$,$ttl$ 表示时间有效性
type = A
时,name
表示主机域名,value
表示 IP 地址
type = NS
时,name
表示域(比如edu.cn
),value
表示权威域名服务器的主机域名
type = CNAME
时,name
表示真实域名的别名,value
表示真实域名
type = MS
时,value
是与 name
相对应的邮件服务器
DNS 底层协议 DNS 可以使用 UDP 或 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传从而保证可靠性。在两种情况下会使用 TCP 进行传输:
如果返回的响应超过的 512 字节(UDP 最大只支持 512 字节的数据)
区域传送(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)
Email 应用 Email 应用包括邮件客户端、邮件服务器和邮件协议。邮件协议包含发送协议和读取协议,发送协议通常使用 SMTP 协议,读取协议通常使用 POP3 或 IMAP 协议。
邮件客户端:发送、接收邮件
邮件服务器:为每一个用户分配一个邮箱,并使用一个消息队列存储等待发送的邮件
邮件协议:邮件服务器之间传递消息所使用的协议
发送协议 SMTP 协议是发送协议,依赖于 TCP 协议,使用命令 / 响应模式进行交互,SMTP 占用的端口号是 25 。
持久性连接:只建立一次 TCP 请求
消息必须使用 7 位 ASCII 码
SMTP 使用回车换行确认消息结束
消息格式 SMTP
header:To、From、Subject
body:消息本身,只能是 ASCII
MIME 为了进行多媒体邮件扩展,在邮件头部增加额外的行声明 MIME 的内容类型,来支持发送多媒体。MIME 可以发送二进制文件,MIME 并没有改动或者取代 SMTP,而是增加邮件主体的结构,定义了非 ASCII 码的编码规则
读取协议 邮件读取协议又称邮件访问协议,主要有 POP3 和 IMAP 协议。
POP3:提供认证/授权和下载功能,无状态协议
IMAP:更复杂,更多功能,能够操纵邮件服务器上的消息
HTTP:网页邮件
POP3
认证过程:客户端发送用户名和密码,服务端响应
事务阶段:
List:列出消息数量
Retr:用编号获取消息
Dete:删除邮件
Quit:退出
…
POP3 模式:
IMAP
所有消息保存在服务器上
允许用户利用文件夹组织信息
IMAP 是有状态协议
P2P 应用 P2P 没有明确的客户端和服务器的角色,而是点对点的应用,P2P 采用共享的机制分发文件。
危害
索引
集中式索引
节点加入时将 IP 和持有的内容通知给中央服务器,中央服务器去维护所有节点的索引信息。集中索引存在单点故障、性能瓶颈、版权等问题
洪泛式查询
完全分布式的架构,每个节点对它共享的文件进行索引且只对它共享的文件进行索引。查询时向已有的 TCP 连接发送查询请求,任何收到查询请求的节点会转发查询请求。如果查询命中,利用反向路径发回查询节点
层次式覆盖网络
层次式覆盖网络是介于集中式索引和洪泛式查询之间的方法。网络节点分为两类:普通节点和超级节点。普通节点只和超级节点建立 TCP 连接,这部分可以看成集中式索引;超级节点维护索引信息,超级节点之间建立 TCP 连接,这部分可以看成洪泛式查询
FTP
FTP
DHCP
DHCP
一个主机如何获取 IP 地址:
静态配置: IP 地址、子网、默认网关和本地域名服务器 DNS
动态配置:动态主机配置协议 DHCP,从 DHCP 服务器获取 IP 地址。DHCP 能够保证主机即插即用,并且允许地址重用
过程:
主机发送”DHCP discover”发现报文,源 IP 地址和端口号是 $0.0.0.0:68$,目的 IP 地址和端口号是 $255.255.255.255:67$。广播的原因是为了发现子网内的 DHCP 服务器
DHCP 服务器利用”DHCP offer“ 提供报文进行响应,DHCP 进行广播的原因是因为此时主机还没有 IP 地址
主机发送 ”DHCP request“ 请求报文,继续广播,原因是可能有多个 IP 地址,主机携带接受的 IP ,其他 DHCP 服务器可以为其他主机分配 IP
DHCP 服务器发送”DHCP ACK“ 确认报文,DHCP 进行广播的原因是因为此时主机还没有 IP 地址,最后主机动态分配了一个 IP 地址
不过客户获取的IP一般是用租期,到期前需要更新租期
当租期使用 50% ,客户端直接向为其提供 IP 的DHCP 服务器发送 DHCP request 报文
如果收到 DHCP 服务器 ACK 报文更新租期;如果没有收到 ACK 报文继续使用
当租期使用 87.5% ,再次向为其提供 IP 的DHCP 服务器发送 DHCP request 报文
如果收到 DHCP 服务器 ACK 报文更新租期;如果没有收到 ACK 报文发送 DHCP offer 重新获取 IP
TELNET TELNET 用于登录到远程主机上,并且远程主机上的输出也会返回。TELNET 可以适应许多计算机和操作系统的差异,例如不同操作系统系统的换行符定义
网络编程 Socket API 对外使用 IP 地址和端口号来区分 Socket,对内操作系统使用套接字描述符来区分 Socket 。每个进程有一个端口号,可以有多个 Socket 。每个进程维护一个 Socket 描述符表,存储套接字描述符,套接字描述符指向对应的 Socket 数据结构。Socket 数据结构中包含源 IP 地址,源端口号,目的 IP 地址和目的端口号,以此来区分唯一 Socket 连接
端口号的范围是$0 \sim 65535$,其中 $0 \sim 1023$ 是知名端口号, $1024 \sim 65535$ 动态端口号。知名端口号为特定的网络应用提供服务端口
处理单个连接请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.apathy;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;public class SocketServer { public static void main (String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9000 ); Socket socket = serverSocket.accept(); DataInputStream dis = new DataInputStream( new BufferedInputStream(socket.getInputStream())); DataOutputStream dos = new DataOutputStream( new BufferedOutputStream(socket.getOutputStream())); while (true ) { double length = dis.readDouble(); if (length < 0 ) break ; System.out.println("服务器端收到的正方形边为:" + length); double result = length * length; dos.writeDouble(result); dos.flush(); } socket.close(); serverSocket.close(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.apathy;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.net.Socket;public class SocketClient { public static void main (String[] args) throws IOException { Socket socket = new Socket("localhost" , 9000 ); DataInputStream dis = new DataInputStream( new BufferedInputStream(socket.getInputStream())); DataOutputStream dos = new DataOutputStream( new BufferedOutputStream(socket.getOutputStream())); for (int i = 5 ; i >= 0 ; i--) { System.out.println("请输入正方形边长(小于0退出)" ); double length = i - 0.5 ; dos.writeDouble(length); dos.flush(); if (length < 0 ) break ; double res = dis.readDouble(); System.out.println("计算结果为:" + res); } socket.close(); } }
处理多个连接请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 package com.apathy;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.atomic.AtomicInteger;public class SocketServer { public static void main (String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888 ); AtomicInteger number = new AtomicInteger(); ExecutorService exec = Executors.newCachedThreadPool(); try { while (true ) { Socket socket = serverSocket.accept(); exec.execute(new SimpleServer(socket, number.getAndIncrement())); } } finally { serverSocket.close(); } } } class SimpleServer implements Runnable { private Socket socket; private int num; public SimpleServer (Socket socket, int num) { this .socket = socket; this .num = num; } @Override public void run () { DataInputStream dis = null ; DataOutputStream dos = null ; try { System.out.println("客户端 " + num + "建立连接" ); dis = new DataInputStream(new BufferedInputStream(socket.getInputStream())); dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); while (true ) { double length = dis.readDouble(); if (length < 0 ) break ; System.out.println("收到客户端 " + num+ " 的正方形边为:" + length); double result = length * length; dos.writeDouble(result); dos.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (dis != null ) dis.close(); if (dos != null ) dos.close(); socket.close(); }catch (IOException e) { e.printStackTrace(); } } } }
异步 IO 处理多个请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 package com.apathy;import java.io.IOException;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Set;public class SocketNIOService { public static void main (String[] args) throws IOException { Selector sel = Selector.open(); ServerSocketChannel socketChannel = ServerSocketChannel.open(); socketChannel.configureBlocking(false ); socketChannel.register(sel, SelectionKey.OP_ACCEPT); ServerSocket serverSocket = socketChannel.socket(); InetSocketAddress address = new InetSocketAddress("127.0.0.1" , 8888 ); serverSocket.bind(address); while (true ) { sel.select(); Set<SelectionKey> set = sel.selectedKeys(); Iterator<SelectionKey> iterator = set.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); if (key.isAcceptable()) { ServerSocketChannel ssc = (ServerSocketChannel)key.channel(); SocketChannel sc = ssc.accept(); sc.configureBlocking(false ); sc.register(sel, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel sc = (SocketChannel) key.channel(); System.out.println(readDataFromSocketChannel(sc)); sc.close(); } iterator.remove(); } } } private static String readDataFromSocketChannel (SocketChannel sChannel) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(1024 ); StringBuilder data = new StringBuilder(); while (true ) { buffer.clear(); int n = sChannel.read(buffer); if (n == -1 ) { break ; } buffer.flip(); int limit = buffer.limit(); char [] dst = new char [limit]; for (int i = 0 ; i < limit; i++) { dst[i] = (char ) buffer.get(i); } data.append(dst); buffer.clear(); } return data.toString(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.apathy;import java.io.IOException;import java.io.OutputStream;import java.net.Socket;public class SocketNIOClient { public static void main (String[] args) throws IOException { Socket socket = new Socket("127.0.0.1" , 8888 ); OutputStream ops = socket.getOutputStream(); String str = "hello, apathy" ; ops.write(str.getBytes()); ops.close(); socket.close(); } }