python Socket网络编程实现C/S模式和P2P


Posted in Python onJune 22, 2020

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模式的应用程序

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, 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模式

python Socket网络编程实现C/S模式和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中正则表达式的使用详解
Oct 17 Python
Python实现网络端口转发和重定向的方法
Sep 19 Python
Python 判断是否为质数或素数的实例
Oct 30 Python
Python编程django实现同一个ip十分钟内只能注册一次
Nov 03 Python
Python Learning 列表的更多操作及示例代码
Aug 22 Python
python/Matplotlib绘制复变函数图像教程
Nov 21 Python
基于python的列表list和集合set操作
Nov 24 Python
flask框架url与重定向操作实例详解
Jan 25 Python
Python多线程Threading、子线程与守护线程实例详解
Mar 24 Python
python实现简单学生信息管理系统
Apr 09 Python
如何完美的建立一个python项目
Oct 09 Python
Python 实现Mac 屏幕截图详解
Oct 05 Python
Python手动或自动协程操作方法解析
Jun 22 #Python
keras使用Sequence类调用大规模数据集进行训练的实现
Jun 22 #Python
Python socket服务常用操作代码实例
Jun 22 #Python
Python如何实现后端自定义认证并实现多条件登陆
Jun 22 #Python
零基础小白多久能学会python
Jun 22 #Python
Keras-多输入多输出实例(多任务)
Jun 22 #Python
python和c语言哪个更适合初学者
Jun 22 #Python
You might like
全国FM电台频率大全 - 6 辽宁省
2020/03/11 无线电
php笔记之:有规律大文件的读取与写入的分析
2013/04/26 PHP
smarty模板引擎中自定义函数的方法
2015/01/22 PHP
PHP获取中国时间(上海时区时间)及美国时间的方法
2017/02/23 PHP
兼容ie、firefox的图片自动缩放的css跟js代码分享
2012/01/21 Javascript
使用documentElement正确取得当前可见区域的大小
2014/07/25 Javascript
jQuery控制DIV层实现由大到小,由远及近动画变化效果
2015/10/09 Javascript
javaScript实现可缩放的显示区效果代码
2015/10/26 Javascript
window.onerror()的用法与实例分析
2016/01/27 Javascript
JS使用eval()动态创建变量的方法
2016/06/03 Javascript
Sequelize中用group by进行分组聚合查询
2016/12/12 Javascript
Angularjs自定义指令Directive详解
2017/05/27 Javascript
Ionic3实现图片瀑布流布局
2017/08/09 Javascript
VUE组件中的 Drawer 抽屉实现代码
2019/08/06 Javascript
微信小程序和H5页面间相互跳转代码实例
2019/09/19 Javascript
jquery 遍历hash操作示例【基于ajax交互】
2019/10/12 jQuery
浅谈微信小程序列表埋点曝光指南
2019/10/15 Javascript
微信小程序报错: thirdScriptError的错误问题
2020/06/19 Javascript
Vue Render函数创建DOM节点代码实例
2020/07/08 Javascript
用Python遍历C盘dll文件的方法
2015/05/06 Python
python turtle 绘制太极图的实例
2019/12/18 Python
python GUI库图形界面开发之PyQt5输入对话框QInputDialog详细使用方法与实例
2020/02/27 Python
python中sort sorted reverse reversed函数的区别说明
2020/05/11 Python
Python flask框架实现浏览器点击自定义跳转页面
2020/06/04 Python
html5手机键盘弹出收起的处理
2020/01/20 HTML / CSS
详解淘宝H5 sign加密算法
2020/08/25 HTML / CSS
HelloFresh奥地利:立即订购烹饪盒
2019/02/22 全球购物
Europcar澳大利亚官网:全球汽车租赁领域的领导者
2019/03/24 全球购物
SQL面试题
2013/04/30 面试题
食品业务员岗位职责
2014/03/18 职场文书
群众路线自我剖析及整改措施
2014/11/04 职场文书
国际贸易实务实训报告
2014/11/05 职场文书
导游词之海南-南湾猴岛
2019/10/12 职场文书
css实现文章分割线样式的多种方法总结
2021/04/21 HTML / CSS
vue使用refs获取嵌套组件中的值过程
2022/03/31 Vue.js
使用Bandicam录制鼠标指针并附带点击声音,还可以添加点击动画效果
2022/04/11 数码科技