?? ??? 开放系统互联参考模型(Open System Interconnect)是国际标准化组织(ISO)制订的一个用于计算机或通信系统间互联的标准体系。采用七层结构,自下而上依次为:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
?? ? ??是一组用于实现网络互连的通信协议。Internet网络体系结构以TCP/IP为核心。采用四层结构,自下而上依次为:链路层、网络层、传输层、应用层。
?? ? ??两个模型之间的对应关系:
?? ? ??TCP/IP协议族
?? ? ?? TCP/IP协议簇是Internet的基础,也是当今最流行的组网形式。TCP/IP是一组协议的代名词,包括许多别的协议,组成了TCP/IP协议簇。
? ? ? ?TCP是为了在不可靠的互联网络上提供一种面向连接的、可靠的、基于字节流的传输层通信协议。
? ? ? ?建立一个TCP连接时需要客户端和服务端总共发送三次数据包以确认连接的建立。
? ? ? ?三次握手过程:
? ? ? ?1) 客户端发送SYN(SYN=x)报文给服务端,进入SYN_SEND状态。
? ? ? ?2) 服务端收到SYN报文,回应一个SYN(SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
? ? ? ?3) 客户端收到服务端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态,完成三次握手。
? ? ? ?
? ? ? ?TCP三次握手的漏洞
? ? ? ?如果你一个客户端向服务端发送了SYN报文后突然死机或掉线,那么服务器在发送SYN+ACK报文后是无法收到客户端ACK报文的,这种情况下服务端一般会不停的重试,并等待一段时间(大约为30秒-2分钟)后丢弃未完成的链接。一个用户出现异常不是什么大问题,但如果一个攻击者发送大量伪造原IP地址的攻击报文到服务器,服务器将为了维护一个非常大的半连接队列而消耗更多的CPU和内存资源。服务器忙于处理伪造的TCP连接请求而无暇理睬客户的正常需求。这种情况称为服务器受到了SYN Flood攻击(SYN洪水攻击)。
解决方案:
? ? ? ?建立一个连接需要三次握手,而终止一个连接要经过四次挥手。这是由TCP的半关(half-close)造成的。
? ? ? ?具体过程如下:
? ? ? ?1) 客户端进程向服务端发送标志位是FIN的报文段,设置序列号为seq,此时,客户端进入FIN_WAIT_1状态,并且停止发送数据。
? ? ? ?2) 服务端收到客户端发送的FIN报文段,向客户端返回一个标志位为ACK(ack=seq+1)的报文段,服务器进入CLOSE_WAIT(关闭等待)状态。客户端收到服务器确认请求后,进入FIN_WAIT_2状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后数据)。
? ? ? ?3) 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文(FIN=1,ACK=seq+1),服务器进入LAST_ACK状态,等待客户端确认。
? ? ? ?4) 客户端收到服务端连接释放的报文后,发出确认报文,客户端进入TIME_WAIT状态。此时TCP连接还没释放,必须经过2MSL(最长报文寿命)的时间后,撤销TCB,进入CLOSED状态。服务端只要收到确认,立即进入CLOSED状态。
?
? ? ? ?TCP的可靠性
? ? ? ?在TCP中,当发送端的数据达到接收主机时,接收主机会发回确认应答(ACK)。如果发送端收到确认应答,说明数据已成功发送到接收主机。反之,如果一段时间内没有收到确认应答,发送端就认为数据丢失,进行重发。因此即使产生丢包,仍然能够保证数据到达接收主机,实现可靠传输。
未收到确认应答并不意味着数据丢失,也有可能接收方已经收到数据,但是确认应答数据在途中丢失,这种情况发送端会误以为接收方没有收到而重发数据。
? ? ? ?对于接收方来说,反复收到相同的数据是不可取的。为了对上层应用提供可靠传输,接收主机必须放弃重复的数据包。因此引入了序列号。
序列号是按照顺序给发送数据的每一个字节(8 位字节)都标上号码的编号。接收端查询接收数据 TCP 首部中的序列号和数据的长度,将自己下一步应该接收的序列号作为确认应答返送回去。通过序列号和确认应答号,TCP 能够识别是否已经接收数据,又能够判断是否需要接收,从而实现可靠传输。
? ? ? ?TCP中的滑动窗口
? ? ? ?滑动窗口是TCP流量控制的一种方法。
首先第一次发送数据的时候窗口大小是根据链路带宽的大小决定的,假设是3。接收方收到数据后会对数据进行确认(ACK),并告诉发送方下次希望收到的数据是多少。发送方收到确认后按照发送方希望的数据大小发送。
? ? ? ?UDP(用户数据协议,User Datagram Protocol)为应用程序提供了一种无需建立连接就可以发送数据包的方法。UDP是一个不可靠的协议。
? ? ? ?使用UDP的服务主要包括:视频音频等多媒体通信、限定于局域网等特定网络中的通信、广播通信等。
? ? ? ?HTTP(超文本传输协议,Hyper Text Transfer Protocol)是用于万维网(www)客户端浏览器和服务器进行传输的协议。
? ? ? ?一次完整HTTP请求的7个过程
? ? ? ?1) 三次握手建立TCP连接。
? ? ? ?2) 客户端向服务器发送请求命令。
? ? ? ?3) 客户端发送请求头信息。
? ? ? ?4) 服务器应答。
? ? ? ?;5) 服务器向客户端发送数据。
? ? ? ?;6) 关闭TCP连接。
? ? ? ?HTTP协议报文结构
? ? ? ?请求报文结构:
? ? ? ?响应报文结构:
? ? ? ?阻塞IO
? ? ? ?应用程序调用IO函数后阻塞,等待数据准备好。当数据准备好时,将数据从内核拷贝到用户空间,IO函数返回。
? ? ? ?非阻塞IO
? ? ? ?当应用程序调用IO函数后不会阻塞,如果没有数据会返回一个错误,应用程序反复调用IO函数,直到数据准备好。不断调用的过程会消耗大量的CPU资源,不推荐使用。
? ? ? ?IO多路复用
? ? ? ?IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题,此外poll、epoll都是这种模型。在该种模式下,用户首先将需要进行IO操作的 socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起 read请求,读取数据并继续执行。从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理 多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处 理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
? ? ? ?信号驱动IO
? ? ? ?套接口进行信号驱动IO,并安装一个信号处理函数,进程继续运行而不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号函数调用IO函数处理数据。
? ? ? ?异步IO
? ? ? ?当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者的输入输出操作。
? ? ? ?LINUX底层使用epoll实现,是伪异步。
? ? ? ?5种IO模型比较:
? ? ? ?select、poll、epoll区别
? ? ? ?传统的BIO通信模型:服务端通常由一个独立Acceptor线程负责监听客户端连接,收到客户端请求后为每个客户端创建一个新的线程进行处理,处理完成后通过输出流返回应答给客户端。该模型最大的问题是当请求增多,线程数量增加,系统性能急剧下降。可以使用线程池进行改进。
? ? ? ?服务端代码:
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8888));
while (true) {
new Thread(new Task(serverSocket.accept())).start();
}
}
static class Task implements Runnable {
private Socket socket;
Task(Socket socket) {
this.socket = socket;
}
@SneakyThrows
@Override
public void run() {
ObjectOutputStream outputStream = null;
ObjectInputStream inputStream = null;
try {
inputStream = new ObjectInputStream(socket.getInputStream());
String name = inputStream.readUTF();
System.out.println("收到客户端发来的信息....." + name);
outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeUTF("Hello," + name);
outputStream.flush();
} finally {
if (inputStream != null) inputStream.close();
if (outputStream != null) outputStream.close();
}
}
}
}
? ? ? ?客户端代码:
public class Client {
private final static String IP = "127.0.0.1";
private final static int PORT = 8888;
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress(IP, PORT));
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeUTF("hello");
outputStream.flush();
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
String context = inputStream.readUTF();
System.out.println("收到了服务器回复的消息..." + context);
outputStream.close();
inputStream.close();
socket.close();
}
}