Python Socket网络编程实战:C/S与P2P模式实现详解
本文详细讲解使用Python实现Socket网络编程的方法,涵盖C/S模式下的TCP/IP与UDP协议代码实现,以及基于PyQt5的图形界面封装。同时介绍P2P模式的UDP通信方案,包含多线程处理、端口绑定及Qt事件循环中的Socket生命周期管理,提供完整服务端与客户端代码示例。
C/S模式网络编程实现
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],addr[1],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协议
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],addr[1],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协议的实现更为简洁。由于无需建立连接,因此也不需要创建线程来专门管理数据的收发过程。
基于PyQt5的C/S应用封装
C/S模式的应用程序

使用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, 400, 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,args=(sock,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],addr[1],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,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.edit = QLineEdit(self)
self.commit_btn = QPushButton('发送',self)
self.text = QTextEdit(self)
#布局
self.edit.setGeometry(20, 410, 280, 30)
self.commit_btn.setGeometry(310,410,70,30)
self.text.setGeometry(20,20,360,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, 300, 400, 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模式通信实现
P2P模式

根据课程要求,P2P(点对点)模式被定义为两个服务器节点相互连接通信(起初误以为是客户端发给服务器再由服务器转发给另一客户端)。为简化实现,直接采用UDP协议,从而避免了复杂的多线程管理。代码如下:
from PyQt5.QtWidgets import (QApplication,QPushButton,
QWidget,QLineEdit,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, 280, 30)
self.commit_btn.setGeometry(310,480,70,30)
self.text.setGeometry(20,90,360,380)
self.host_label.setGeometry(20,20,65,25)
self.host.setGeometry(90,20,110,25)
self.dst_port_label.setGeometry(205,20,65,25)
self.dst_port_edit.setGeometry(275,20,110,25)
self.src_port_label.setGeometry(20,55,65,25)
self.src_port_edit.setGeometry(90,55,110,25)
self.que_ren_btn.setGeometry(205,55,70,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, 300, 400, 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'),('127.0.0.1',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],addr[1],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内容,请搜索本站历史文章或继续浏览后续相关文章,感谢大家的支持!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













