socket 編程
-客戶端/服務器架構 :即 C/S架構
1,硬件C/S 架構(打印機)
2, 軟件C/S 架構(web服務)
C/S架構與socket的關系:socket就是為了完成C/S架構的開發
-osi 七層:
應用層--運輸層--網絡層--鏈路層--物理層

socket 抽象層在應用層和運輸層之間
socket概念(socket也是套接字)
socket是應用層和TCP/IP協議中間通信的軟件層,它是一組接口,在設計模式中,socket其實就是一個門面模式,它把復雜的TCP/IP協議封裝隱藏在socket接口后,讓socket去組織數據,以符合指定協議,所以只需遵循socket規定去編程就可以。
套接字分為2種:
-基於文件型的套接字家族 AF_UNIX
用於一台機器的不同程序之間
linux 一切皆文件,基於文件的套接字調用的是底層的文件系統來取數據,2個套接字進程運行在同一個機器,可以通過訪問同一個文件系統來間接完成通信
-基於網絡類型的套接字家族 AF_INET
用於網絡編程
通過網絡來實現2個程序通訊
socket 基於tcp運行流程圖如下:

例如:
服務端:
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#socket.SOCK_STREAM 是基於流的通訊方式,也就是TCP
#socket.AF_INET 代表是網絡嵌套家族類型
phone.bind(('192.168.1.4',8000))
#括號內寫IP地址+端口 自己電腦IP是 192.168.1.4 ,監聽端口是8000
phone.listen(5) #代表同時可以接5個電話
conn,addr =phone.accept()
msg=conn.recv(1024)# 收消息 ,1024代表可以接收多少字節的信息
print('客戶端發來的消息是',msg)
conn.send(msg.upper())#發消息
conn.close()
phone.close()
客戶端
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('192.168.1.4',8000))
phone.send('hello'.encode('utf-8'))#發消息
data =phone.recv(1024) #收消息
print('收到服務端發來的消息',data)
運行結果是:
服務端:客戶端發來的消息是 b'hello'
客戶端:收到服務端發來的消息 b'HELLO'
socket 底層工作原理

客戶端服務端循環發送接收消息
服務端代碼:
from socket import *
ip_import =('192.168.1.3',8000)
back_log = 5
buffer_size = 1024
tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_import)
tcp_server.listen(back_log)
conn,add = tcp_server.accept()
print('雙向鏈接是',conn)
print('客戶端的地址是',add)
while True:
msg = conn.recv(buffer_size)
print('客戶發來的是',msg.decode('utf-8'))
conn.send(msg.upper())
conn.close()
tcp_server.close()
服務端運行結果是:
雙向鏈接是 <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.1.3', 8000), raddr=('192.168.1.3', 52951)>
客戶端地址是 ('192.168.1.3', 52951)
客戶發來的是 nihao
客戶發來的是 hi
客戶發來的是 how are you
客戶端代碼:
from socket import *
ip_port=('192.168.1.3',8000)
back_log=5
buffer_size=1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
client_msg = input('>>:').strip()
if not client_msg: continue
tcp_client.send(client_msg.encode('utf-8'))
print('客戶端已發送消息')
data = tcp_client.recv(buffer_size)
print('收到服務端發來的消息',data.decode('utf-8'))
客戶端結果如下:
>>:nihao
收到服務端發來的消息 NIHAO
>>:hi
收到服務端發來的消息 HI
>>:how are you
收到服務端發來的消息 HOW ARE YOU
注意:如果輸入可以是空格,但不可以是空,是空的話會繼續要求輸入。
socket 收發消息原理刨析
客戶端發消息:是從應用程序發送到用戶態內存,然后發送到內核態內存然后再通過網卡發出
客戶端消息:是從網卡進入內核態內存然后發送到用戶態內存
服務端同樣如此。

服務端多次接收雙向連接
windows 系統和linux/mac系統下,socket 不同之處:
斷開客戶端,windows 系統下服務端會報錯,而linux/mac系統下,服務端接收的是空。
windows 系統下:
服務端:from socket import *
ip_port =('192.168.1.3',8001)
back_log = 5
buffer_size = 1024
tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn,add = tcp_server.accept()
print('接收的鏈接是',conn)
print('地址是',add)
while True:
print('服務端開始運行了')
try:
data = conn.recv(buffer_size)
print('客戶發來的是',data.decode('utf-8'))
conn.send(data.upper())
print('服務端已發送',data.upper())
except Exception:
break
conn.close()
tcp_server.close()
客戶端
from socket import *
ip_port=('192.168.1.3',8001)
back_log=5
buffer_size=1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
client_msg = input('>>:').strip()
if not client_msg: continue
tcp_client.send(client_msg.encode('utf-8'))
print('客戶端已發送消息')
data = tcp_client.recv(buffer_size)
print('收到服務端發來的消息',data.decode('utf-8'))
tcp_client.close()
linux 系統下:
from socket import *
ip_port =('192.168.1.3',8001)
back_log = 5
buffer_size = 1024
tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
#在bind 之前進行socket 設置,使其不卡在time_wait占用地址
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn,add = tcp_server.accept()
print('接收的鏈接是',conn)
print('地址是',add)
while True:
print('服務端開始運行了')
try:
data = conn.recv(buffer_size)
if not data: break
print('客戶發來的是',data.decode('utf-8'))
conn.send(data.upper())
print('服務端已發送',data.upper())
except Exception:
break
tcp_server.close()
客戶端代碼如上,不變。
總結:服務端,客戶端基本要求:


基於udp 的套接字
udp沒有連接
服務端
from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024
udp_server = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 數據報
udp_server.bind(ip_port)
while True:
data,addr = udp_server.recvfrom(buffer_size)
print(data.decode('utf-8'))
udp_server.sendto(data.upper(),addr)
udp_server.close()
客戶端
from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024
udp_client = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 數據報
while True:
msg = input('>>:').strip()
udp_client.sendto(msg.encode('utf-8'),ip_port)
data,addr = udp_client.recvfrom(buffer_size)
print(data.decode('utf-8'))
udp_client.close()
運用基於udp的套接字,來制作時間服務器(ntp),代碼如下:
時間服務端:
from socket import *
import time
ip_port = ('192.168.1.5',8080)
buffer_size = 1024
time_server = socket(AF_INET,SOCK_DGRAM)
time_server.bind(ip_port)
while True:
data,addr = time_server.recvfrom(buffer_size)
if not data:
fmt = '%Y-%m-%d-%X'
else:
fmt = data.decode('utf-8')
back_time = time.strftime(fmt)
time_server.sendto(back_time.encode('utf-8'),addr)
time_server.close()
時間客戶端:
from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024
time_client = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 數據報
while True:
msg = input('>>:').strip()
time_client.sendto(msg.encode('utf-8'),ip_port)
data,addr = time_client.recvfrom(buffer_size)
print('服務器的標准時間是:',data.decode('utf-8'))
time_client.close()
recv 在自己這段的緩沖區為空時,會阻塞
recvfrom 在自己這段的緩沖區為空時,就收一個空
基於tcp實現遠程命令
subprocess 模塊
代碼:變量名=subprocess.Popen(命令,shell=True,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
將命令結果封裝在管道(PIPE)中,stdin 代表輸入,stdout 代表輸出,stderr代表報錯。
想要讀取內容:代碼:變量名.stdrr.read()
服務端
from socket import *
import subprocess
ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024
tcp_sever = socket(AF_INET,SOCK_STREAM)
tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) #設置socket,使其不卡在time wait,占用地址
tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)
while True:
conn,addr = tcp_sever.accept()
print('新的客戶端連接',addr)
while True:
try:
#收消息
cmd = conn.recv(buffer_size)
if not cmd:break
print('收到客戶端的命令',cmd)
#執行命令,得到命令的結果cmd_res
res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
#發消息
if not cmd_res:
cmd_res = '操作成功'.encode('utf-8') #此代碼代表,如果cmd_res 為空的話,也會顯示結果。
conn.send(cmd_res)
except Exception as e:
print(e)
break
tcp_sever.close()
客戶端
from socket import *
ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd = input('>>:').strip()
if not cmd:continue
if cmd == 'quit':break
tcp_client.send(cmd.encode('utf-8'))
cmd_res = tcp_client.recv(buffer_size)
print('命令的執行結果是',cmd_res.decode('utf-8'))
tcp_client.close()
實操結果如下:

粘包
注意:只有tcp 會粘包,udp不會粘包。
粘包問題主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據造成的。

解決粘包
方式一:比較低端一些
服務端:
from socket import *
import subprocess
ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024
tcp_sever = socket(AF_INET,SOCK_STREAM)
tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)
while True:
conn,addr = tcp_sever.accept()
print('新的客戶端連接',addr)
while True:
try:
#收消息
cmd = conn.recv(buffer_size)
if not cmd:break
print('收到客戶端的命令',cmd)
#執行命令,得到命令的結果cmd_res
res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
#發消息
if not cmd_res:
cmd_res = '操作成功'.encode('utf-8')
length =len(cmd_res)
conn.send(str(length).encode('utf-8'))
client_ready = conn.recv(buffer_size)
if client_ready == b'ready':
conn.send(cmd_res)
except Exception as e:
print(e)
break
tcp_sever.close()
客戶端
from socket import *
ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd = input('>>:').strip()
if not cmd:continue
if cmd == 'quit':break
tcp_client.send(cmd.encode('utf-8'))
length= tcp_client.recv(buffer_size)
tcp_client.send(b'ready')
length = int(length.decode('utf-8'))
recv_msg = b''
recv_size = 0
while recv_size < length:
recv_msg +=tcp_client.recv(buffer_size)
recv_size = len(recv_msg)
print('命令的執行結果是',recv_msg.decode('utf-8'))
tcp_client.close()
方式二:比較高端一些
服務端:
from socket import *
import subprocess
import struct
ip_port = ('192.168.1.2',8080)
back_log = 5
buffer_size = 1024
tcp_sever = socket(AF_INET,SOCK_STREAM)
tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)
while True:
conn,addr = tcp_sever.accept()
print('新的客戶端連接',addr)
while True:
try:
#收消息
cmd = conn.recv(buffer_size)
if not cmd:break
print('收到客戶端的命令',cmd)
#執行命令,得到命令的結果cmd_res
res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
#發消息
if not cmd_res:
cmd_res = '操作成功'.encode('utf-8')
length =len(cmd_res)
conn.send(struct.pack('i',length))
conn.send(cmd_res)
except Exception as e:
print(e)
break
tcp_sever.close()
客戶端:
from socket import *
import struct
from functools import partial
ip_port = ('192.168.1.2',8080)
back_log = 5
buffer_size = 1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd = input('>>:').strip()
if not cmd:continue
if cmd == 'quit':break
tcp_client.send(cmd.encode('utf-8'))
length_data= tcp_client.recv(4)
length = struct.unpack('i',length_data)[0]
recv_msg = ''.join(iter(partial(tcp_client.recv, buffer_size), b''))
print('命令的執行結果是',recv_msg.decode('utf-8'))
tcp_client.close()
總結:



socket 並發socketserver
socketserver-tcp套接字 並發
並發:即多個客戶端與服務端的同時交互
服務端:
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
# print('conn is:', self.request) # self.request相當於conn
# print('addr is:', self.client_address) # self.client_address相當於addr
while True:
try:
# 收消息
data = self.request.recv(1024)
if not data: break
print('收到客戶端的消息是:', data,self.client_address)
# 發消息
self.request.send(data.upper())
except Exception as e:
print(e)
break
if __name__ == '__main__':
s = socketserver.ThreadingTCPServer(('192.168.1.2',8081),MyServer)
s.serve_forever()
客戶端:
from socket import *
ip_port = ('192.168.1.2', 8081)
buffer_size = 1024
tcp_client = socket(AF_INET, SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
msg = input('>>: ').strip()
if not msg: continue
tcp_client.send(msg.encode('utf-8'))
print('客戶端已經發送消息')
data = tcp_client.recv(buffer_size)
print('收到服務端發來的消息', data.decode('utf-8'))
tcp_client.close()
socketserver-udp套接字 並發
對於tcp來說,self.request = conn
但是對於udp來說,self.request =(data,udp的套接字對象)
服務端:
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
#self.request[0] 是data
#self.request[1]是udp的socket套接字
#self.client_address 是conn
#收消息
print('收到客戶端的消息是',self.request[0],self.client_address)
#發消息
self.request[1].sendto(self.request[0].upper(),self.client_address)
if __name__ == '__main__':
s=socketserver.ThreadingUDPServer(('192.168.1.2',8082),MyServer) #多線程
s.serve_forever()
客戶端:
from socket import *
ip_port=('192.168.1.2',8082)
buffer_size=1024
udp_client=socket(AF_INET,SOCK_DGRAM) #數據報
while True:
msg=input('>>: ').strip()
udp_client.sendto(msg.encode('utf-8'),ip_port)
data,addr=udp_client.recvfrom(buffer_size)
# print(data.decode('utf-8'))
print(data)
認證客戶端合法性
服務端
from socket import *
import os
import hmac
secret_key = b'ni hao ma'
def conn_auth(conn):
'''
認證客戶端連接
:param conn:
:return:
'''
print('開始驗證連接的合法性')
msg = os.urandom(32)
conn.sendall(msg)
h=hmac.new(secret_key,msg)#加嚴
digest=h.digest()
respone=conn.recv(len(digest))
return hmac.compare_digest(respone,digest)#對比respone 與digest 是否一致
def data_handler(conn, bufize=1024):
if not conn_auth(conn):
print('該連接不合法,關閉')
conn.close()
return
print('連接合法,開始通信')
while True:
data = conn.recv(bufize)
if not data: break
conn.send(data.upper())
def server_handle(ip_port,bufize,backlog=5):
'''
只處理連接
:param ip_port:
:param bufize:
:param backlog:
:return:
'''
tcp_socket_server = socket(AF_INET, SOCK_STREAM)
tcp_socket_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 在bind 之前進行socket 設置,使其不卡在time_wait占用地址
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(backlog)
while True:
conn, addr = tcp_socket_server.accept()
print('conn是%s,addr是%s' % (conn, addr))
data_handler(conn, bufize)
tcp_socket_server.close()
if __name__ == '__main__':
ip_port =('192.168.1.2',8080)
bufize = 1024
server_handle(ip_port,bufize)
客戶端:
from socket import *
import os,hmac
secret_key = b'ni hao ma'
def conn_auth(conn):
'''
驗證客戶端到服務器的連接合法性
:param conn:
:return:
'''
msg = conn.recv(32)
h = hmac.new(secret_key, msg)
digest = h.digest()
conn.sendall(digest)
def client_handler(ip_port,bufsize=1024):
tcp_socket_client = socket(AF_INET, SOCK_STREAM)
tcp_socket_client.connect(ip_port)
conn_auth(tcp_socket_client)
while True:
#發消息
cmd = input('>>:').strip()
if not cmd: continue
if cmd == 'quit': break
tcp_socket_client.send(cmd.encode('utf-8'))
#收消息
data=tcp_socket_client.recv(bufsize)
print(data.decode('utf-8'))
tcp_socket_client.close()
if __name__ == '__main__':
ip_port = ('192.168.1.2', 8080)
bufize = 1024
client_handler(ip_port,bufize)
作業

