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计算一个文件里字数的方法
Jun 15 Python
Python2.x与Python3.x的区别
Jan 14 Python
pyhanlp安装介绍和简单应用
Feb 22 Python
python验证身份证信息实例代码
May 06 Python
python绘制评估优化算法性能的测试函数
Jun 25 Python
Python 进程之间共享数据(全局变量)的方法
Jul 16 Python
python+pygame实现坦克大战
Sep 10 Python
提升python处理速度原理及方法实例
Dec 25 Python
python3实现raspberry pi(树莓派)4驱小车控制程序
Feb 12 Python
浅谈pytorch torch.backends.cudnn设置作用
Feb 20 Python
在python image 中实现安装中文字体
May 16 Python
Django如何实现密码错误报错提醒
Sep 04 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
PHP+MYSQL的文章管理系统(二)
2006/10/09 PHP
根据中文裁减字符串函数的php代码
2013/12/03 PHP
总结一些PHP中好用但又容易忽略的小知识
2017/06/02 PHP
laravel 中某一字段自增、自减的例子
2019/10/11 PHP
JavaScript去掉数组中的重复元素
2011/01/13 Javascript
javascript中注册和移除事件的4种方式
2013/03/20 Javascript
分享10个原生JavaScript技巧
2015/04/20 Javascript
Jquery日历插件制作简单日历
2015/10/28 Javascript
基于JavaScript如何实现ajax调用后台定义的方法
2015/12/29 Javascript
js实现文字向上轮播功能
2017/01/13 Javascript
基于JavaScript实现验证码功能
2017/04/01 Javascript
js实现复制功能(多种方法集合)
2018/01/06 Javascript
vue操作下拉选择器获取选择的数据的id方法
2018/08/24 Javascript
vue中Element-ui 输入银行账号每四位加一个空格的实现代码
2018/09/14 Javascript
element vue Array数组和Map对象的添加与删除操作
2018/11/14 Javascript
layui use 定义js外部引用函数的方法
2019/09/26 Javascript
[22:20]初生之犊-TI4第5名LGD战队纪录片
2014/08/13 DOTA
python3转换code128条形码的方法
2019/04/17 Python
基于Python实现船舶的MMSI的获取(推荐)
2019/10/21 Python
python的列表List求均值和中位数实例
2020/03/03 Python
Centos7下源码安装Python3 及shell 脚本自动安装Python3的教程
2020/03/07 Python
解决Keras中CNN输入维度报错问题
2020/06/29 Python
详解HTML5新增标签
2017/11/27 HTML / CSS
HTML5 离线应用之打造零请求、无流量网站的解决方法
2013/04/25 HTML / CSS
美国维生素、补充剂、保健食品购物网站:Vitacost
2016/08/05 全球购物
梅西百货官网:Macy’s
2020/08/04 全球购物
英文版餐饮运营管理求职信
2013/11/06 职场文书
工商管理系学生的自我评价分享
2013/11/29 职场文书
自我鉴定书面格式
2014/01/13 职场文书
办理生育手续介绍信
2014/01/14 职场文书
护士自我评价
2014/02/01 职场文书
工程造价专业大学生职业规划范文
2014/03/09 职场文书
我们的节日春节活动方案
2014/08/22 职场文书
导游词之山西祁县乔家大院
2019/10/14 职场文书
《天净沙·秋思》教学反思三篇
2019/11/02 职场文书
详解Go与PHP的语法对比
2021/05/29 PHP