Python IRC ChatBot hangs on socket.recv after seem

2019-07-25 12:52发布

问题:

Hey so I decided to create an IRC ChatBot whose sole purpose it is to read incoming messages from Twitch Chat and if a giveaway is recognized by a keyword it's supposed to enter the giveaway by sending !enter in Chat.

I build the Bot upon this source: https://github.com/BadNidalee/ChatBot. I only changed things in the Run.py so thats the only Code I'm going to post. The unaltered ChatBot does work but it has no reconnect ability and regularly stops receiving data because the socket closes or other reasons.

All I wanted to change was make it so that the ChatBot is stable and can just stay in the IRC Chat constantly without disconnecting. I tried to achieve this by setting a timeout of 8 seconds for my socket and catching timeout exceptions that would occur and reconnect after they occur.

And all in all it does seem to work, my Bot does what it's supposed to even when alot of messages are coming in, it recognizes when a Giveaway starts and answers acordingly. IRC Server PING Messages are also handled and answered correctly. If there is no message in Chat for over 8 seconds the Exception gets thrown correctly and the Bot also reconnects correctly to IRC.

BUT heres my Problem: After seemingly random times the socket will literally just Stop working. What I find strange is it will sometimes work for 20 minutes and sometimes for an hour. It doesn't occur when special events, like lots of messages or something else happens in Chat, it really seems random. It will not timeout there's just nothing happening anymore. If I cancel the program with CTRL-C at this point the console sais the last call was "readbuffer = s.recv(1024)" But why is it not throwing a timeout exception at that point? If s.recv was called the socket should timeout if nothing is received after 8 seconds but the program just stops and there is no more output until you manually abort it.

Maybe I went about it the wrong way completely. I just want a stable 24/7-able ChatBot that scans for one simple keyword and answers with one simple !enter. This is also my first Time programming in Python so If I broke any conventions or made any grave mistakes let me know.

The getUser Method returns the username of the line of chat that is scanned currently.

The getMessage Method returns the message of the line of chat that is scanned.

The openSocket Method opens the Socket and sends JOIN NICK PASS etc to the IRC

#!/usr/bin/python
import string
import socket
import datetime
import time
from Read import getUser, getMessage
from Socket import openSocket, sendMessage
from Initialize import joinRoom

connected = False
readbuffer = ""


def connect():
    print "Establishing Connection..."
    irc = openSocket()
    joinRoom(irc)
    global connected
    connected = True
    irc.settimeout(8.0)
    print "Connection Established!"
    return irc

while True:
    s = connect()
    s.settimeout(8.0)
    while connected:
            try:
                readbuffer = s.recv(1024)
                temp = string.split(readbuffer, "\n")
                readbuffer = temp.pop()


                for line in temp:
                    if "PING" in line:
                        s.send(line.replace("PING", "PONG"))
                        timern = str(datetime.datetime.now().time())
                        timern = timern[0:8]
                        print timern + " PING received"
                        break

                    user = getUser(line)
                    message = getMessage(line)
                    timern = str(datetime.datetime.now().time())
                    timern = timern[0:8]
                    print timern +" " + user + ": " + message
                    if "*** NEW" in message:
                        sendMessage(s, "!enter")
                        break


            except socket.timeout:
                connected = False
                print "Socket Timed Out, Connection closed!"
                break
            except socket.error:
                connected = False
                print "Socket Error, Connection closed!"
                break

回答1:

I think you've missunderstood how timeout work on the socket.

s.settimeout(8.0)

Will only set s.connect(...) to timeout if it can't reach the destination host.
Further more, usually what you want to use instead if s.setblocking(0) however this alone won't help you either (probably).

Instead what you want to use is:

import select
ready = select.select([s], [], [], timeout_in_seconds)
if ready[0]:
    data = s.recv(1024)

What select does is check the buffer to see if any incoming data is available, if there is you call recv() which in itself is a blocking operation. If there's nothing in the buffer select will return empty and you should avoid calling recv().

If you're running everything on *Nix you're also better off using epoll.

from select import epoll, EPOLLIN
poll = epoll()
poll.register(s.fileno(), EPOLLIN)

events = poll.poll(1) # 1 sec timeout
for fileno, event in events:
    if event is EPOLLIN and fileno == s.fileno():
        data = s.recv(1024)

This is a crude example of how epoll could be used.
But it's quite fun to play around with and you should read more about it