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协议是面向无连接的,即服务器与客户端不需要提前建立连接,只需要向指定的端口直接发送数据即可。
服务端
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协议是非常简单的,由于不需要建立连接,所以也不需要创建线程来管理数据的收发。
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模式就是用两个服务器相互连接通信(我以为是要客户端发送给服务器,服务器再转发给另一个客户端),为了实现方便,直接采用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内容请搜索python博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持python博客!
Powered By python教程网 鲁ICP备18013710号
python博客 - 小白学python最友好的网站!