How epoll detect clientside close in Python?

后端 未结 10 564
清歌不尽
清歌不尽 2021-02-04 21:22

Here is my server

\"\"\"Server using epoll method\"\"\"

import os
import select
import socket
import time

from oodict import OODict

addr = (\         


        
相关标签:
10条回答
  • 2021-02-04 21:45

    After I move select.EPOLLHUP handling code to the line before select.EPOLLIN, hup event still cant be got in 'telnet'. But by coincidence I found that if I use my own client script, there are hup events! strange...

    And according to man epoll_ctl

       EPOLLRDHUP (since Linux 2.6.17)
              Stream socket peer closed connection, or shut down writing half of connection.  (This flag is especially useful for writing simple code  to
              detect peer shutdown when using Edge Triggered monitoring.)
    
       EPOLLERR
              Error  condition  happened on the associated file descriptor.  epoll_wait(2) will always wait for this event; it is not necessary to set it
              in events.
    
       EPOLLHUP
              Hang up happened on the associated file descriptor.  epoll_wait(2) will always wait for this event; it  is  not  necessary  to  set  it  in
              events.
    

    Seems there shall be a EPOLLRDHUP event when remote side closed connection, which is not implemented by python, don't know why

    0 讨论(0)
  • 2021-02-04 21:48

    The EPOLLRDHUP flag is not defined in Python for no reason. If your Linux kernel is >= 2.6.17, you can define it and register your socket in epoll like this:

    import select
    if not "EPOLLRDHUP" in dir(select):
        select.EPOLLRDHUP = 0x2000
    ...
    epoll.register(socket.fileno(), select.EPOLLIN | select.EPOLLRDHUP)
    

    You can then catch the event you need using the same flag (EPOLLRDHUP):

    elif event & select.EPOLLRDHUP:
         print "Stream socket peer closed connection"
         # try shutdown on both side, then close the socket:
         socket.close()
         epoll.unregister(socket.fileno())
    

    For more info you can check selectmodule.c in python's repository:

    0 讨论(0)
  • 2021-02-04 21:49

    The issue why you're not detecting EPOLLHUP/EPOLLERR in your code is because of the bitwise operations you are doing. See when a socket is ready to read epoll will throw a flag with bit 1 which is equal to select.EPOLLIN (select.EPOLLIN == 1). Now say the client hangs up (gracefully or not) epoll on the server will throw a flag with bit 25 which is equal to EPOLLIN+EPOLLERR+EPOLLHUP. So with the bit 25 (event variable in your code) you can see how EPOLLERR is not being detected because all of your elif statements (with the exception of EPOLLOUT line) don't return 0 so the first elif statement is executed, for example:

    >>> from select import EPOLLIN,EPOLLOUT,EPOLLHUP,EPOLLERR
    >>> event = 25
    >>> event & EPOLLIN
    1
    >>> event & EPOLLERR
    8
    >>> event & EPOLLHUP
    16
    >>> event & EPOLLOUT
    0
    

    Notice how the first three don't return 0? That's why your code isn't detecting EPOLLERR/EPOLLHUP correctly. When a client hangs up you can still read from the socket as the server side is still up (of course it would return 0 data if you did) hence EPOLLIN but since the client hung up it's also EPOLLHUP and since it's EPOLLHUP it's also EPOLLERR as a hangup is somewhat of an error. I know I'm late on commenting on this but I hope I helped someone out there lol

    Here is a way I would rewrite your code to express what I'm saying better:

    import os
    import select
    import socket
    import time
    
    from oodict import OODict
    
    addr = ('localhost', 8989)
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(addr)
    s.listen(8)
    s.setblocking(0) # Non blocking socket server
    epoll = select.epoll()
    read_only = select.EPOLLIN | select.EPOLLPRI | select.EPOLLHUP | select.EPOLLERR
    read_write = read_only | select.EPOLLOUT
    biterrs = [25,24,8,16,9,17,26,10,18] #Bitwise error numbers
    epoll.register(s.fileno(),read_only)
    
    cs = {}
    data = ''
    while True:
        time.sleep(1)
        events = epoll.poll(1) # Timeout 1 second
        print 'Polling %d events' % len(events)
        for fileno, event in events:
            if fileno == s.fileno():
                sk, addr = s.accept()
                sk.setblocking(0)
                print addr
                cs[sk.fileno()] = sk
                epoll.register(sk.fileno(),read_only)
    
            elif (event is select.EPOLLIN) or (event is select.EPOLLPRI):
                data = cs[fileno].recv(4)
                print 'recv ', data
                epoll.modify(fileno, read_write)
            elif event is select.EPOLLOUT:
                print 'send ', data
                cs[fileno].send(data)
                data = ''
                epoll.modify(fileno, read_only)
    
            elif event in biterrs:
                print 'err'
                epoll.unregister(fileno)
    
    0 讨论(0)
  • 2021-02-04 21:52

    I have another approach..

    try:
        data = s.recv(4096)
    except socket.error:
        if e[0] in (errno.EWOULDBLOCK, errno.EAGAIN): # since this is a non-blocking socket..
            return # no error
        else:
            # error
            socket.close()
    
    if not data: #closed either
        socket.close() 
    
    0 讨论(0)
提交回复
热议问题