一:TCP連接之三次握手與四次揮手
TCP是一種面向連接的、可靠的、基於字節流的傳輸層通信協議。所以,兩臺遵循TCP的主機在彼此交換數據包之前必須先建立一個TCP連接。
TCP通過三次握手建立連接:
1,客戶端發送SYN(SEQ=x)報文給服務器端,進入SYN_SEND狀態。
2,服務器端收到SYN報文,迴應一個SYN (SEQ=y)ACK(ACK=x+1)報文,進入SYN_RECV狀態。
3,客戶端收到服務器端的SYN報文,迴應一個ACK(ACK=y+1)報文,進入Established狀態。
三次握手完成,TCP客戶端和服務器端成功地建立連接,可以開始傳輸數據了。
TCP終止一個連接要經過四次揮手,這是由TCP的半關閉(half-close)造成的:
1,某個應用進程首先調用close,稱該端執行“主動關閉”(active close)。該端的TCP於是發送一個FIN分節,表示數據發送完畢。
2, 接收到這個FIN的對端執行 “被動關閉”(passive close),這個FIN由TCP確認。
FIN的接收也作爲一個文件結束符(end-of-file)傳遞給接收端應用進程,放在已排隊等候該應用進程接收的任何其他數據之後
3,一段時間後,接收到這個文件結束符的應用進程將調用close關閉它的套接字。這導致它的TCP也發送一個FIN
4,接收這個最終FIN的原發送端TCP(即執行主動關閉的那一端)確認這個FIN。
在步驟2與步驟3之間,從執行被動關閉一端到執行主動關閉一端流動數據是可能的,這稱爲“半關閉”(half-close)。
二:何謂 "套接字"--socket
伯克利套接字(Internet Berkeley sockets) ,又稱爲BSD 套接字(BSD sockets)是一種應用程序接口(API),用於網際套接字( socket)與Unix域套接字,包括了一個用C語言寫成的應用程序開發庫,主要用於實現進程間通訊,在計算機網絡通訊方面被廣泛使用。
Berkeley套接字應用程序接口形成了事實上的網絡套接字的標準精髓。這套應用程序接口也被用於Unix域套接字(Unix domain sockets),後者可以在單機上爲進程間通訊(IPC)的接口。
本篇主要講的是網際套接字socket,它大多源自Berkeley套接字標準。
socket是一種操作系統提供的進程間通信機制,也是操作系統爲應用程序提供的一種應用程序接口(API)
在套接字接口中,以IP地址及通信端口組成套接字地址(socket address)。遠程的套接字地址,以及本地的套接字地址完成連接後,再加上使用的協議(protocol),這個五元組(five-element tuple),作爲套接字對(socket pairs),之後就可以彼此交換數據
socket本質是編程接口(API),是對TCP/IP協議族傳輸層及其以下層的封裝
通過socket這個編程接口,可以實現端口到端口的通信,即能實現任何應用程序之間的通信
三:python中socket的實現--socket模塊
TCP是一種面向連接的、可靠的、基於字節流的傳輸層通信協議。
#創建socket對象最常用方法(還有很多其他創建方法)
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
family(不全):socket.AF_INET IPv4(默認)
socket.AF_INET6 IPv6
socket.AF_UNIX 只能夠用於單一的Unix系統進程間通信
type(不全):
socket.SOCK_STREAM 流式socket , for TCP (默認)
socket.SOCK_DGRAM 數據報式socket , for UDP
socket.SOCK_RAW 原始套接字
常用方法:
socket.bind(address)
Bind the socket to address.
socket.listen([backlog])
Enable a server to accept connections.
If not specified, a default reasonable value is chosen--for backlog
socket.accept()
Accept a connection;The return value is a pair (conn, address) where conn is a new socket object
usable to send and receive data on the connection, and address is the address bound to the socket
on the other end of the connection
socket.connect(address)
Connect to a remote socket at address.
socket.send(bytes[, flags]) TCP method
Send data to the socket. The socket must be connected to a remote socket
Returns the number of bytes sent. Applications are responsible for checking that all data has been sent;
if only some of the data was transmitted, the application needs to attempt delivery of the remaining data
socket.sendall(bytes[, flags])
Send data to the socket. The socket must be connected to a remote socket.
this method continues to send data from bytes until either all data has been sent or an error occurs.
None is returned on success.
socket.sendto(bytes[, flags], address) UDP method
Send data to the socket. The socket should not be connected to a remote socket, since the destination socket
is specified by address.Return the number of bytes sent
socket.recv(bufsize[, flags])
Receive data from the socket. The return value is a bytes object representing the data received.
Note: For best match with hardware and network realities, the value of bufsize should be a relatively
small power of 2, for example, 4096
socket.recvfrom(bufsize[, flags])
Receive data from the socket. The return value is a pair (bytes, address)
socket.close()
Mark the socket closed
Note: close() releases the resource associated with a connection but does not necessarily close the
connection immediately. If you want to close the connection in a timely fashion, call shutdown() before close().
#服務器端 Sever
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080)) # 服務器需要綁定地址
server.listen(5) # 可以掛起的最大連接請求數量,不傳則自動選擇一個合理的默認值
while True:
connection,client_addr = server.accept() # 建立連接
print("客戶端地址:",client_addr)
while True:
try:
msg = connection.recv(1024) # 1024表示一次從內存中取1024字節的數據
if not msg:break # 針對linux系統
connection.send(msg.upper())
except ConnectionError: # 捕捉客戶端斷開連接異常
break
connection.close() # 斷開連接
server.close() # 關閉socket對象
# 客戶端 Client
import socket
client = socket.socket() # 默認family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None
client.connect(('127.0.0.1',8080)) # 與服務器建連接
while True:
msg = input('>>:').strip()
if not msg:continue
if msg == 'q':
break
client.send(msg.encode('utf-8')) #只能發送bytes類型的數據,所有這裏需要先編碼
data = client.recv(1024) # 接收到的是bytes類型
print(data.decode('utf-8'))
client.close()
下圖模擬了5臺客戶端依次訪問服務器的過程: UDP是一種無連接的,不可靠的,面向報文的傳輸層通信協議
# UDP Server
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))
while True:
msg,client_addr= server.recvfrom(1024)
print(msg.decode('utf-8'),client_addr) # hello ('127.0.0.1', 51705)
server.sendto(msg.upper(),client_addr)
# UDP Client
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
msg = input('>>:').strip()
client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
data,server_addr = client.recvfrom(1024)
print(data.decode('utf-8'),server_addr) # HELLO ('127.0.0.1', 8080)
四:TCP粘包現象及其處理方法
TCP粘包是指發送方發送的若干包數據到接收方接收時粘成一包,從接收緩衝區看,後一包數據的頭緊接着前一包數據的尾,其本質原因是TCP是基於字節流的,數據之間沒有相應分界
會造成粘包:
1發送端需要等緩衝區滿才發送出去,造成粘包
2接收方不及時接收緩衝區的包,造成多個包一起接收
爲TCP數據加上保護邊界,可預防粘包:
(1)發送固定長度的數據
(2)把數據的大小與數據一塊發送
(3)使用特殊標記來區分數據間隔,如給每個數據包加上一個定製的包頭