socket簡介
在編程的過程中,我們需要使用網絡編程,這時我們不得不和網絡通信的底層基礎打交道了.我們必須讓自己傳輸的數據符合網絡通信的基本協議,即TCP/IP協議,但是網絡通信協議本身很復雜.我們不可能在編程的過程中還自己去對數據進行封包處理,這是便出現了socket幫助我們處理相關的數據傳輸,在其他語言里也可以使用socket幫我們處理相關問題.
socket本質就是一組接口,是在應用層與TCP/IP協議族通信中間的一個抽象層,龐大復雜的TCP/IP協議族我們便可以不用過多關注,而只需要通過socket提供的接口就可以相互連接.
socke模塊中的基本命令
s.bind() 在服務端綁定(主機,端口號)到套接字
s.listen() 開始TCP監聽
s.accept() 被動接受TCP客戶的連接,(阻塞式)等待連接的到來,收到的是元組形式,包含內容和地址
s.connect() 主動初始化TCP服務器連接
s.connect_ex() connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
s.recv() 接收TCP數據
s.send() 發送TCP數據(send在待發送數據量大於己端緩存區剩余空間時,數據丟失,不會發完)
s.sendall() 發送完整的TCP數據(本質就是循環調用send,sendall在待發送數據量大於己端緩存區剩余空間時,數據不丟失,循環調用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() 創建一個與該套接字相關的文件
TCP的服務端和客戶端示例
TCP服務端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
##網絡編程需要調用AF_INET,而SOCK_STREAM代表數據流,即TCP方式
phone.bind(('192.168.1.101',9000))
##綁定IP地址和端口,必須是元組
phone.listen(5)
##設置監聽數量
while True:
conn, addr = phone.accept()
##得到內容對象和地址
while True:
try: ##異常處理特殊情況
feedback = conn.recv(1024)
##得到內容對象傳來的值,里面規定字節數量
print(feedback.decode("UTF-8"))
##編譯,因為只有被編譯的字符才能被傳輸
msg = input(">>:")
conn.send(msg.encode("UTF-8"))
##發送
except Exception:
break
conn.close() ##關閉連接了的對象
phone.close() ##關閉服務器
TCP客戶端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect_ex(('192.168.1.101',9000)) ##連接對象
while True:
msg = input(">>:")
phone.send(msg.encode("UTF-8")) ##發送消息
feedback = phone.recv(1024) ##返回消息
print(feedback.decode("UTF-8"))
phone.close() ##關閉對象
問題
可能在多次開啟關閉服務器的時候,會出現IP地址已經被使用了的情況,我們可以用以下方法解決:
phone = socket(AF_INIT, SOCKET_STREAM)
phone.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
phone.bind(('192.168.1.101',9000))
UDP的服務端和客戶端示例
UDP的服務端
from socket import *
ip_addr = ('192.168.1.101',9000)
buff_size = 1024
udp_server = socket(AF_INET, SOCK_DGRAM)
##SOCK_DGRAM表示UDP方式
udp_server.bind(ip_addr)
##綁定IP地址和端口
while True:
data, addr = udp_server.recvfrom(buff_size)
##受信息
print(data.decode("utf-8"))
udp_server.sendto(data.upper(), addr)
##發信息
UDP的客戶端
from socket import *
ip_addr = ('192.168.1.101',9000)
buff_size = 1024
udp_client = socket(AF_INET, SOCK_DGRAM)
while True:
msg = input(">>:")
udp_client.sendto(msg.encode("utf-8"), ip_addr)
##不需要綁定,只需要傳一個地址
date, addr = udp_client.recvfrom(buff_size)
print(date.decode("utf-8"))
subprogress模塊
TCP和UDP比較
TCP | UDP | |
---|---|---|
傳輸方式 | 數據流(STREAM) | 數據報(DGRAM) |
導致問題 | 粘包 | 丟包 |
接待對象 | 一個 | 多個 |
連接 | 需要連接線 | 不需要連接線 |
TCP是以數據流的方式,在數據很多時,會多次地不分內容關聯性地傳輸給對方內核態緩存,當用戶態緩存來取數據時,並不知道數據的開頭和結尾,那么就出現了粘包現象.因此粘包問題主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的,所以通過TCP方式不能發送空.
然而,UDP方式是以信息為單位傳輸數據,在數據里面包含了開頭和結尾的界限,因此不會出現粘包的現象,但是當傳輸來的數據被儲存在內核態緩存里面時,如果應用態緩存沒有一次性的取完數據,那么數據就會被丟棄
解決了粘包現象的遠程指令程序
客戶端
from socket import *
ip_port = ('127.0.0.1', 9000)
client = socket(AF_INET, SOCK_STREAM)
client.connect_ex(ip_port)
while True:
command = input(">>:")
if not command: continue
client.send(command.encode("utf-8"))
feedback_of_length = int(client.recv(1024).decode("gbk")) #先得到數據的長度
client.send("OK".encode("gbk")) #並且發送可以傳輸了的命令
all_data = b""
data_length = 0
while data_length < feedback_of_length:
all_data += client.recv(1024) #循環地把數據聯結在一起
data_length = len(all_data)
last_date = all_data.decode("gbk") ##最后解碼
print(last_date)
client.close()
服務端
from socket import *
import subprocess
ip_port = ('127.0.0.1', 9000)
buff_size = 5
server = socket(AF_INET, SOCK_STREAM)
server.bind(ip_port)
server.listen(buff_size)
while True:
conn, addr = server.accept()
while True:
try:
cmd = conn.recv(1024)
if not cmd: continue
res = subprocess.Popen(cmd.decode("utf-8"), shell=True,
stderr = subprocess.PIPE,
stdin = subprocess.PIPE,
stdout = subprocess.PIPE
)
err = res.stderr.read()
if not res:
get_res = "Run Perfectly"
elif err:
get_res = err
else:
get_res = res.stdout.read()
length_of_res = len(get_res) #發送數據長度
conn.send(str(length_of_res).encode("gbk"))
last_info = conn.recv(1024) #得到是否傳輸的指令
if last_info.decode("utf-8") == "OK":
conn.send(get_res)
except Exception:
break
socketserver實現並發
TCP版服務端
import socketserver
class MyServer(socketserver.BaseRequestHandler): ##必須要繼承這個父類
def handle(self): ##必須要重新定義這個方法
while True:
try:
data = self.request.recv(1024) ##recv就相當於conn了
if not data: break
print(data.decode("utf-8"), self.client_address) ##只有data和client_address這兩個量
self.request.sendall(data)
except Exception:
break
if __name__ == "__main__":
s = socketserver.ThreadingTCPServer(('192.168.1.101',9001), MyServer) ##實例化一個對象
s.serve_forever() ##永遠運行
UDP版服務端
import socketserver
class MyServer(socketserver.BaseRequestHandle):
def handle(self):
##(b'ad', <socket.socket fd=444, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0,
## laddr=('192.168.1.101', 8081)>) request是一個元組
print(self.request[0].decode("utf-8"))
self.request[1].sendto(self.request[0], self.client_address)
if __name__ == "__main__":
s = socketserver.ThreadingUDPServer(('192.168.1.101',8080), MyServer)
s.server_forever()