Python的Socket编程过程中实现UDP端口复用的实例分享


Posted in Python onMarch 19, 2016

关于端口复用

一个套接字不能同时绑定多个端口,如果客户端想绑定端口号,一定要调用发送信息函数之前绑定( bind )端口,因为在发送信息函数( sendto, 或 write ),系统会自动给当前网络程序分配一个随机端口号,这相当于随机绑定了一个端口号,这里只会分配一次,以后通信就以这个随机端口通信,我们再绑定端口号的话,就会绑定失败。如果我们放在发送信息函数( sendto, 或 write )之前绑定,那样程序将以我们绑定的端口号发送信息,不会再随机分配一个端口号。实际上,默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口,这时候,别的套接字就无法使用这个端口。那如何让两个套接字都能成功绑定一个端口呢?这时候就需要要到端口复用了。端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。
端口复用能在系统已开放的端口上进行通讯,只对输入的信息进行字符匹配,不对网络数据进行任何拦截、复制类操作,所以对网络数据的传输性能丝毫不受影响。
但要注意,建立连接后服务端程序占用极少系统资源,被控端不会在系统性能上有任何察觉,通常被后门木马所利用。
在winsock的实现中,对于服务器的绑定是可以多重绑定的,在确定多重绑定使用谁的时候,根据一条原则是谁的指定最明确则将包递交给谁,而且没有权限之分,也就是说低级权限的用户是可以重绑定在高级权限如服务启动的端口上的,这是非常重大的一个安全隐患。

Python解决UDP端口复用问题
一直觉得UDP协议很简单,但是今天问题让我感觉到网络的基础真是博大精深。

    废话少说,来看问题吧。由于协议的需要,我得实现一个UDP的客户端和服务器端,并且从同一个端口读写数据。

    最初不以为然,无非就是用两个socket,一个监听并从这个端口读取数据(服务器端采用了twisted),另一个向这个端口写入数据,用python实现只要10行左右的代码。

def startServer(queue, port): 
  reactor.listenUDP(port, DhtResponseHandler(queue)) 
  reactor.run() 

def sendUdpMsg(self, addr, msg): 
  socketHandler = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
  socketHandler.bind(("", self.port)) 
  socketHandler.sendto(msg, addr) 
  socketHandler.close()

     由于要向同一个端口写数据,于是client必须有bind,但是运行后发现server先bind了这个端口,client运行时会报错

error: [Errno 10048] Only one usage of each socket address (protocol/network address/port) is normally permitted  

     一般这种错误时因为多个socket不能同时bind同一个地址
     由于基础不够扎实,我开始疯狂的搜索,发现有人说端口复用的问题,所谓的端口复用,是指一个套接字释放掉一个端口后有一个wait_time,另一个套接字如果接着bind就会报错。虽然我的问题不完全一样,但是我欣喜若狂的使用了。即在client bind前加上如下一句
socketHandler.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

     但是仍然报错:

error: [Errno 10013] An attempt was made to access a socket in a way forbidden by its access permissions  

    (顺便一提,还有另一个参数叫SO_REUSEPORT,即复用端口,另外有一个叫SO_EXCLUSIVEADDRUSE,即不准复用该端口,其他socket的参数还有很多,可以参考winsockhttp://msdn.microsoft.com/en-us/library/aa924071.aspx或者unix下的socket)
     这个10013错误让我百思不得其解,搜索一下,主要有两种解释,有人说是需要提升应用程序的权限为管理员,我用的是eclipse+pydev,提升完eclipse权限没用,实际上还要修改python.exe的权限,方法是在这个程序上右键,兼容性一栏中勾上以系统管理员身份运行;有人说是跟其他程序地址或者端口冲突。但是我测试过发现都不行。

     另外,运行的时候发现,twisted的服务器端一定是要在主线程中,否则会报signal一定要在主线程才能接受的错误,但是twisted的reactor一运行起来就阻塞了。

     在twisted文档中翻到,原来还有一种UDP叫做connected UDP,变态吧,所谓connected UDP,就是只能向一个地址收发数据,看起来貌似可以,但是不符合可以向多个地址接收数据。

     最后在一篇文章中翻到说需要两个端口都设置重用,于是我试着重新写一个服务器,与之前的客户端配合,运行良好,完全无错

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
sock.bind(("", port)) 
data, address = sock.recvfrom(4096)

     好吧,看来问题在调用twisted了,不知道他是否有这样的设置,进去将这部分代码翻了一下,找不到这样设置的参数。

class Port(abstract.FileHandle): 
  def __init__(self, port, proto, interface='', maxPacketSize=8192, 
         reactor=None): 
    """ 
    Initialize with a numeric port to listen on. 
    """ 
    self.port = port 
    self.protocol = proto 
    self.readBufferSize = maxPacketSize 
    self.interface = interface 
    self.setLogStr() 
    self._connectedAddr = None 
 
    abstract.FileHandle.__init__(self, reactor) 
 
    skt = socket.socket(self.addressFamily, self.socketType) 
    addrLen = _iocp.maxAddrLen(skt.fileno()) 
    self.addressBuffer = _iocp.AllocateReadBuffer(addrLen) 
    # WSARecvFrom takes an int 
    self.addressLengthBuffer = _iocp.AllocateReadBuffer( 
        struct.calcsize('i')) 
 
  def startListening(self): 
    """ 
    Create and bind my socket, and begin listening on it. 
 
    This is called on unserialization, and must be called after creating a 
    server to begin listening on the specified port. 
    """ 
    self._bindSocket() 
    self._connectToProtocol() 
 
 
  def createSocket(self): 
    return self.reactor.createSocket(self.addressFamily, self.socketType) 
 
 
  def _bindSocket(self): 
    try: 
      skt = self.createSocket() 
      skt.bind((self.interface, self.port)) 
    except socket.error, le: 
      raise error.CannotListenError, (self.interface, self.port, le) 
 
    # Make sure that if we listened on port 0, we update that to 
    # reflect what the OS actually assigned us. 
    self._realPortNumber = skt.getsockname()[1] 
 
    log.msg("%s starting on %s" % ( 
        self._getLogPrefix(self.protocol), self._realPortNumber)) 
 
    self.connected = True 
    self.socket = skt 
    self.getFileHandle = self.socket.fileno

    难道说twisted就完全不提供这样的功能?最终在multicast中翻到这样一段,也就是,多播的情况是支持地址复用的,动手测起来。

class MulticastPort(MulticastMixin, Port): 
  """ 
  UDP Port that supports multicasting. 
  """ 
 
  implements(interfaces.IMulticastTransport) 
 
 
  def __init__(self, port, proto, interface='', maxPacketSize=8192, 
         reactor=None, listenMultiple=False): 
    Port.__init__(self, port, proto, interface, maxPacketSize, reactor) 
    self.listenMultiple = listenMultiple 
 
 
  def createSocket(self): 
    skt = Port.createSocket(self) 
    if self.listenMultiple: 
      skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
      if hasattr(socket, "SO_REUSEPORT"): 
        skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 
    return skt

     将server端改成如下代码,运行通过!

reactor.listenMulticast(port, DhtResponseHandler(queue), listenMultiple=True) 
reactor.run()

     感触良多,底层的知识比较重要,浮沙筑高台果然危险。

Python 相关文章推荐
python中cPickle用法例子分享
Jan 03 Python
零基础写python爬虫之爬虫的定义及URL构成
Nov 04 Python
Python的批量远程管理和部署工具Fabric用法实例
Jan 23 Python
python网络编程调用recv函数完整接收数据的三种方法
Mar 31 Python
Python cookbook(数据结构与算法)筛选及提取序列中元素的方法
Mar 19 Python
Python3.6实现带有简单界面的有道翻译小程序
Apr 16 Python
解决pytorch-yolov3 train 报错的问题
Feb 18 Python
python 中不同包 类 方法 之间的调用详解
Mar 09 Python
python 一维二维插值实例
Apr 22 Python
基于Tensorflow读取MNIST数据集时网络超时的解决方式
Jun 22 Python
Python如何执行系统命令
Sep 23 Python
python编程的核心知识点总结
Feb 08 Python
使用Python编写基于DHT协议的BT资源爬虫
Mar 19 #Python
浅析Python基础-流程控制
Mar 18 #Python
浅析Python编写函数装饰器
Mar 18 #Python
python正则表达式之作业计算器
Mar 18 #Python
基于python yield机制的异步操作同步化编程模型
Mar 18 #Python
理解Python中的With语句
Mar 18 #Python
简述Python中的进程、线程、协程
Mar 18 #Python
You might like
php下使用以下代码连接并测试
2008/04/09 PHP
php使用curl获取https请求的方法
2015/02/11 PHP
php正则表达式学习笔记
2015/11/13 PHP
php微信支付接口开发程序
2016/08/02 PHP
纯JS实现的批量图片预览加载功能
2011/08/14 Javascript
将Datatable转化成json发送前台实现思路
2013/09/06 Javascript
枚举的实现求得1-1000所有出现1的数字并计算出现1的个数
2013/09/10 Javascript
jQuery中removeProp()方法用法实例
2015/01/05 Javascript
利用CSS、JavaScript及Ajax实现图片预加载的方法
2016/11/29 Javascript
JavaScript轻松创建级联函数的方法示例
2017/02/10 Javascript
vue.js实现单选框、复选框和下拉框示例
2017/07/18 Javascript
用最少的JS代码写出贪吃蛇游戏
2018/01/12 Javascript
JS实现随机抽选获奖者
2019/11/07 Javascript
微信小程序自定义导航栏(模板化)
2019/11/15 Javascript
Bootstrap table 服务器端分页功能实现方法示例
2020/06/01 Javascript
vue实现员工信息录入功能
2020/06/11 Javascript
JS倒计时两种实现方式代码实例
2020/07/27 Javascript
jQuery+ajax实现用户登录验证
2020/09/13 jQuery
解决antd 下拉框 input [defaultValue] 的值的问题
2020/10/31 Javascript
[00:17]DOTA2荣耀之路5:It’s a disastah!
2018/05/28 DOTA
Python2实现的LED大数字显示效果示例
2017/09/04 Python
python+PyQT实现系统桌面时钟
2020/06/16 Python
Python 闭包,函数分隔作用域,nonlocal声明非局部变量操作示例
2019/10/14 Python
Django中密码的加密、验密、解密操作
2019/12/19 Python
Django REST framwork的权限验证实例
2020/04/02 Python
Python BeautifulReport可视化报告代码实例
2020/04/13 Python
澳大利亚二手奢侈品网站:Modsie
2019/09/23 全球购物
俄罗斯香水和化妆品在线商店:Aroma-butik
2020/02/28 全球购物
介绍一下Java的安全机制
2012/06/28 面试题
商务日语毕业生自荐信
2013/11/23 职场文书
幼儿园教师奖惩制度
2014/02/01 职场文书
2014组织生活会方案
2014/05/19 职场文书
公务员爱岗敬业演讲稿
2014/08/26 职场文书
庆元旦主持词
2015/07/06 职场文书
Pytorch GPU内存占用很高,但是利用率很低如何解决
2021/06/01 Python
Python图像处理库PIL详细使用说明
2022/04/06 Python