1

I've found on GitHub an open source project that is very simple: the same Python script can be used as a Server or as a Client.

import socket
import threading
import queue
import sys
import random
import os


#Client Code
def ReceiveData(sock):
    while True:
        try:
            data,addr = sock.recvfrom(1024)
            print(data.decode('utf-8'))
        except:
            pass

def RunClient(serverIP):
    host = socket.gethostbyname(socket.gethostname())
    port = random.randint(6000,10000)
    print('Client IP->'+str(host)+' Port->'+str(port))
    server = (str(serverIP),5000)
    s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    s.bind((host,port))

    name = input('Please write your name here: ')
    if name == '':
        name = 'Guest'+str(random.randint(1000,9999))
        print('Your name is:'+name)
    s.sendto(name.encode('utf-8'),server)
    threading.Thread(target=ReceiveData,args=(s,)).start()
    while True:
        data = input()
        if data == 'qqq':
            break
        elif data=='':
            continue
        data = '['+name+']' + '->'+ data
        s.sendto(data.encode('utf-8'),server)
    s.sendto(data.encode('utf-8'),server)
    s.close()
    os._exit(1)
#Client Code Ends Here


#Server Code
def RecvData(sock,recvPackets):
    while True:
        data,addr = sock.recvfrom(1024)
        recvPackets.put((data,addr))

def RunServer():
    host = socket.gethostbyname(socket.gethostname())
    port = 5000
    print('Server hosting on IP-> '+str(host))
    s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    s.bind((host,port))
    clients = set()
    recvPackets = queue.Queue()

    print('Server Running...')

    threading.Thread(target=RecvData,args=(s,recvPackets)).start()

    while True:
        while not recvPackets.empty():
            data,addr = recvPackets.get()
            if addr not in clients:
                clients.add(addr)
                continue
            clients.add(addr)
            data = data.decode('utf-8')
            if data.endswith('qqq'):
                clients.remove(addr)
                continue
            print(str(addr)+data)
            for c in clients:
                if c!=addr:
                    s.sendto(data.encode('utf-8'),c)

    s.close()
#Serevr Code Ends Here

if __name__ == '__main__':
    if len(sys.argv)==1:
        RunServer()
    elif len(sys.argv)==2:
        RunClient(sys.argv[1])
    else:
        print('Run Serevr:-> python Chat.py')
        print('Run Client:-> python Chat.py <ServerIP>')

As you can see by the picture it works really well:

enter image description here

But my goal is to make the Server join the chat too.

Is it possible for a UDP chat room to have the Server to act as a Client too while it's working as a Server?

Francesco Mantovani
  • 10,216
  • 13
  • 73
  • 113

1 Answers1

0

To have the server also run a client, you can insert this line just before the call to RunServer() in the __main__ part of the code:

    threading.Thread(target=RunClient,args=('127.0.0.1',)).start()

That will launch a thread that runs RunClient('127.0.0.1') in parallel with the server's event-loop.

However, there are a couple of other problems with the code that you'll also need to fix before this will work correctly. In particular:

At the top of RunServer(), you set host to the IP address of one of the network interface cards on the server machine. That means that the server can only accept incoming TCP connections on that one network interface, which means that (unless that network interface happens to be the internal/loopback interface), that the server won't accept the connection to 127.0.0.1 from its own client-thread. The easy fix for that is to simple set host to an empty string instead, that way the server will accept incoming TCP connections on all local network interfaces and the problem goes away.

def RunServer():
    host = '' # was:  socket.gethostbyname(socket.gethostname())
    [...]

The second problem is that your server's event-loop is busy-waiting, causing the server process to unnecessarily use up 100% of a CPU core the entire time it is running. That's terribly inefficient. The cause of that problem is here, inside RunServer():

while True:
    while not recvPackets.empty():
        data,addr = recvPackets.get()
        [...]

Note that in the Python Queue class, it is the call to get() that blocks until there is more data to receive, preventing the CPU from spinning. But in the above code, get() is never called unless/until recvPackets is non-empty, so the thread never blocks.

The fix is simple, just delete the while not recvPackets.empty() line entirely, so that get() will be called even when the Queue is empty. get() will not return until it has data to return.

while True:
   data,addr = recvPackets.get()
   [...]
Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234