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 基础教程之Map使用方法
Jan 17 Python
Python编程实现微信企业号文本消息推送功能示例
Aug 21 Python
详解Python requests 超时和重试的方法
Dec 18 Python
在python plt图表中文字大小调节的方法
Jul 08 Python
与Django结合利用模型对上传图片预测的实例详解
Aug 07 Python
基于Python 中函数的 收集参数 机制
Dec 21 Python
Macbook安装Python最新版本、GUI开发环境、图像处理、视频处理环境详解
Feb 17 Python
python 控制台单行刷新,多行刷新实例
Feb 19 Python
Python实现动态给类和对象添加属性和方法操作示例
Feb 29 Python
Pandas对每个分组应用apply函数的实现
Dec 13 Python
用60行代码实现Python自动抢微信红包
Feb 04 Python
Python学习之包与模块详解
Mar 19 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
解决FastCGI 进程超过了配置的活动超时时限的问题
2013/07/03 PHP
学习php开源项目的源码指南
2014/12/21 PHP
Zend Framework路由器用法实例详解
2016/12/11 PHP
PHP基于socket实现的简单客户端和服务端通讯功能示例
2017/07/10 PHP
Extjs中ComboBox加载并赋初值的实现方法
2012/03/22 Javascript
jQuery函数的等价原生函数代码示例
2013/05/27 Javascript
JQueryiframe页面操作父页面中的元素与方法(实例讲解)
2013/11/19 Javascript
利用js正则表达式验证手机号,email地址,邮政编码
2014/01/23 Javascript
javascript + jquery实现定时修改文章标题
2014/03/19 Javascript
js实现的简单radio背景颜色选择器代码
2015/08/18 Javascript
JS正则表达式比较常见用法
2016/01/26 Javascript
JS获取当前脚本文件的绝对路径
2016/03/02 Javascript
深入理解angularjs过滤器
2016/05/25 Javascript
巧用Vue.js+Vuex制作专门收藏微信公众号的app
2016/11/03 Javascript
webpack构建的详细流程探底
2018/01/08 Javascript
ajax前台后台跨域请求处理方式
2018/02/08 Javascript
webpack 打包压缩js和css的方法示例
2018/03/20 Javascript
JS使用数组实现的队列功能示例
2019/03/04 Javascript
详解微信小程序开发(项目从零开始)
2019/06/06 Javascript
解决vue自定义全局消息框组件问题
2019/11/22 Javascript
详解Python中表达式i += x与i = i + x是否等价
2017/02/08 Python
python递归查询菜单并转换成json实例
2017/03/27 Python
python简单线程和协程学习心得(分享)
2017/06/14 Python
python中的字典操作及字典函数
2018/01/03 Python
Django跨域请求CSRF的方法示例
2018/11/11 Python
Python中单线程、多线程和多进程的效率对比实验实例
2019/05/14 Python
在python3中使用shuffle函数要注意的地方
2020/02/28 Python
python deque模块简单使用代码实例
2020/03/12 Python
django ObjectDoesNotExist 和 DoesNotExist的用法
2020/07/09 Python
信息部岗位职责
2013/11/12 职场文书
学生社团文化节开幕式主持词
2014/03/28 职场文书
诚信贷款承诺书
2014/05/30 职场文书
列车乘务员工作不细心检讨书
2014/10/07 职场文书
台积电称即便经济低迷也没有降价的计划
2022/04/21 数码科技
virtualenv隔离Python环境的问题解析
2022/06/21 Python
Python  序列化反序列化和异常处理的问题小结
2022/12/24 Python