目錄
1:Python3中socket編程介紹
這裏就不介紹網絡編程的基礎知識了,比如TCP/IP協議,OSI模型,TCP的三次握手等。下面直接介紹python中socket編程;
2:簡單的點對點
只接受單個連接的服務端:01_TcpServer.py:
# -*- coding: utf-8 -*-
import socket
import time
server = socket.socket()
ip_port = ('127.0.0.1',8000)
server.bind(ip_port) # 綁定ip+端口
server.listen(10) # 監聽
# print(help(server.listen))
print('啓動服務:等待客戶端的連接......')
conn,addr = server.accept()
print('客戶端已連接:')
print(conn)
print(addr)
while True:
print('等待客戶端的數據:')
client_data = conn.recv(1024) # 接收客戶端的數據,接收的數據是當前時間的秒數,下面處理是把秒轉換爲字符串
client_data = client_data.decode('utf-8')
stru_t = time.localtime(float(client_data))
strTime = time.strftime('%Y-%m-%d %H:%M:%S',stru_t)
print(f'接收來自{addr}的數據:{strTime}' )
conn.send(str(time.time()).encode('utf-8')) # 給客戶端發送當前時間的秒數
conn.close()
server.close()
01_TcpClient.py
# -*- coding: utf-8 -*-
import socket
import time
client = socket.socket()
ip_port = ('127.0.0.1',8000)
client.connect(ip_port)
while True:
client.send(str(time.time()).encode('utf-8'))
print('等待服務端的數據:')
server_data = client.recv(1024)
server_data = server_data.decode('utf-8')
stru_t = time.localtime(float(server_data))
strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
print(f'來自服務端的數據:{strTime}')
time.sleep(10)
client.close()
3:併發服務端
3.1:創建線程處理
對於每個客戶端連接都創建一個線程來處理:
02_TcpServer.py:
# -*- coding: utf-8 -*-
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
import time
import threading # 線程模塊
import traceback # 打印異常信息
# 處理每個鏈接
def handle_client(conn, addr):
print(conn, addr)
while True:
try:
print(f'等待客戶端{addr}的數據:')
client_data = conn.recv(1024) # 接收客戶端的數據,接收的數據是當前時間的秒數,下面處理是把秒轉換爲字符串
client_data = client_data.decode('utf-8')
stru_t = time.localtime(float(client_data))
strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
print(f'接收來自{addr}的數據:{strTime}')
conn.send(str(time.time()).encode('utf-8')) # 給客戶端發送當前時間的秒數
except ConnectionResetError:
val = traceback.format_exc()
print(val)
break
except Exception:
val = traceback.format_exc()
print(val)
print(f'關閉鏈接{addr}')
conn.close()
def StartTcpServer(ip,port):
server = socket.socket()
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 在bind前加,允許地址重用
server.bind((ip,port)) # 綁定ip+端口
server.listen(10) # 監聽
# print(help(server.accept))
while True:
try:
print('等待客戶端的連接......')
conn, addr = server.accept() # 一直會阻塞在這裏,直到有新的連接進來
# 創建一個線程來處理每個鏈接
threading.Thread(target=handle_client, args=(conn, addr)).start()
except Exception:
val = traceback.format_exc()
print(val)
break # 有異常退出循環
server.close()
if __name__ == '__main__':
StartTcpServer('127.0.0.1', 8000)
02_TcpClient.py:
# -*- coding: utf-8 -*-
import socket
import time
import traceback
def StartTcpClient(ip_port):
# 開3個客戶端
client_list = [socket.socket() for i in range(3)]
# ip_port = ('127.0.0.1',8000)
for client in client_list:
print(client)
client.connect(ip_port)
while True:
for index,client in enumerate(client_list):
try:
client.send(str(time.time()).encode('utf-8'))
print(f'clirnt[{index}],等待服務端的數據:')
server_data = client.recv(1024)
server_data = server_data.decode('utf-8')
stru_t = time.localtime(float(server_data))
strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
print(f'來自服務端的數據:{strTime}')
time.sleep(2)
except Exception:
val = traceback.format_exc()
print(val)
client.close()
client_list.remove(client)
if __name__ == '__main__':
StartTcpClient(('127.0.0.1',8000))
3.2:socketserver模塊處理
在上面使用每來一個連接,就創建一個線程的方式來處理,如果連接的數量過多,創建線程就會出現問題。
在Python中提供了socketserver模塊,socketserver在內部使用IO多路複用以及多線程/進程機制,實現了併發處理多個客戶端請求的socket服務端。
03_TcpServer.py
# -*- coding: utf-8 -*-
import socketserver
import time
import traceback # 打印異常信息
def handle_client_data(data,addr):
try:
data = data.decode('utf-8')
# print(type(data),data)
stru_t = time.localtime(int(data))
strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
print(f"來自{addr}的客戶端向你發來信息:{data},轉換之後:{strTime}" )
except Exception:
pass
# 每個鏈接都使用一個 TcpServer 實例對象來處理,並且會自動調用handle方法,退出handle方法鏈接就會自動斷開。
class TcpServer(socketserver.BaseRequestHandler):
"""
必須繼承socketserver.BaseRequestHandler類
"""
def handle(self):
"""
必須實現這個方法!
:return:
"""
conn = self.request # request裏封裝了所有請求的數據
conn.sendall(str(time.time()).encode('utf-8'))
print(f"1111:{self.client_address}") # 打日誌驗證每個鏈接是不是都會進來一次
while True:
try:
data = conn.recv(1024)
handle_client_data(data,self.client_address)
conn.sendall(str(int(time.time())).encode('utf-8'))
except Exception:
val = traceback.format_exc()
print(val)
break
print(f'退出客戶端{self.client_address}的處理。')
def StartTcpServer(ip,port):
# 創建一個多線程TCP服務器
server = socketserver.ThreadingTCPServer((ip,port), TcpServer)
print("啓動socketserver服務器!")
# 啓動服務器,服務器將一直保持運行狀態
server.serve_forever()
if __name__ == '__main__':
StartTcpServer('127.0.0.1', 8000)
03_TcpClient.py:
# -*- coding: utf-8 -*-
import socket
import time
import traceback
def StartTcpClient(ip_port,nums):
client_list = [socket.socket() for i in range(nums)]
for client in client_list:
print(client)
client.connect(ip_port)
while True:
try:
for index,client in enumerate(client_list):
try:
# client.send(str( int(time.time()) ).encode('utf-8'))
client.sendall(str( int(time.time()) ).encode('utf-8'))
print(f'clirnt[{index}],等待服務端的數據:')
server_data = client.recv(1024)
# print(f'來自服務端的數據:{server_data}')
try:
server_data = server_data.decode('utf-8')
stru_t = time.localtime(float(server_data))
strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
print(f'來自服務端的數據:{server_data} 轉換之後:{strTime}')
except Exception:
pass
time.sleep(1)
except (ConnectionResetError,ConnectionAbortedError):
val = traceback.format_exc()
print(val)
client.close()
client_list.remove(client)
if len(client_list) < 1:
raise
except Exception:
val = traceback.format_exc()
print(val)
except Exception:
val = traceback.format_exc()
print(val)
print('退出')
break
def test():
print(str(time.time() ),time.time(),int(time.time()))
if __name__ == '__main__':
StartTcpClient(('127.0.0.1',8000),1)
test()
3.3:使用select模塊
Python中的select模塊專注於I/O多路複用,提供了select poll epoll三個方法(其中後兩個在Linux中可用,windows僅支持select),另外也提供了kqueue方法(freeBSD系統)
select方法:
進程指定內核監聽哪些文件描述符(最多監聽1024個fd)的哪些事件,當沒有文件描述符事件發生時,進程被阻塞;當一個或者多個文件描述符事件發生時,進程被喚醒。
04_TcpServer.py:
# -*- coding: utf-8 -*-
import socket
import select
from socket import SOL_SOCKET,SO_REUSEADDR
import time
import traceback # 打印異常信息
def handle_client_data(data,addr):
try:
data = data.decode('utf-8')
# print(type(data),data)
stru_t = time.localtime(int(data))
strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
print(f"來自{addr.getpeername()}的客戶端向你發來信息:{data},轉換之後:{strTime}")
except Exception:
pass
def StartTcpServer(ip,port):
server = socket.socket()
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 在bind前加,允許地址重用
server.bind((ip,port)) # 綁定ip+端口
server.listen(10) # 監聽
read_fd_list = [server,]
while True:
try:
r_list, w_list, error_list = select.select(read_fd_list, [], [], 1) # 超時設置爲1秒
stru_t = time.localtime(time.time())
strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
print(f'select之後:len(r_list)={len(r_list)},時間:{strTime}')
for fd in r_list: # r_list 爲可讀的文件描述符列表
if fd == server: # 可讀的文件描述符 爲 server(上面起服務創建的),說明有鏈接請求過來。
conn, addr = fd.accept() # 接收客戶端鏈接
print(addr)
read_fd_list.append(conn) # 把客戶端,加入讀輪詢列表
data = conn.recv(1024)
print(f'接收數據:msg={data}')
handle_client_data(data, conn)
conn.sendall(str(time.time()).encode('utf-8')) # sendall 會循環調用send,把所有數據都發出去。
# conn.send(str(time.time()).encode('utf-8')) # send 發送出去的數據可能是部分數據
else: # 客戶端可讀,就是有客戶端發送數據過來。
try:
data = fd.recv(1024)
# print(help(fd))
handle_client_data(data,fd)
fd.sendall(str(int(time.time())).encode('utf-8'))
except (ConnectionResetError,ConnectionAbortedError,ConnectionRefusedError):
val = traceback.format_exc()
print(val)
fd.close()
read_fd_list.remove(fd)
except Exception:
val = traceback.format_exc()
print(val)
break # 有異常退出循環
server.close()
if __name__ == '__main__':
StartTcpServer('127.0.0.1', 8000)
04_TcpClient.py:
# -*- coding: utf-8 -*-
import socket
import time
import traceback
def StartTcpClient(ip_port,nums):
client_list = [socket.socket() for i in range(nums)]
for client in client_list:
print(client)
client.connect(ip_port)
while True:
try:
for index,client in enumerate(client_list):
try:
client.sendall(str( int(time.time()) ).encode('utf-8'))
# client.send(str( int(time.time()) ).encode('utf-8'))
print(f'clirnt[{index}],等待服務端的數據:')
server_data = client.recv(1024)
# print(f'來自服務端的數據:{server_data}')
server_data = server_data.decode('utf-8')
stru_t = time.localtime(float(server_data))
strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
print(f'來自服務端的數據:{server_data} 轉換之後:{strTime}')
# time.sleep(1)
except (ConnectionResetError, ConnectionAbortedError, ConnectionRefusedError):
val = traceback.format_exc()
print(val)
client.close()
client_list.remove(client)
if len(client_list) < 1:
raise
except Exception:
val = traceback.format_exc()
print(val)
except Exception:
val = traceback.format_exc()
print(val)
print('退出')
break
if __name__ == '__main__':
StartTcpClient(('127.0.0.1',8000),1)
# StartTcpClient(('127.0.0.1',8000),2000)
4:socket實現web服務器
4.1:簡單web服務器
在瀏覽器中訪問 http://127.0.0.1:8000/home 等
# -*- coding: utf-8 -*-
import socket
def StartWebServer(ip,port):
# 創建socket對象
sk = socket.socket()
# 綁定IP和端口
sk.bind((ip,port))
# 監聽
sk.listen()
while True:
# 等待連接
conn, addr = sk.accept()
# 接收數據
data = conn.recv(8096)
print(data)
# 返回狀態行
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
# 返回數據
try:
data = data.decode('utf-8')
url = data.split()[1]
ret_data = url
if url == "/test":
ret_data = "test"
elif url == "/home":
ret_data = "home"
elif url == "/index":
ret_data = "index"
else:
ret_data = "404"
conn.send(f'<h1>{ret_data}</h1>'.encode('utf-8'))
except Exception:
pass
# 關閉連接
conn.close()
if __name__ == '__main__':
StartWebServer('127.0.0.1', 8000)
4.2:簡單web服務器:函數版本
# -*- coding: utf-8 -*-
import socket
import time
def test(url):
ret = f'test: {url}'
return ret.encode('utf-8')
def index(url):
ret = f'index: {url}'
return ret.encode('utf-8')
def home(url):
ret = f'home: {url}'
return ret.encode('utf-8')
def gettime(url):
now = time.strftime('%Y-%m-%d %H:%M:%S')
ret = f'time: {url} {now}'
return ret.encode('utf-8')
url_map = {'/test':test,'/index':index,'/home':home,'/time':gettime}
def handle_client_data(data):
data = data.decode('utf-8')
url = data.split()[1]
ret_data = url
if url in url_map:
func = url_map[url]
ret_data = func(url)
else:
ret_data = b"404"
return ret_data
def StartWebServer(ip,port):
# 創建socket對象
sk = socket.socket()
# 綁定IP和端口
sk.bind((ip,port))
# 監聽
sk.listen()
while True:
# 等待連接
conn, addr = sk.accept()
# 接收數據
data = conn.recv(8096)
print(data)
# 返回狀態行
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
# 返回數據
try:
ret_data = handle_client_data(data)
conn.send(ret_data)
except Exception:
pass
# 關閉連接
conn.close()
if __name__ == '__main__':
StartWebServer('127.0.0.1', 8000)
在瀏覽器中訪問:http://127.0.0.1:8000/time
4.3:簡單web服務器:返回動態頁面
# -*- coding: utf-8 -*-
import socket
import time
def test(url):
ret = f'test: {url}'
return ret.encode('utf-8')
def index(url):
with open('index.html', 'rb') as f:
ret = f.read()
return ret
def home(url):
ret = f'home: {url}'
return ret.encode('utf-8')
def gettime(url):
now = time.strftime('%Y-%m-%d %H:%M:%S')
with open('time.html', 'r', encoding='utf-8') as f:
data = f.read()
data = data.replace('##time##', now)
return data.encode('utf-8')
url_map = {'/test':test,'/index':index,'/home':home,'/time':gettime}
def handle_client_data(data):
data = data.decode('utf-8')
url = data.split()[1]
ret_data = url
if url in url_map:
func = url_map[url]
ret_data = func(url)
else:
ret_data = b"404"
return ret_data
def StartWebServer(ip,port):
# 創建socket對象
sk = socket.socket()
# 綁定IP和端口
sk.bind((ip,port))
# 監聽
sk.listen()
while True:
# 等待連接
conn, addr = sk.accept()
# 接收數據
data = conn.recv(8096)
print(data)
# 返回狀態行
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
# 返回數據
try:
ret_data = handle_client_data(data)
conn.send(ret_data)
except Exception:
pass
# 關閉連接
conn.close()
if __name__ == '__main__':
StartWebServer('127.0.0.1', 8000)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<div>
<h1>index</h1>
<h2>index</h2>
<h3>index</h3>
<h4>index</h4>
</div>
</body>
</html>
time.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>time</title>
</head>
<body>
<h1>當前時間是: ##time## </h1>
</body>
</html>
4.4:併發web服務器
使用socketserver實現:
# -*- coding: utf-8 -*-
import socketserver
import time
import traceback # 打印異常信息
def test(url):
ret = f'test: {url}'
return ret.encode('utf-8')
def index(url):
with open('index.html', 'rb') as f:
ret = f.read()
return ret
def home(url):
ret = f'home: {url}'
return ret.encode('utf-8')
def gettime(url):
now = time.strftime('%Y-%m-%d %H:%M:%S')
with open('time.html', 'r', encoding='utf-8') as f:
data = f.read()
data = data.replace('##time##', now)
return data.encode('utf-8')
url_map = {'/test':test,'/index':index,'/home':home,'/time':gettime}
def handle_client_data(data):
data = data.decode('utf-8')
url = data.split()[1]
ret_data = url
if url in url_map:
func = url_map[url]
ret_data = func(url)
else:
ret_data = b"404"
return ret_data
# 每個鏈接都使用一個 TcpServer 實例對象來處理,並且會自動調用handle方法,退出handle方法鏈接就會自動斷開。
class TcpServer(socketserver.BaseRequestHandler):
"""
必須繼承socketserver.BaseRequestHandler類
"""
def handle(self):
"""
必須實現這個方法!
:return:
"""
conn = self.request # request裏封裝了所有請求的數據
# conn.sendall(str(time.time()).encode('utf-8'))
# print(f"1111:{self.client_address}") # 打日誌驗證每個鏈接是不是都會進來一次
while True:
try:
data = conn.recv(8096)
print(data)
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
# 返回數據
try:
ret_data = handle_client_data(data)
conn.sendall(ret_data)
except Exception:
pass
break
except Exception:
val = traceback.format_exc()
print(val)
break
print(f'退出客戶端{self.client_address}的處理。')
def StartTcpServer(ip,port):
# 創建一個多線程TCP服務器
server = socketserver.ThreadingTCPServer((ip,port), TcpServer)
print("啓動socketserver服務器!")
# 啓動服務器,服務器將一直保持運行狀態
server.serve_forever()
if __name__ == '__main__':
StartTcpServer('127.0.0.1', 8000)