TCP/IP协议叫做传输控制/网际协议,它规范了网络上的所有通信设备,尤其是一个主机与另一个主机的数据往来格式和传送方式,是网络中使用的基本通信协议。
TCP/IP是一个协议族,包括上百个功能的协议,其中TCP和IP协议是保证数据完整传输的两个基本的重要协议。TCP/IP协议传输数据的过程中,TCP协议负责将数据分成若干个数据包,并给每个数据包加上包头,包头上面有相应的编号,以此保证数据接收时能将数据还原。IP协议给每个包头加上接收端主机地址,这样能够确保数据传输到正确的目的主机地址。所以,IP协议保证数据的传输,TCP协议保证数据传输的质量。
2、TCP/IP协议分层
TCP/IP通信协议采用了四层的层级模型结构,它参考了OSI的七层模型,主要分为应用层、传输层、网络层和链路层。
-
应用层:负责处理特定的应用程序细节。比如简单邮件传输协议(SMTP),文件传输协议(FTP),网络远程访问协议(Telnet)以及简单网络管理协议(SNMP)。
-
传输层:提供了节点间的数据传送服务,主要为两台主机上的应用程序提供端到端的通信。其中主要包括两种重要的传输协议,传输控制协议(TCP)和用户数据报协议(UDP)。TCP提供了可靠的数据通信,因此应用层可以不考虑传输的完整性。而UDP并不能保证数据能够到达目的端,任何必须的可靠性由应用层提供。
-
网路层:负责提供基本的数据封包传送功能,让每一块数据包都能到达目的主机。主要包括网络协议(IP)、Internet互联网控制报文协议(ICMP Internet)以及Internet组管理协议(IGMP Internet)。
-
链路层:通常包括操作系统中的设备驱动程序和计算机中对应的网路接口卡,主要处理物理接口细节。
3、TCP
传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器端进行数据交换时,必须现在双方建立一个TCP连接,之后才能传输数据。它能够保证数据传输过程中数据是可靠的、顺序的以及不会重复。但是,TCP在数据转移时必须创建并保持一个连接,这个会给通信过程增加开销,对比UDP传输速率会较慢。
TCP建立连接需要进行三次握手:
-
建立连接时,客户端A向服务器B发送同步序列号SYN包(SYN=j),并且进入SYN_SEND状态,等待服务器确认。
-
服务器B接收到SYN包时,将发送确认序号ACK(j+1)对客户端的SYN报文段进行确认,同时还会发回服务器的初始序号SYN包(SYN=k)作为应答,即SYN+ACK包,此时服务器进入SYN_RECV状态。
-
客户端A收到服务器B的SYN+ACK包时,将发送给服务器确认包ACK(ACK=k+1),发送完成后,客户端和服务器进入ESTABLISHED状态,完成三次握手。
客户端状态:
CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
前三个状态是客户端三次握手建立连接的状态迁移。建立连接后是四次挥手断开连接的状态迁移。
客户端主动请求关闭连接时,会向服务器端发送FIN报文,此时客户端进入FIN_WAIT_1状态。服务器不会立即关闭socket连接,仍然可以发送数据,此时服务器端会发送ACK确认包给客户端,客户端进入FIN_WAIT_2状态,等待服务器发送FIN报文。在客户端接收到FIN报文并发送ACK确认包时,客户端进入TIME_WAIT状态,在等待2MSL后依然没得到回复时,此时证明服务器已经正常断开连接,此时会断开TCP连接。
服务器状态:
CLOSED->LISTEN->SYN_RECV->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
同样,前几个状态是三次握手的状态转移。当服务器接收到关闭连接的FIN报文并发送ACK回答请求时,会进入CLOSE_WAIT状态。当服务器发送数据包完毕时,会发送FIN报文主动请求关闭连接,这时等待客户端的答复,即ACK的接收,进入LAST_ACK状态。当接收到ACK时,就关闭连接,进入CLOSED状态。
4、UDP
UDP是一个非连接的协议,传输数据之前不需要建立源端和终端的连接,它是一个简单的面向数据报的运输层协议。它只是把应用程序传给IP层的数据报发送出去,并不能保证它们能到达目的地。因为UDP没有建立连接,所以不需要维护连接状态,传输速率对比TCP快得多。
5、Socket
Socket接口是TCP/IP网络的API,它用于描述IP地址和端口,是一个通信链的句柄。为了实现网络通信过程,必须标识某台主机的一个进程。可以利用IP层的ip地址唯一标识主机,利用协议和端口号唯一标识主机的一个进程。因此,可以利用ip地址+协议+端口号唯一标识网络中的一个进程。这样一来,便可以利用socket进行通信。
常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
Socket通信过程是以“打开—读写—关闭”为模式实现的,以TCP为例子,在Server端上,其流程如下:
socket()->bind()->listen()->accept()->recv()->close()
-
Socket():建立socket,可以用于建立TCP或者UDP连接。建立socket会给一个socket数据结构分配内存空间,socket结构体包含5种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。
-
bind():用于建立socket与本机上一个端口之间的关联。在参数的设置中,必须指定本机的地址以及端口等信息,并且需要进行字节序的转换。
-
listen():使socket处于监听状态,并为该socket建立一个输入数据队列,将到达的请求保存在队列中,直到程序处理它们。
-
accept():让服务器接收客户的连接请求。在建立好输入队列时,服务器会调用accept()函数,然后等待客户的连接请求。没有客户请求到来时,服务器会一直停留在accept上,即阻塞,直到接收到用户请求才往下执行。
-
recv():recv()和send()函数用于tcp连接的数据传输。
-
close():释放相应的socket。
总体流程:首先调用socket函数建立服务器端的socket,然后调用bind函数将本机地址与一个端口号绑定,接着调用listen在相应的socket上进行监听,当accept接收到一个连接服务请求时,将生成新的socket,利用该socket进行数据的交换,最后关闭socket。
在Client端上,其通信流程如下:
socket()->connect()->send()->close()
-
socket():客户端建立socket。
-
connect():根据服务器ip地址和端口连接服务器socket
-
send():客户端与服务器传输数据
-
close():关闭客户端socket
总体流程:通过服务器ip地址,创建socket之后调用connect与服务器建立连接,成功之后利用send和recv函数与服务器进行数据交换,最后关闭socket。
对于udp,由于没有建立端到端的连接,因此在发送和接收数据时应指明相应的主机地址。sendto()和recvfrom()就是用于在无连接的数据报socket方式下进行数据传输。
6、多个Client连接一个Server
可以利用多个accept()实现多client连接,多accept()的server建立多个子线程,每个子线程都要一个accept(),这样可以为每个Client建立socket描述符。accept连接成功后,会产生一个新的socket描述符,这样通过循环多线程利用accept产生多socket描述符就可以与多个client进行连接通信。
7、IOCP
传统的C/S模式通常为每个客户端创建一个新的线程,这种实现方式会随着用户量的增大而效率急剧下降。IOCP的引入解决了这种高并发的异步I/O请求,是一种高性能的I/O模型。
IOCP本质就是事先创建好N个线程,让它们先挂起,之后将用户的请求投递到一个消息队列中。然后让这N个线程逐一地从消息队列中取出消息并进行处理。这样一来,不仅减少了线程创建的开销,同时提高了线程的利用率。
处理流程:
首先创建一个完成端口,然后再创建一个或多个工作线程,并指定它们到这个完成端口上去读取数据。然后将远程连接的套接字句柄关联到这个完成端口,工作线程调用getQueuedCompletionStatus在关联到这个完成端口上的所有套接字上等待I/O的完成,最后发出send和recv,并继续下一次循环阻塞在getQueuedCompletionStatus。
-
创建一个完成端口
HANDLE completionPort =CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0);
-
创建一个线程threadA
-
threadA循环调用GetQueuedCompletionStatus得到I/O的结果
while(true){
bRet= GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,(PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&IpOverlapped, INFINITE);
}
-
主线程循环调用accept等待客户端连上
-
主线程accept返回新连接建立以后,把这个新的套接字句柄关联到完成端口上,然后发出一个异步的read或write操作
-
主线程继续下一次循环,阻塞在accept等待客户连接
-
操作系统完成read或write操作,并把结果发给完成端口
-
ThreadA线程里的GetQueuedCompletionStatus马上返回,并从完成端口中取得刚才完成的read/write结果
-
在 ThreadA 线程里对这些数据进行处理,然后接着发出 Read/Write,并继续下一次循环阻塞在 GetQueuedCompletionStatus这里。