socket

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


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