Zac
Zac

Reputation: 75

Send a string message to multiple threads

I have an IRC client that receives messages on a socket.

From this client I have created several bots that connect to other peoples chat channels on twitch. (These are authorized and not Spam bots!).

Each bot is created in a separate thread that takes the channel name along with a few other parameters.

My issue is my IRC socket can only bind to one port and this handles all the IRC messages, each message has a #channel string as the third word-string that directs it to a particular channel. These messages can be handled inside each bot as each one knows the name of its channel.

My problem is; How do I send the string received over the socket to multiple threads?

import time
import socket
import threading
import string
import sys
import os

class IRCBetBot:
    #irc ref
    irc = None

    def __init__(self,IRCRef,playerName,channelName,currencyName):

        #assign variables
        self.irc = IRCRef
        self.channel = '#' + channelName

        self.irc.send(('JOIN ' + self.channel + '\r\n') .encode("utf8"))

        #create readbuffer to hold strings from IRC
        readbuffer = ""

        # This is the main loop
        while 1:

            ##readbuffer## <- need to send message from IRC to this variable 

            for line in temp:
                line=str.rstrip(line)
                line=str.split(line)

                if (len(line) >= 4) and ("PRIVMSG" == line[1]) and (self.channel == line[2]) and not ("jtv" in line[0]):
                    #call function to handle user message
                if(line[0]=="PING"):
                    self.irc.send(("PONG %s\r\n" % line[0]).encode("utf8"))




def runAsThread(ircref,userName, channelName, currencyPrefix):
    print("Got to runAsThread with : " + str(userName) + " " + str(channelName) + " " + str(currencyPrefix))
    IRCBetBot(ircref,userName,channelName,currencyPrefix)

# Here we create the IRC connection
#IRC connection variables
nick = 'mybot'                  #alter this value with the username used to connect to IRC eg: "username".
password = "oauth:mykey"        #alter this value with the password used to connect to IRC from the username above.
server = 'irc.twitch.tv'
port = 6667

#create IRC socket
irc = socket.socket()

irc.connect((server, port))

#sends variables for connection to twitch chat
irc.send(('PASS ' + password + '\r\n').encode("utf8"))
irc.send(('USER ' + nick + '\r\n').encode("utf8"))
irc.send(('NICK ' + nick + '\r\n').encode("utf8"))

# Array to hold all the new threads 
threads = [] 
# authorised Channels loaded from file in real program
authorisedChannels = [["user1","#channel1","coin1"],["user2","#channel2","coin2"],["user3","#channel3","coin3"]]

for item in authorisedChannels:
    try:
        userName = item[0]
        channelName = item[1]
        currencyPrefix = item [2]
        myTuple = (irc,userName,channelName,currencyPrefix)
        thread = threading.Thread(target=runAsThread,args = myTuple,)
        thread.start()
        threads.append(thread)
        time.sleep(5) # wait to avoid too many connections to IRC at once from same IP
    except Exception as e:
        print("An error occurred while creating threads.")
        print(str(e))

#create readbuffer to hold strings from IRC
readbuffer = ""

# This is the main loop
while 1:
    readbuffer= readbuffer+self.irc.recv(1024).decode("utf-8")
    temp=str.split(readbuffer, "\n")
    readbuffer=temp.pop( )
    #
    #Need to send readbuffer to each IRCBetBot() created in runAsThread that contains a while 1: loop to listen for strings in its __init__() method.
    #   

print ("Waiting...")

for thread in threads:
    thread.join()

print ("Complete.")

I need to somehow get the readbuffer from the main loop into each IRCBetBot object created in separate threads? Any ideas?

Upvotes: 1

Views: 2792

Answers (2)

dano
dano

Reputation: 94901

Here's an example that shows how you can do this using a queue for each thread. Instead of just creating a list of threads, we create a dict of threads with the channel as the key, and store both the thread object and a queue that can be used to talk to the thread in the dict.

#!/usr/bin/python3

import threading
from queue import Queue


class IRCBetBot(threading.Thread):
    def __init__(self, q, playerName, channelName, currencyName):
        super().__init__()
        self.channel = channelName
        self.playerName = playerName
        self.currencyName = currencyName
        self.queue = q 

    def run(self):
        readbuffer = ""
        while 1:
            readbuffer = self.queue.get()  # This will block until a message is sent to the queue.
            print("{} got msg {}".format(self.channel, readbuffer))

if __name__ == "__main__":

    authorisedChannels = [["user1","#channel1","coin1"],
                          ["user2","#channel2","coin2"],
                          ["user3","#channel3","coin3"]]

threads = {}
for item in authorisedChannels:
    try:
        userName = item[0]
        channelName = item[1]
        currencyPrefix = item [2]
        myTuple = (userName,channelName,currencyPrefix)
        q = Queue() 
        thread = IRCBetBot(q, *myTuple )
        thread.start()
        threads[channelName] = (q, thread)
    except Exception as e:
        print("An error occurred while creating threads.")
        print(str(e))

while 1:
    a = input("Input your message (channel: msg): ")
    channel, msg = a.split(":")
    threads[channel][0].put(msg)  # Sends a message using the queue object

As you can see, when messages come into the socket, we parse the channel out (which your code already does) and then just pass the message on to the appropriate queue in our thread dict.

Sample output (slightly tweaked so the output isn't scrambled due to the concurrent print calls):

dan@dantop:~$ ./test.py 
Input your message (channel: msg): #channel1: hi there
#channel1 got msg  hi there
Input your message (channel: msg): #channel2: another one
#channel2 got msg  another one

Upvotes: 3

Victor Parmar
Victor Parmar

Reputation: 74

Well one way to do it would be to have an array of readBuffers similar to the array of threads. And then each thread basically is waiting on data on it's particular readbuffer.

When you get in data you can pass it to the thread you're interested in or just copy the data over to all the readbuffers and let the threads process it if they are interested. An observer pattern would work best in this case.

Upvotes: 0

Related Questions