Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。html
簡而言之:node
其實站在你的角度上看,socket就是一個模塊。咱們經過調用模塊中已經實現的方法創建兩個進程之間的鏈接和通訊。
也有人將socket說成ip+port,由於ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程序。
因此咱們只要確立了ip和port就能找到一個應用程序,而且使用socket模塊來與之通訊。
套接字得發展史: (套接字分爲兩類,文件類型和網絡類型)python
(文件)套接字家族的名字:AF_UNIX(起源於UNIX)web
unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊編程
(網絡)套接字家族的名字:AF_INETwindows
(還有AF_INET6被用於ipv6,還有一些其餘的地址家族,不過,他們要麼是隻用於某個平臺,要麼就是已經被廢棄,或者是不多被使用,或者是根本沒有實現,全部地址家族中,AF_INET是使用最普遍的一個,python支持不少種地址家族,可是因爲咱們只關心網絡編程,因此大部分時候我麼只使用AF_INET)設計模式
TCP(Transmission Control Protocol)可靠的、面向鏈接的協議(eg:打電話)、傳輸效率低全雙工通訊(發送緩存&接收緩存)、面向字節流。使用TCP的應用:Web瀏覽器;電子郵件、文件傳輸程序。瀏覽器
UDP(User Datagram Protocol)不可靠的、無鏈接的服務,傳輸效率高(發送前時延小),一對1、一對多、多對1、多對多、面向報文,盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統 (DNS);視頻流;IP語音(VoIP)緩存
具體的三次握手,四次揮手過程:服務器
三次握手:
SYN_SEND
狀態,等待服務器(B)的確認;主機B由SYN=1知道,A要求創建聯機;SYN_RECV
狀態;ESTABLISHED
狀態,完成TCP三次握手問題一:爲何鏈接創建須要三次握手,而不是兩次握手?
四次揮手:
Sequence Number
和Acknowledgment Number
,向主機2發送一個FIN
報文段;此時,主機1進入FIN_WAIT_1
狀態;這表示主機1沒有數據要發送給主機2了;FIN
報文段,向主機1回一個ACK
報文段,Acknowledgment Number
爲Sequence Number
加1;主機1進入FIN_WAIT_2
狀態;主機2告訴主機1,我也沒有數據要發送了,能夠進行關閉鏈接了;FIN
報文段,請求關閉鏈接,同時主機2進入CLOSE_WAIT
狀態;FIN
報文段,向主機2發送ACK
報文段,而後主機1進入TIME_WAIT
狀態;主機2收到主機1的ACK
報文段之後,就關閉鏈接;此時,主機1等待2MSL後依然沒有收到回覆,則證實Server端已正常關閉,那好,主機1也能夠關閉鏈接了問題一:.爲何TIME_WAIT狀態須要通過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?
問題二:lient發送完最後一個ack以後,進入time_wait狀態,可是他怎麼知道server有沒有收到這個ack呢?莫非sever也要等待一段時間,若是收到了這個ack就close,若是沒有收到就再發一個fin給client?這麼說server最後也有一個time_wait哦?求解答
答:由於網絡緣由,主動關閉的一方發送的這個ACK包極可能延遲,從而觸發被動鏈接一方重傳FIN包。極端狀況下,這一去一回,就是兩倍的MSL時長。若是主動關閉的一方跳過TIME_WAIT直接進入CLOSED,或者在TIME_WAIT停留的時長不足兩倍的MSL,那麼當被動
關閉的一方早先發出的延遲包到達後,就可能出現相似下面的問題:1.舊的TCP鏈接已經不存在了,系統此時只能返回RST包2.新的TCP鏈接被創建起來了,延遲包可能干擾新的鏈接,這就是爲何time_wait須要等待2MSL時長的緣由。
問題三:爲何要四次揮手?
答:確保數據可以完整傳輸。
當被動方收到主動方的FIN報文通知時,它僅僅表示主動方沒有數據再發送給被動方了。但未必被動方全部的數據都完整的發送給了主動方,因此被動方不會立刻關閉SOCKET,它可能還須要發送一些數據給主動方後,再發送FIN報文給主動方,告訴主動方贊成關閉鏈接,因此這裏的ACK報文和FIN報文多數狀況下都是分開發送的
xxxxxxxxxx
總結:
TCP協議:
一、 面向鏈接\可靠\慢\對傳遞的數據的長短沒有要求
二、 兩臺機器之間要想傳遞信息必須先創建鏈接
三、 以後在有了鏈接的基礎上,進行信息的傳遞
四、可靠 : 數據不會丟失 不會重複被接收
五、慢 : 每一次發送的數據還要等待結果
六、 三次握手和四次揮手
UDP協議:
一、無鏈接\不可靠\快\不能傳輸過長的數據0
二、機器之間傳遞信息不須要創建鏈接 直接發就行
三、不可靠 : 數據有可能丟失
五層協議:
一、應用層 python send(b'hello,world')
# socket(虛擬,對下面的進行了整合,直接使用)
二、傳輸層 端口 tcp/udp協議 四層路由器 四層交換機
三、網絡層 ip地址相關 ip協議 路由器 三層交換機
四、數據鏈路層 mac地址相關 arp協議 網卡 二層交換機
五、物理層 網線
基於TCP協議的socket使用:
#基礎服務端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字
sk.listen() #監聽連接
conn,addr = sk.accept() #接受客戶端連接
ret = conn.recv(1024) #接收客戶端信息
print(ret) #打印客戶端信息,須要時要解碼decode
conn.send(b'hi') #向客戶端發送信息,編碼必要時
conn.close() #關閉客戶端套接字,不是closed
sk.close() #關閉服務器套接字(可選)
#基礎客服端
import socket
sk = socket.socket() # 建立客戶套接字
sk.connect(('127.0.0.1',8898)) # 嘗試鏈接服務器,0,0,0,1是全網段,全部來防本機的均可以
sk.send(b'hello!')
ret = sk.recv(1024) # 對話(發送/接收)
print(ret)
sk.close() # 關閉客戶套接字
#注意,在服務端啓動時,若是遇到,Address alreadly in use,則須要在bind前加sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
#TCP的聊天版本服務端:可多鏈接,可是必須一對一回復
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
while True:
conn,addr = sk.accept() # 等待用戶來鏈接我
while True:
msg = input('>>>')
conn.send(msg.encode('utf-8'))
if msg.upper() == 'Q':
break
content = conn.recv(1024).decode('utf-8') # 等待 客戶端給我φ消息
if content.upper() == 'Q': break
print(content)
conn.close()
sk.close()
#TCP客戶端:
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
while True:
ret = sk.recv(1024).decode('utf-8')
if ret.upper() == 'Q':break
print(ret)
msg = input('>>>')
sk.send(msg.encode('utf-8'))
if msg.upper() == 'Q':
break
sk.close()
基於UDP協議的socket的使用:
xxxxxxxxxx
#基礎服務端
import socket
udp_sk= socket.socket(type=socket.SOCK_DGRAM) #建立一個服務器的套接字
udp_sk.bind(('127.0.0.1',9000)) #綁定服務器套接字
msg,addr = udp_sk.recvfrom(1024) # 若是有from則須要addr接收地址
print(msg)
udp_sk.sendto(b'hi',addr) # 對話(接收與發送)
udp_sk.close()
#基礎客服端
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)
#進階服務端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9001))
while True:
msg,client_addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
content = input('>>>')
sk.sendto(content.encode('utf-8'),client_addr)
sk.close()
#進階客戶端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
server_addr = ('127.0.0.1',9001)
while True:
content = input('>>>')
if content.upper() == 'Q':break
sk.sendto(content.encode('utf-8'),server_addr)
msg = sk.recv(1024).decode('utf-8')
if msg.upper() == 'Q':break
print(msg)
sk.close()
#高級QQ服務端
import socket
ip_port=('127.0.0.1',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_server_sock.bind(ip_port)
while True:
qq_msg,addr=udp_server_sock.recvfrom(1024)
print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
back_msg=input('回覆消息: ').strip()
udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
#高級QQ服務端
import socket
BUFSIZE=1024
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
qq_name_dic={
'金老闆':('127.0.0.1',8081),
'哪吒':('127.0.0.1',8081),
'egg':('127.0.0.1',8081),
'yuan':('127.0.0.1',8081),
}
while True:
qq_name=input('請選擇聊天對象: ').strip()
while True:
msg=input('請輸入消息,回車發送,輸入q結束和他的聊天: ').strip()
if msg == 'q':break
if not msg or not qq_name or qq_name not in qq_name_dic:continue
udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])
back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
udp_client_socket.close()
xxxxxxxxxx
服務端套接字函數
s.bind() 綁定(主機,端口號)到套接字
s.listen() 開始TCP監聽
s.accept() 被動接受TCP客戶的鏈接,(阻塞式)等待鏈接的到來
客戶端套接字函數
s.connect() 主動初始化TCP服務器鏈接
s.connect_ex() connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
公共用途的套接字函數
s.recv() 接收TCP數據
s.send() 發送TCP數據
s.sendall() 發送TCP數據,發送一個字符串套接字,知道全部都發完,遞歸版的send
s.recvfrom() 接收UDP數據
s.sendto() 發送UDP數據
s.getpeername() 鏈接到當前套接字的遠端的地址
s.getsockname() 當前套接字的地址
s.getsockopt() 返回指定套接字的參數
s.setsockopt() 設置指定套接字的參數
s.close() 關閉套接字
面向鎖的套接字方法
s.setblocking() 設置套接字的阻塞與非阻塞模式
s.settimeout() 設置阻塞套接字操做的超時時間
s.gettimeout() 獲得阻塞套接字操做的超時時間
面向文件的套接字的函數
s.fileno() 返回套接字的文件描述符
s.makefile() 建立一個與該套接字相關的文件