python網絡編程(socket)

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()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章