python 實戰之模仿開發QQ聊天軟體(三)TCP/IP伺服器與客戶端建設
無論是p2p還是c/s還是b/s,只要用到通訊,必然是要用到今天寫的這個。
TCP/IP是網路軟體最核心的部分,缺少這個你只能當做單機遊戲玩。
TCP/IP,只需要搞清楚udp和tcp這兩個就可以了。
兩者的區別在於
udp每次傳送資訊都需要傳送ip和埠號,可以比作寄信,tcp只需要傳送一次ip和埠號直到客戶端執行結束,可以比作是打電話。
udp的保密性不好,所以在做通訊的時候基本選擇tcp,這篇文章只提tcp。
tcp客戶端與伺服器的工作模式如下圖所示:
先解釋一下ip,埠號,套接字都是什麼意思:
打個比方: 朋友使用導航去你家找你玩,必然需要知道你住在哪裡。
ip就是哪個街道哪個小區哪一撞哪一單元哪一層,埠號是哪一個門。
套接字就是他出發地和目的地,也就是說套接字是兩個端點。
客戶端模型
import socket
1.使用socket建立套接字:
tcp_client_socket = socket.socket(socket.AF_INET,socekt.SOCK_STREAM)
socket(AF_INET,SOCK_STREAM)
AF_INET決定了要用ipv4地址(32位的)與埠號(16位的)的組合
流式Socket(SOCK_STREAM)是一種面向連線的Socket
2.使用connect()方法連線伺服器
tcp_client_socket.connect(("192.168.0.1",8080))
("192.168.0.1",8080)是一個元組,首元素為伺服器ip地址,字元型的,8080是伺服器當前程式使用埠號,整型的
我的伺服器和客戶端是在同一個網段的,所以可以直接連線,不同網段需要使用交換機或者網線直連。(校園網wifi會組建內網,即使是同一個路由器也不行,網線直連後修改ip地址就可以了)
3.使用recv()、send()方法收發資料
recv_data = tcp_client_socket.recv(1024) # 客戶端接收伺服器發來的資訊最大長度為1K
data = recv_data.decode("utf-8") #解碼
tcp_client_socket.send("123".encode("utf-8)) # 客戶端向伺服器以utf-8的編碼格式傳送123
傳送字串的時候需要注意,如果是windows上執行的伺服器編碼格式是gbk,而linux,mac都是utf-8
4.使用close()方法關閉套接字
tcp_client_socket.close()
伺服器模型
1.使用socket建立套接字
tcp_service_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
2.使用bind()繫結本地資訊
tcp_service_socket.bind(("",8080))
3.使用listen()加入監聽等待客戶端接入
tcp_service_socket.listen(128)
4.使用accept()為客戶建立新套接字並讀取使用者ip和埠號
new_client_socket,client_addr = tcp_service_socket.accpet()
這個過程可能不太好理解,大概意思就是你打電話給10086,首先是打給10086的主機,然後主機分配一個話務員給你,幫你辦理業務
5.使用recv()/send()傳送資料
new_client_socket.send()
recv_data = new.client_socket.recv(1024)
這個和客戶端是沒有區別的
6.關閉新套接字
new_client_socket.close()
相當於客服業務結束,結束通話他所在的分機
7.關閉源套接字
tcp_service_socket.close()
樣例:檔案下載器
伺服器主機ip 192.168.0.1 埠號 8081
客戶端主機ip 192.168.0.2 埠由系統分配
客戶端原始碼
import socket
def main():
# 建立套接字
tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 獲取伺服器的ip port
# addr = ("192.168.0.1",8081)
# 連線伺服器
tcp_socket.connect(("192.168.0.1",8081))
# 獲取下載檔名
download_file_name = input("檔名")
# 將檔名傳送到伺服器
tcp_socket.send(download_file_name.encode("utf-8"))
# 接受檔案中的資料
recv_data = tcp_socket.recv(1024*1024)
if recv_data:
# 儲存接收的資料到檔案中
with open("[新]" + download_file_name,"wb") as f:
f.write(recv_data)
# 關閉套接字
tcp_socket.close()
if __name__ == "__main__":
main()
伺服器原始碼
import socket
def send_file_2_client(new_client_socket,client_addr):
# 接收客戶端傳送的請求
file_name = new_client_socket.recv(1024).decode("utf-8")
print("客戶端(%s)需要下載的檔名為:%s" % (str(client_addr),file_name))
file_content = None
# 開啟檔案,讀取資料
try:
f = open(file_name,"rb")
file_content = f.read()
f.close()
except Exception as ret:
print("沒有要下載的檔案(%s)" % file_name)
# 傳送資料給客戶端
if file_content:
# new_client_socket.send("hahahah".encode("utf-8"))
new_client_socket.send(file_content)
def main():
# 建立socket
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 繫結本地資訊
tcp_server_socket.bind(("",8081))
# 加入監聽套接字等待客戶端連線
tcp_server_socket.listen(128)
while True:
print("等待一個新客戶端的到來")
# 建立新套接字與客戶端連線
new_client_socket,client_addr = tcp_server_socket.accept()
print("新客戶端已經到來了:%s" % str(client_addr))
# 傳送檔案
send_file_2_client(new_client_socket,client_addr)
# 關閉套接字
new_client_socket.close()
tcp_server_socket.close()
if __name__ == '__main__':
main()
當然,目前這個只能完成單點操作,加入thread可以實現多工模式