1. 程式人生 > 程式設計 >python Socket網路程式設計實現C/S模式和P2P

python Socket網路程式設計實現C/S模式和P2P

C/S模式

由於網路課需要實現Socket網路程式設計,所以簡單實現了一下,C/S模式分別用TCP/IP協議與UDP協議實現,下面將分別講解。

TCP/IP協議

TCP/IP協議是面向連線的,即客戶端與伺服器需要先建立連線後才能傳輸資料,以下是伺服器端的程式碼實現。

服務端:

import socket
from threading import Thread

def deal(sock,addr):
 print('Accept new connection from {}:{}'.format(addr[0],addr[1]))
 sock.send('與伺服器連線成功!'.encode('utf-8'))
 while True:
  data = sock.recv(1024).decode('utf-8') #1024為接收資料的最大大小
  print('receive from {}:{} :{}'.format(addr[0],addr[1],data))
  sock.send('資訊已成功收到'.encode('utf-8'))

##建立tcp/IPV4協議的socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#為socket繫結埠
s.bind(('127.0.0.1',10240))
#監聽埠,引數5為等待的最大連線量
s.listen(5)
print("Waiting for connection...")

while True:
 sock,addr = s.accept()
 t1 = Thread(target=deal,args=(sock,addr))
 t1.start()

#斷開與該客戶端的連線
sock.close()
s.close()

需要注意的是,伺服器在等待客戶端連線時,即accept()函式這裡是阻塞的,如下程式碼每次只能接受一個客戶端的連線。

while True:
  #接受一個新連線,accept等待並返回一個客戶端連線
  sock,addr = s.accept()
  print('Accept new connection from {}:{}'.format(addr[0],addr[1]))
  #給客戶端傳送訊息
  sock.send('連線成功!'.encode('utf-8'))
  while True:
    data = sock.recv(1024).decode('utf-8')  #1024為接收資料的最大大小
    print('receive from {}:{} :{}'.format(addr[0],data))
    sock.send('資訊已成功收到'.encode('utf-8'))
  #斷開與該客戶端的連線
  sock.close()

也就是說如果採用以上方式,一個客戶端與伺服器建立連線後,伺服器就會進入一個死迴圈去收發該客戶端的資訊,因此需要引入多執行緒,每與一個客戶端建立連線,就為其建立一個執行緒用於控制資訊的收發,這樣便可以接受多個客戶端的連線了。

客戶端:

import socket

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

##建立連線
s.connect(('127.0.0.1',10240))

#接收客戶端連線成功伺服器發來的訊息
print(s.recv(1024).decode('utf-8'))

while True:
  data = input('傳送給伺服器:')
  if len(data)>0:
    
    s.send(data.encode('utf-8'))
    print('form sever:{}'.format(s.recv(1024).decode('utf-8')))
s.close()

客戶端是比較簡單的,需要與伺服器建立連線後,再進行收發資訊,這裡不再贅述了。

UDP協議

UDP協議是面向無連線的,即伺服器與客戶端不需要提前建立連線,只需要向指定的埠直接傳送資料即可。

服務端

import socket

#為伺服器建立socket並繫結埠  SOCK_DGRAM指定了socket的型別為udp
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

s.bind(('127.0.0.1',7890))

print('Waiting for data...')
#upd無需監聽
while True:
  data,addr = s.recvfrom(1024)
  print('Recevie from {}:{} :{}'.format(addr[0],data.decode('utf-8')))
  #sendto的另一個引數為客戶端socket地址
  s.sendto('資訊已成功收到!'.encode('utf-8'),addr)

客戶端

import socket

#為伺服器建立socket並繫結埠  SOCK_DGRAM指定了socket的型別為udp
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
  data = input('傳送給伺服器:')
  s.sendto(data.encode('utf-8'),('127.0.0.1',7890))
  
  print('Receive from sever:{}'.format(s.recv(1024).decode('utf-8')))

可以看到UDP協議是非常簡單的,由於不需要建立連線,所以也不需要建立執行緒來管理資料的收發。

C/S模式的應用程式

python Socket網路程式設計實現C/S模式和P2P

使用PyQt5對以上的程式進行封裝,這是基於TCP/IP協議實現的。

服務端

from PyQt5.QtWidgets import (QApplication,QPushButton,QWidget,QLineEdit,QTextEdit)
import sys
import socket
from threading import Thread

import datetime

class UI(QWidget):
  def __init__(self):
    super().__init__()
    self.initUI()
    
  def initUI(self):
    
    #控制元件
    self.clear_btn = QPushButton('清空內容',self)
    self.text = QTextEdit(self)
    
    #佈局
    self.clear_btn.setGeometry(150,400,100,40)
    self.text.setGeometry(20,20,360,370)
    
    self.text.setReadOnly(True)
    
    #訊號連線
    self.clear_btn.clicked.connect(self.commit)
    #初始化socket
    self.s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    ##建立連線
    self.s.bind(('127.0.0.1',10240))
    
    self.s.listen(5)
    self.text.setText("Waiting for connection...")
    self.t = Thread(target = self.recv,args = ())
    self.t.start()
    #主窗口布局
    self.setGeometry(300,300,450)
    self.setWindowTitle('Server')
    self.show()
    
    
  def commit(self):
    self.text.clear()
      
  def recv(self):
    while True:
      sock,addr = self.s.accept()
      t1 = Thread(target=self.deal,addr))
      t1.start()
    sock.close()
      
  def deal(self,sock,addr):
    #sock,addr = s.accept()
    self.text.append('Accept new connection from {}:{}'.format(addr[0],addr[1]))
    sock.send('與伺服器連線成功!'.encode('utf-8'))
    while True:
      data = sock.recv(1024).decode('utf-8')  #1024為接收資料的最大大小
      self.text.append('[{}] receive from {}:{} :{}'.format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),addr[0],data))
      sock.send('資訊已成功收到'.encode('utf-8'))
    sock.close()
    
  def closeEvent(self,event):
    self.s.close()
    event.accept()

if __name__ == '__main__':
  app = QApplication(sys.argv)
  ex = UI()
  sys.exit(app.exec_())

這裡需要注意的是,由於Qt的主程式本身一直處於迴圈,如果直接阻塞等待客戶端連線會導致程式崩潰,因此需要在Qt初始化時建立一個執行緒用於等待客戶端的連線,要想同時多個客戶端訪問伺服器,還需要在連線成功後再建立一個執行緒單獨用於接收該客戶端的資料。

客戶端

from PyQt5.QtWidgets import (QApplication,QTextEdit)
import sys
import socket
from threading import Thread

import datetime

class UI(QWidget):
  def __init__(self):
    super().__init__()
    self.initUI()
    
  def initUI(self):
    
    #控制元件
    self.edit = QLineEdit(self)
    self.commit_btn = QPushButton('傳送',self)
    self.text = QTextEdit(self)
    
    #佈局
    self.edit.setGeometry(20,410,280,30)
    self.commit_btn.setGeometry(310,70,30)
    self.text.setGeometry(20,380)
    
    self.text.setReadOnly(True)
    
    #訊號連線
    self.commit_btn.clicked.connect(self.commit)
    #初始化socket
    self.s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    ##建立連線
    self.s.connect(('127.0.0.1',10240))
    self.text.setText('伺服器 [{}]:{}\n'.format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),self.s.recv(1024).decode('utf-8')))
    #主窗口布局
    self.setGeometry(300,450)
    self.setWindowTitle('Client')
    self.show()
    
  def commit(self):
    if len(self.edit.text()):
      text = self.edit.text()
      self.s.send(text.encode('utf-8'))
      self.text.append('本機 [{}]:{}\n'.format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),text))
      self.text.append('伺服器 [{}]:{}\n'.format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),self.s.recv(1024).decode('utf-8')))
      self.edit.clear()
      
  def closeEvent(self,event):
    self.s.close()
    event.accept()
    
  def recv(self):
    while True:
      pass

if __name__ == '__main__':
  app = QApplication(sys.argv)
  ex = UI()
  sys.exit(app.exec_())

客戶端還是比較簡單,不需要建立執行緒,在傳送按紐點選時觸發事件,向伺服器傳送資料,並將傳送的資料與伺服器返回的資料顯示在textEdit上。

P2P模式

python Socket網路程式設計實現C/S模式和P2P

老師說P2P模式就是用兩個伺服器相互連線通訊(我以為是要客戶端傳送給伺服器,伺服器再轉發給另一個客戶端),為了實現方便,直接採用UDP協議,也不用建立那麼多執行緒了。程式碼如下:

from PyQt5.QtWidgets import (QApplication,QTextEdit,QLabel)
import sys
import socket
from threading import Thread

import datetime

class UI(QWidget):
  def __init__(self):
    super().__init__()
    self.initUI()
    
  def initUI(self):
    
    #控制元件
    self.edit = QLineEdit(self)
    self.commit_btn = QPushButton('傳送',self)
    self.text = QTextEdit(self)
    self.host_label = QLabel('ip地址:',self)
    self.host = QLineEdit(self)
    self.dst_port_label = QLabel('目標埠:',self)
    self.dst_port_edit = QLineEdit(self)
    self.src_port_label = QLabel('本機埠:',self)
    self.src_port_edit = QLineEdit(self)
    self.que_ren_btn = QPushButton('確認',self)
    
    #self.host_label.setStyleSheet("QLabel{font-size:25px}")
    #self.dst_port_label.setStyleSheet("QLabel{font-size:25px}")
    #self.src_port_label.setStyleSheet("QLabel{font-size:25px}")
    #佈局
    self.edit.setGeometry(20,480,90,380)
    self.host_label.setGeometry(20,65,25)
    self.host.setGeometry(90,110,25)
    self.dst_port_label.setGeometry(205,25)
    self.dst_port_edit.setGeometry(275,25)
    self.src_port_label.setGeometry(20,55,25)
    self.src_port_edit.setGeometry(90,25)
    self.que_ren_btn.setGeometry(205,25)
    
    self.text.setReadOnly(True)
    
    #訊號連線
    self.commit_btn.clicked.connect(self.commit)
    self.que_ren_btn.clicked.connect(self.que_ren)
    #初始化socket
    self.s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

    #主窗口布局
    self.setGeometry(300,520)
    self.setWindowTitle('Client')
    self.show()
    
  def commit(self):
    if len(self.edit.text()):
      text = self.edit.text()
      self.s.sendto(text.encode('utf-8'),self.dst_port))
      self.text.append('本機 [{}]:\n{}\n'.format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),text))
      self.edit.clear()
      
  def closeEvent(self,event):
    self.s.close()
    event.accept()
    
  def recv(self):
    while True:
      data,addr = self.s.recvfrom(1024)
      self.text.append('{}:{}[{}]:\n{}\n'.format(addr[0],datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),data.decode('utf-8')))
    
  def que_ren(self):
    self.src_port = int(self.src_port_edit.text())
    self.dst_port = int(self.dst_port_edit.text())
    #繫結ip地址與埠
    self.s.bind(('127.0.0.1',self.src_port))
    #開啟接收訊息的執行緒
    self.t = Thread(target=self.recv,args=())
    self.t.start()

if __name__ == '__main__':
  app = QApplication(sys.argv)
  ex = UI()
  sys.exit(app.exec_())

首先需要輸入要傳送資訊的IP地址,以及埠號,以及設定自己的埠號(IP地址沒有用到,我設定了是127.0.0.1),點選確定按鈕時觸發事件,會為socket繫結埠號,並且建立一個用於接收訊息的執行緒,在點擊發送按鈕時會觸發另一個事件用於傳送訊息,傳送與接收的訊息最後會顯示在TextEdit上。

注意

這裡要統一說明一下,在使用Qt封裝後程序會一直迴圈執行,導致關閉程式時socket也沒有關閉(因為我也剛學,不清楚不關閉的後果,可能會佔用這個埠一段時間吧),因此需要重寫Qt的closeEvent函式,在該函式中進行關閉。

總結

到此這篇關於python Socket網路程式設計實現C/S模式和P2P的文章就介紹到這了,更多相關python Socket C/S模式和P2P內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!