Socket-C/S
Socket又稱"套接字",應用程序
通常通過"套接字"向網絡發出請求或者應答網絡請求,使主機間或者一臺計算機上的進程間可以通訊。
socket起源於UNIX,在Unix一切皆文件哲學的思想下,socket是一種"打開—讀/寫—關閉"模式的實現,服務器和客戶端各自維護一個"文件",在建立連接打開後,可以向自己文件寫入內容供對方讀取或者讀取對方內容,通訊結束時關閉文件。socket的英文原義是“插槽”或“插座”,就像我們家裏座機一樣,如果沒有網線的那個插口,電話是無法通信的。Socket是實現TCP,UDP協議的接口,便於使用TCP,UDP。
# 流程描述:
# 1 服務器根據地址類型(ipv4,ipv6)、socket類型、協議創建socket
# 2 服務器爲socket綁定ip地址和端口號
# 3 服務器socket監聽端口號請求,隨時準備接收客戶端發來的連接,這時候服務器的socket並沒有被打開
# 4 客戶端創建socket
# 5 客戶端打開socket,根據服務器ip地址和端口號試圖連接服務器socket
# 6 服務器socket接收到客戶端socket請求,被動打開,開始接收客戶端請求,直到客戶端返回連接信息。這時候socket進入阻塞狀態,
# 所謂阻塞即accept()方法一直等到客戶端返回連接信息後才返回,開始接收下一個客戶端連接請求
# 7 客戶端連接成功,向服務器發送連接狀態信息
# 8 服務器accept方法返回,連接成功
# 9 客戶端向socket寫入信息(或服務端向socket寫入信息)
# 10 服務器讀取信息(客戶端讀取信息)
# 11 客戶端關閉
# 12 服務器端關閉
Socket 對象(內建)方法
服務器端
s.bind()
# 綁定地址(host,port)到套接字,在AF_INET下,以元組(host,port)的形式表示地址。
s.listen()
# 開始TCP監聽。backlog指定在拒絕連接之前,操作系統可以掛起的最大連接數量。該值至少爲1,大部分應用程序設爲5就可以了。
s.accept()
# 被動接受TCP客戶端連接,(阻塞式)等待連接的到來
客戶端
s.connect()
# 主動初始化TCP服務器連接,。一般address的格式爲元組(hostname,port),如果連接出錯,返回socket.error錯誤。
s.connect_ex()
# connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
公共用途的函數
s.recv()
# 接收TCP數據,數據以字符串形式返回,bufsize指定要接收的最大數據量。flag提供有關消息的其他信息,通常可以忽略。
s.send()
# 發送TCP數據,將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。
s.sendall()
# 完整發送TCP數據,完整發送TCP數據。將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。
s.close()
# 關閉套接字
s.recvform()
# 接收UDP數據,與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。
s.sendto()
# 發送UDP數據,將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。
s.getpeername()
# 返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。
s.getsockname()
# 返回套接字自己的地址。通常是一個元組(ipaddr,port)
s.setsockopt(level,optname,value)
# 設置給定套接字選項的值。
s.getsockopt(level,optname[.buflen])
# 返回套接字選項的值。
s.settimeout(timeout)
# 設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因爲它們可能用於連接的操作(如connect())
s.gettimeout()
# 返回當前超時期的值,單位是秒,如果沒有設置超時期,則返回None。
s.fileno()
# 返回套接字的文件描述符。
s.setblocking(flag)
# 如果flag爲0,則將套接字設爲非阻塞模式,否則將套接字設爲阻塞模式(默認值)。非阻塞模式下,如果調用recv()沒有發現任何數據,或send()調用無法立即發送數據,那麼將引起socket.error異常。
s.makefile()
# 創建一個與該套接字相關連的文件
我們使用 socket 模塊的 socket 函數來創建一個 socket 對象。socket 對象可以通過調用其他函數來設置一個 socket 服務。
現在我們可以通過調用 bind(hostname, port) 函數來指定服務的 port(端口)。
接着,我們調用 socket 對象的 accept 方法。該方法等待客戶端的連接,並返回 connection 對象,表示已連接到客戶端。
import socket # 導入socket模塊
sk = socket.socket() # 創建socket對象
sk.bind(("127.0.0.1",8888)) # 綁定端口,“127.0.0.1”代表本機地址,8888爲設置鏈接的端口地址
sk.listen(5) # 設置監聽,最多可有5個客戶端進行排隊
conn, addr = sk.accept() # 阻塞狀態,被動等待客戶端的連接
print(conn) # conn可以理解客戶端的socket對象
# <socket.socket fd=4,family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0,laddr=('127.0.0.1', 9005), raddr=('127.0.0.1', 36694)>
print(addr) # addr爲客戶端的端口地址
# ('127.0.0.1', 40966)
accept_data = conn.recv(1024) # conn.recv()接收客戶端的內容,接收到的是bytes類型數據,
accept_data2 = str(accept_data,encoding="utf8") #str(data,encoding="utf8")用“utf8”進行解碼
print("".join(("接收內容:", accept_data2, " 客戶端口:",str(addr[1]))))
send_data = input("輸入發送內容:")
conn.sendall(bytes(send_data,encoding="utf8")) # 發送內容必須爲bytes類型數據,bytes(data,encoding="utf8")用“utf8”格式進行編碼
conn.close()
客戶端
socket.connect(hosname, port ) 方法打開一個 TCP 連接到主機爲 “127.0.0.1” 端口爲 port 的服務商。連接後我們就可以從服務端後期數據,記住,操作完成後需要關閉連接
import socket
sk = socket.socket()
sk.connect(("127.0.0.1",8888)) # 主動初始化與服務器端的連接
send_data = input("輸入發送內容:")
sk.sendall(bytes(send_data,encoding="utf8"))
accept_data = sk.recv(1024)
print(str(accept_data,encoding="utf8"))
sk.close()
以上只是實現了服務端一次的接收和發送,下面我們進行升級可以一直進行通訊
import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 9008))
sk.listen(5)
while True:
conn, addr = sk.accept()
while True:
accept_data = str(conn.recv(1024),
encoding="utf8")
print("".join(["接收內容:",accept_data, " 客戶端口:", str(addr[1])]))
if accept_data == "byebye": # 如果接收到“byebye”則跳出循環結束和第一個客戶端的通訊,開始與下一個客戶端進行通訊
break
send_data = input("輸入發送內p容:")
conn.sendall(bytes(send_data, encoding="utf8"))
conn.close() # 跳出循環時結束通訊
客戶端
import socket
sk = socket.socket()
sk.connect(("127.0.0.1",9008)) # 主動初始化與服務器端的連接
while True:
send_data = input("輸入發送內容:")
sk.sendall(bytes(send_data, encoding="utf8"))
if send_data == "byebye":
break
accept_data = str(sk.recv(1024), encoding="utf8")
print("".join(("接收內容:",accept_data)))
sk.close()
簡單併發實例
服務端
import socketserver # 導入socketserver模塊
classMyServer(socketserver.BaseRequestHandler): # 創建一個類,繼承自socketserver模塊下的BaseRequestHandler類
def handle(self): # 要想實現併發效果必須重寫父類中的handler方法,在此方法中實現服務端的邏輯代碼(不用再寫連接準備,包括bind()、listen()、accept()方法)
while 1:
conn = self.request
addr = self.client_address
# 上面兩行代碼,等於 conn,addr = socket.accept(),只不過在socketserver模塊中已經替我們包裝好了,還替我們包裝了包括bind()、listen()、accept()方法
while 1:
accept_data =str(conn.recv(1024), encoding="utf8")
print(accept_data)
if accept_data =="byebye":
break
send_data =bytes(input(">>>>>"), encoding="utf8")
conn.sendall(send_data)
conn.close()
if __name__ == '__main__':
sever = socketserver.ThreadingTCPServer(("127.0.0.1", 8888),
MyServer) # 傳入 端口地址 和 我們新建的繼承自socketserver模塊下的BaseRequestHandler類 實例化對象
sever.serve_forever() # 通過調用對象的serve_forever()方法來激活服務端
客戶端
import socket
sk = socket.socket()
sk.connect(("127.0.0.1",8888)) # 主動初始化與服務器端的連接
while True:
send_data = input("輸入發送內容:")
sk.sendall(bytes(send_data, encoding="utf8"))
if send_data == "byebye":
break
accept_data = str(sk.recv(1024), encoding="utf8")
print("".join(("接收內容:",accept_data)))
sk.close()