socket介紹
網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱爲一個socket。
建立網絡通信連接至少要一對端口號(socket)。socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員做網絡開發所用的接口,這就是Socket編程接口;HTTP是轎車,提供了封裝或者顯示數據的具體形式;Socket是發動機,提供了網絡通信的能力。
Socket的英文原義是“孔”或“插座”。作爲BSD UNIX的進程通信機制,取後一種意思。通常也稱作"套接字",用於描述IP地址和端口,是一個通信鏈的句柄,可以用來實現不同虛擬機或不同計算機之間的通信。在Internet上的主機一般運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,並綁定到一個端口上,不同的端口對應於不同的服務。Socket正如其英文原義那樣,像一個多孔插座。一臺主機猶如佈滿各種插座的房間,每個插座有一個編號,有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節目。 客戶軟件將插頭插到不同編號的插座,就可以得到不同的服務。
Socket非常類似於電話插座。以一個國家級電話網爲例,電話的通話雙方相當於相互通信的2個進程,區號是它的網絡地址;區內一個單位的交換機相當於一臺主機,主機分配給每個用戶的局內號碼相當於Socket號。任何用戶在通話之前,首先要佔有一部電話機,相當於申請一個Socket;同時要知道對方的號碼,相當於對方有一個固定的Socket。然後向對方撥號呼叫,相當於發出連接請求(假如對方不在同一區內,還要撥對方區號,相當於給出網絡地址)。假如對方在場並空閒(相當於通信的另一主機開機且可以接受連接請求),拿起電話話筒,雙方就可以正式通話,相當於連接成功。雙方通話的過程,是一方向電話機發出信號和對方從電話機接收信號的過程,相當於向Socket發送數據和從socket接收數據。通話結束後,一方掛起電話機相當於關閉Socket,撤消連接。
socket編程
socket協議
TCP: 面向連接的通信協議
建立連接:三次握手
(1) 第一次握手:Client端(客戶端)調用connect函數調用,系統爲Client隨機分配一個端口,連同傳入connect中的參數(Server的IP和端口),這就形成了一個連接四元組,客戶端發送一個帶SYN:同步序列編號(Synchronize Sequence Numbers)標誌的TCP報文到服務器。這是三次握手過程中的報文1。connect調用讓Client端的socket處於SYN_SENT(請求鏈接)狀態,等待服務器確認。
注:如果連接成功了就變爲ESTABLISHED(確定的),此時SYN_SENT狀態非常短暫。
(2)第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
注:在TCP/IP協議中,如果接收方成功的接收到數據,那麼會回覆一個ACK數據。
注: ACK (Acknowledgement)即是確認字符,在數據通信中,接收站發給發送站的一種傳輸類控制字符。表示發來的數據已確認接收無誤。
(3) 第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶器和客務器進入ESTABLISHED狀態,完成三次握手。連接已經可以進行讀寫操作。
斷開連接:四次揮手
(1) 當client想要關閉它與server之間的連接。client(某個應用進程)首先調用close主動關閉連接,這時TCP發送一個FIN M;client端處於FIN_WAIT1狀態。
注:FIN是用來掃描保留的端口,發送一個FIN包(或者是任何沒有ACK或SYN標記的包)到目標的一個開放的端口,然後等待迴應。許多系統會返回一個復位標記。
(2) 當server端接收到FIN M之後,執行被動關閉。對這個FIN進行確認,返回給client ACK。當server端返回給client ACK後,client處於FIN_WAIT2狀態,server處於CLOSE_WAIT狀態。它的接收也作爲文件結束符傳遞給應用進程,因爲FIN的接收意味着應用進程在相應的連接上再也接收不到額外數據;
(3) 一段時間之後,當server端檢測到client端的關閉操作(read返回爲0)。接收到文件結束符的server端調用close關閉它的socket。這導致server端的TCP也發送一個FIN N;此時server的狀態爲LAST_ACK。
(4) 當client收到來自server的FIN後 。 client端的套接字處於TIME_WAIT狀態,它會向server端再發送一個ack確認,此時server端收到ack確認後,此套接字處於CLOSED狀態。這樣每個方向上都有一個FIN和ACK。
UDP:無連接的協議
UDP:協議傳輸容易丟包,成本低,但是速度快.
1. 通信步驟
服務器端先初始化Socket,然後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然後連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求並處理請求,然後把迴應數據發送給客戶端,客戶端讀取數據,最後關閉連接,一次交互結束。
被動:當服務器開啓,不會主動訪問客戶端,只是被動等待請求
阻塞:當有一個用戶與服務端發起通信的時候,這個時候信道阻塞,其他用戶無法訪問
注:TCP發送數據時,已經建立好TCP連接,所以不需要指定地址,UDP是面向無連接的,所以每次發送要指定是發送給誰
服務端與客戶端之間發送的數據必須是字符串
2. 通信方式
單 工 通信雙方只有一條信道,且通信身份不可逆。 BB機
半雙工 通信雙方只有一條信道,且通信身份可逆。 對講機
全雙工 通信雙方只有多條信道,且通信身份可逆。 手機
socket tcp
TCP服務端
1、創建套接字
sock = socket.socket(socket.AF_INET,sokcet.SOCK_STREAM)
2、綁定套接字到本地IP與端口
sock.bind(())
3、開始監聽
sock.listen()
4、接受客戶端的連接請求
sock.accept()
5、接收傳來的數據,併發送給客戶端數據
sock.recv() sock.send()
6、傳輸完畢,關閉套接字
sock.close()
案例:
#導入套接字
import socket
#創建一個套接字,並且以tcp連接
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#綁定套接字,參數爲雙元素元組,如果寫空代表本地所有ip
#第二個參數是端口 0-65525
sock.bind(('',9000))
#監聽,最大端口爲5
sock.listen(5)
#接收連接請求
#conntent 用來接收請求用戶的消息和發送對該用戶的的消息功能
#address 是請求用戶的身份(ip,port)
content,address = sock.accept()
print('%s:%s id connected……'%address)
#發送數據,數據必須是字節的形式
content.send('hello'.encode())
#接收數據參數是字節的形式
print(content.recv(521))
#關閉套接字
sock.close()
注:AF_INET(又稱 PF_INET)是 IPv4 網絡協議的套接字類型
TCP 客戶端
1. 創建套接字
sock = socket.socket(socket.AF_INET,sokcet.SOCK_STREAM)
2. 連接服務端的地址和端口
sock.connect(())
3. 連接後發送數據和接受數據
sock.recv() sock.send()
4. 傳輸完畢,關閉套接字
sock.close()
案例:
import socket
#創建一個套接字
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#套接字連接
sock.connect(('127.0.0.1',9000))
#接收數據
print(sock.recv(521))
#發送數據
sock.send('Hi'.encode())
#關閉套接字
sock.close()
socket UDP
爲方便理解,假設爲服務端
1.創建套接字(SOCK_DGRAM數據報)
sock = socket.socket(socket.AF_INET,sokcet.SOCK_DGRAM)
2.綁定套接字到本地IP與端口
sock.bind(())
3.發送接收數據(需要指定發送給和接收具體的地址端口)
sock.sendto()
sock.recvfrom()
4.傳輸完畢,關閉套接字
sock.close()
創建server端
案例:
import socket
#創建一個個套接字 UDP
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#綁定端口
sock.bind(('',9099))
#接收信息,data是客戶端發送的信息,address是ip和端口
data,address = sock.recvfrom(521)
#發送數據,到指定地址
sock.sendto('server'.encode(),('127.0.0.1',8001))
#打印信息和地址
print(data.decode(),address)
#關閉套接字
sock.close()
爲方便理解,假設爲客戶端
1、創建套接字
sock = socket.socket(socket.AF_INET,sokcet.SOCK_DGRAM)
2、綁定套接字到本地IP與端口
sock.bind(())
3、發送接收數據(需要指定發送給誰)
sock.sendto()
sock.recvfrom()
4、傳輸完畢,關閉套接字
sock.close()
案例:
import socket
#創建一個套接字
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#綁定端口
sock.bind(('127.0.0.1',8001))
#發送信息到一個地址
sock.sendto('client'.encode(),('127.0.0.1',9099))
#得到服務端發送的信息
data,address = sock.recvfrom(521)
#打印響應的信息和地址
print(data.decode(),address)
sock.close()
TCP/UDP通信
1. TCP 服務端
import socket
print('正在連接中……')
def dealclient(sock,addr):
info = sock.recv(1024).decode()
while info != 'exit':
print('客戶端:'+info)
send_mes = input('>>>')
sock.send(send_mes.encode())
if send_mes =='exit':
break
info = sock.recv(1024).decode()
sock.close()
if __name__ == '__main__':
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 9001))
s.listen(1)
sock, addr=s.accept()
dealclient(sock,addr)
2. TCP 客戶端
import socket
s= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('127.0.0.1',9001))
print('已經建立連接……')
info = ''
while info != 'exit':
send_mes=input('>>>')
s.send(send_mes.encode())
if send_mes =='exit':
break
info = s.recv(1024).decode()
print('服務器:' + info)
s.close()
3. UDP server
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.bind(('127.0.0.1',9002))
print('wait ....')
while True:
data,addr = s.recvfrom(1024)
print('客戶端:'+data.decode())
info = input('>>>')
if data.decode() == 'exit':
break
s.sendto(info.encode(),addr)
s.close()
4.UDP client
import socket
s= socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.connect(('127.0.0.1',9002))
print('connection……')
info = ''
while info != 'exit':
send_mes=input('>>>')
s.sendall(send_mes.encode())
if send_mes =='exit':
break
info = s.recv(1024).decode()
print('服務端:' + str(info))
s.close()
TCP和UDP對比:
1.基於連接與無連接;
2.對系統資源的要求(TCP較多,UDP少);
3.UDP程序結構較簡單;
4.流模式與數據報模式 ;5.TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證。
SocketServer
SocketServer是socket的再封裝。
每個客戶端連接服務器時,SocketServer會在服務器上創建一線程或者進程,專門負責處理客戶端
Socketserver 是在socket的基礎上python編寫的用於編寫web服務的基礎框架
SocketServer內部使用 IO多路複用,以及 “多線程” 和 “多進程” ,從而實現併發處理多個客戶端請求的Socket服務端。即:每個客戶端請求連接到服務器時,Socket服務端都會在服務器是創建一個“線程”或者“進程” 專門負責處理當前客戶端的所有請求。
在py2.x版本當中爲 SocketServer
在py3.x版本當中爲 socketserver
SocketServer中類分爲三種類型
一是Server類:BaseServer/TCPServer/UDPServer用來接收客戶的請求。TCPServer處理TCP請求,UDPServer處理UDP請求。BaserServer是基類,不能直接使用。TCPServer繼承自BaseServer,UDPServer繼承自TCPServer。暫時不明白爲什麼UDPServer要繼承自TCPServer,後面再說。
二是Handler類:BaseRequestHandler/DatagramRequestHandler/StreamRequestHandler用來處理每一個客戶請求。一般用使用BaseRequestHandler就行。
但StreamRequestHandler/DatagramRequestHandler提供了一些特別的功能,前者用來處理流式(TCP)請求,後者處理數據報(UDP)請求。Server每收到一個客戶請求就會創建一個Handler類示例來處理該請求。默認情況下,TCPServer/UDPServer是單進程單線程的模型,依次處理每個客戶請求,一個請求處理完畢才能接着處理下一個請求。
三是MixIn類:ForkingMixIn/ThreadingMixIn用來爲Server提供多進程/多線程併發處理能力的。ForkingMixIn是多進程模型,ThreadingMixin是多線程模型。這裏特別巧妙的是,你只要創建一個類,同時繼承Server類和MixIn類就能自動獲得併發處理請求的能力。該模塊本身就直接提供了這種類。
服務端代碼:
import socketserver
#創建一個類,繼承BaseRwequsetHanler
class MYHandle(socketserver.BaseRequestHandler):
#類似於析構函數__init__
def setup(self):
print('myhandle is start ')
#用來處理邏輯
def handle(self):
#self.server 當前服務
#self.client_address 客戶端的身份(ip.port
#self.request 用來接收和發送數據
print(self.server)
print('%s:%s is connect '%self.client_address)
recv = self.request.recv(521)
print(recv)
self.request.send('i am server '.encode())
#類似於__del__
def finish(self):
print('myhandle is stop ')
if __name__ == '__main__':
#第一個參數來綁定ip
#第二個位開啓對象
server = socketserver.ThreadingTCPServer(('127.0.0.1',8000),MYHandle)
#開啓服務,永遠
server.serve_forever()
客戶端代碼(客戶端還是使用socket)
import socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(('127.0.0.1',8000))
sock.send('client2'.encode())
print(sock.recv(521).decode())
sock.close()