tcp三次握手和四次揮手瀏覽器
咱們知道網絡層,能夠實現兩個主機之間的通訊。可是這並不具體,由於,真正進行通訊的實體是在主機中的進程,是一個主機中的一個進程與另一個主機中的一個進程在交換數據。IP協議雖然能把數據報文送到目的主機,可是並無交付給主機的具體應用進程。而端到端的通訊才應該是應用進程之間的通訊。緩存
UDP,在傳送數據前不須要先創建鏈接,遠地的主機在收到UDP報文後也不須要給出任何確認。雖然UDP不提供可靠交付,可是正是由於這樣,省去和不少的開銷,使得它的速度比較快,好比一些對實時性要求較高的服務,就經常使用的是UDP。對應的應用層的協議主要有 DNS,TFTP,DHCP,SNMP,NFS 等。服務器
TCP,提供面向鏈接的服務,在傳送數據以前必須先創建鏈接,數據傳送完成後要釋放鏈接。所以TCP是一種可靠的的運輸服務,可是正由於這樣,不可避免的增長了許多的開銷,好比確認,流量控制等。對應的應用層的協議主要有 SMTP,TELNET,HTTP,FTP 等。網絡
三次握手:socket
1.TCP服務器進程先建立傳輸控制塊TCB,時刻準備接受客戶進程的鏈接請求,此時服務器就進入了 LISTEN(監聽)狀態;tcp
2.TCP客戶進程也是先建立傳輸控制塊TCB,而後向服務器發出鏈接請求報文,這是報文首部中的同部位SYN=1,同時選擇一個初始序列號 seq=x ,此時,TCP客戶端進程進入了 SYN-SENT(同步已發送狀態)狀態。TCP規定,SYN報文段(SYN=1的報文段)不能攜帶數據,但須要消耗掉一個序號。學習
3.TCP服務器收到請求報文後,若是贊成鏈接,則發出確認報文。確認報文中應該 ACK=1,SYN=1,確認號是ack=x+1,同時也要爲本身初始化一個序列號 seq=y,此時,TCP服務器進程進入了SYN-RCVD(同步收到)狀態。這個報文也不能攜帶數據,可是一樣要消耗一個序號。spa
4.TCP客戶進程收到確認後,還要向服務器給出確認。確認報文的ACK=1,ack=y+1,本身的序列號seq=x+1,此時,TCP鏈接創建,客戶端進入ESTABLISHED(已創建鏈接)狀態。TCP規定,ACK報文段能夠攜帶數據,可是若是不攜帶數據則不消耗序號。code
5.當服務器收到客戶端的確認後也進入ESTABLISHED狀態,此後雙方就能夠開始通訊了。 orm
四次揮手:
數據傳輸完畢後,雙方均可釋放鏈接。最開始的時候,客戶端和服務器都是處於ESTABLISHED狀態,而後客戶端主動關閉,服務器被動關閉。服務端也能夠主動關閉,一個流程。
1.客戶端進程發出鏈接釋放報文,而且中止發送數據。釋放數據報文首部,FIN=1,其序列號爲seq=u(等於前面已經傳送過來的數據的最後一個字節的序號加1),此時,客戶端進入FIN-WAIT-1(終止等待1)狀態。 TCP規定,FIN報文段即便不攜帶數據,也要消耗一個序號。
2.服務器收到鏈接釋放報文,發出確認報文,ACK=1,ack=u+1,而且帶上本身的序列號seq=v,此時,服務端就進入了CLOSE-WAIT(關閉等待)狀態。TCP服務器通知高層的應用進程,客戶端向服務器的方向就釋放了,這時候處於半關閉狀態,即客戶端已經沒有數據要發送了,可是服務器若發送數據,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。
3.客戶端收到服務器的確認請求後,此時,客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送鏈接釋放報文(在這以前還須要接受服務器發送的最後的數據)。
4.服務器將最後的數據發送完畢後,就向客戶端發送鏈接釋放報文,FIN=1,ack=u+1,因爲在半關閉狀態,服務器極可能又發送了一些數據,假定此時的序列號爲seq=w,此時,服務器就進入了LAST-ACK(最後確認)狀態,等待客戶端的確認。
5.客戶端收到服務器的鏈接釋放報文後,必須發出確認,ACK=1,ack=w+1,而本身的序列號是seq=u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。注意此時TCP鏈接尚未釋放,必須通過2∗∗MSL(最長報文段壽命)的時間後,當客戶端撤銷相應的TCB後,才進入CLOSED狀態。
6.服務器只要收到了客戶端發出的確認,當即進入CLOSED狀態。一樣,撤銷TCB後,就結束了此次的TCP鏈接。能夠看到,服務器結束TCP鏈接的時間要比客戶端早一些。
基於TCP和UDP兩個協議下socket的通信流程
1.TCP和UDP對比
TCP(Transmission Control Protocol)可靠的、面向鏈接的協議(eg:打電話)、傳輸效率低全雙工通訊(發送緩存&接收緩存)、面向字節流。使用TCP的應用:Web瀏覽器;文件傳輸程序。
UDP(User Datagram Protocol)不可靠的、無鏈接的服務,傳輸效率高(發送前時延小),一對1、一對多、多對1、多對多、面向報文(數據包),盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統 (DNS);視頻流;IP語音(VoIP)。
直接看圖對比其中差別
上代碼感覺一下,須要建立兩個文件,文件名稱隨便起,爲了方便看,個人兩個文件名稱爲tcp_server.py(服務端)和tcp_client.py(客戶端),將下面的server端的代碼拷貝到tcp_server.py文件中,將下面client端的代碼拷貝到tcp_client.py的文件中,而後先運行tcp_server.py文件中的代碼,再運行tcp_client.py文件中的代碼,而後在pycharm下面的輸出窗口看一下效果。
server端代碼示例(若是比喻成打電話)
import socket sk = socket.socket() sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字 sk.listen() #監聽連接 conn,addr = sk.accept() #接受客戶端連接 ret = conn.recv(1024) #接收客戶端信息 print(ret) #打印客戶端信息 conn.send(b'hi') #向客戶端發送信息 conn.close() #關閉客戶端套接字 sk.close() #關閉服務器套接字(可選)
client端代碼示例
import socket sk = socket.socket() # 建立客戶套接字 sk.connect(('127.0.0.1',8898)) # 嘗試鏈接服務器 sk.send(b'hello!') ret = sk.recv(1024) # 對話(發送/接收) print(ret) sk.close() # 關閉客戶套接字
socket綁定IP和端口時可能出現下面的問題:
解決辦法:
#加入一條socket配置,重用ip和端口 import socket from socket import SOL_SOCKET,SO_REUSEADDR sk = socket.socket() sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #在bind前加,容許地址重用 sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字 sk.listen() #監聽連接 conn,addr = sk.accept() #接受客戶端連接 ret = conn.recv(1024) #接收客戶端信息 print(ret) #打印客戶端信息 conn.send(b'hi') #向客戶端發送信息 conn.close() #關閉客戶端套接字 sk.close() #關閉服務器套接字(可選)
可是若是你加上了上面的代碼以後仍是出現這個問題:OSError: [WinError 10013] 以一種訪問權限不容許的方式作了一個訪問套接字的嘗試。那麼只能換端口了,由於你的電腦不支持端口重用。
記住一點,用socket進行通訊,必須是一收一發對應好。
提一下:網絡相關或者須要和電腦上其餘程序通訊的程序才須要開一個端口。
在看UDP協議下的socket以前,咱們還須要加一些內容來說:看代碼
server端
import socket from socket import SOL_SOCKET,SO_REUSEADDR sk = socket.socket() # sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) sk.bind(('127.0.0.1',8090)) sk.listen() conn,addr = sk.accept() #在這阻塞,等待客戶端過來鏈接 while True: ret = conn.recv(1024) #接收消息 在這仍是要阻塞,等待收消息 ret = ret.decode('utf-8') #字節類型轉換爲字符串中文 print(ret) if ret == 'bye': #若是接到的消息爲bye,退出 break msg = input('服務端>>') #服務端發消息 conn.send(msg.encode('utf-8')) if msg == 'bye': break conn.close() sk.close()
client端
import socket sk = socket.socket() sk.connect(('127.0.0.1',8090)) #鏈接服務端 while True: msg = input('客戶端>>>') #input阻塞,等待輸入內容 sk.send(msg.encode('utf-8')) if msg == 'bye': break ret = sk.recv(1024) ret = ret.decode('utf-8') print(ret) if ret == 'bye': break sk.close()
你會發現,第一個鏈接的客戶端能夠和服務端收發消息,可是第二個鏈接的客戶端發消息服務端是收不到的
import socket from socket import SOL_SOCKET,SO_REUSEADDR sk = socket.socket() # sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #容許地址重用,這個東西都說能解決問題,我很是不建議你們這麼作,容易出問題 sk.bind(('127.0.0.1',8090)) sk.listen() # 第二步演示,再加一層while循環 while True: #下面的代碼所有縮進進去,也就是循環創建鏈接,可是無論怎麼聊,只能和一個聊,也就是另一個優雅的斷了以後才能和另一個聊 #它不能同時和好多人聊,仍是長鏈接的緣由,一直佔用着這個端口的鏈接,udp是能夠的,而後咱們學習udp conn,addr = sk.accept() #在這阻塞,等待客戶端過來鏈接 while True: ret = conn.recv(1024) #接收消息 在這仍是要阻塞,等待收消息 ret = ret.decode('utf-8') #字節類型轉換爲字符串中文 print(ret) if ret == 'bye': #若是接到的消息爲bye,退出 break msg = input('服務端>>') #服務端發消息 conn.send(msg.encode('utf-8')) if msg == 'bye': break conn.close()
client端代碼
import socket sk = socket.socket() sk.connect(('127.0.0.1',8090)) #鏈接服務端 while True: msg = input('客戶端>>>') #input阻塞,等待輸入內容 sk.send(msg.encode('utf-8')) if msg == 'bye': break ret = sk.recv(1024) ret = ret.decode('utf-8') print(ret) if ret == 'bye': break # sk.close()
強制斷開鏈接以後的報錯信息:
總結一下UDP下的socket通信流程
先從服務器端提及。服務器端先初始化Socket,而後與端口綁定(bind),recvform接收消息,這個消息有兩項,消息內容和對方客戶端的地址,而後回覆消息時也要帶着你收到的這個客戶端的地址,發送回去,最後關閉鏈接,一次交互結束
上代碼感覺一下,須要建立兩個文件,文件名稱隨便起,爲了方便看,個人兩個文件名稱爲udp_server.py(服務端)和udp_client.py(客戶端),將下面的server端的代碼拷貝到udp_server.py文件中,將下面cliet端的代碼拷貝到udp_client.py的文件中,而後先運行udp_server.py文件中的代碼,再運行udp_client.py文件中的代碼,而後在pycharm下面的輸出窗口看一下效果。
server端代碼示例
import socket udp_sk = socket.socket(type=socket.SOCK_DGRAM) #建立一個服務器的套接字 udp_sk.bind(('127.0.0.1',9000)) #綁定服務器套接字 msg,addr = udp_sk.recvfrom(1024) print(msg) udp_sk.sendto(b'hi',addr) # 對話(接收與發送) udp_sk.close() # 關閉服務器套接字
client端代碼示例
import socket ip_port=('127.0.0.1',9000) udp_sk=socket.socket(type=socket.SOCK_DGRAM) udp_sk.sendto(b'hello',ip_port) back_msg,addr=udp_sk.recvfrom(1024) print(back_msg.decode('utf-8'),addr)