Multithreading sockets with a central relay-like server - python

I have previously managed to implement a client-server socket script which relays messages between a single client and the server and I'm now trying to implement a multiple-client system.
More specifically, I would like to use the server as some sort of medium between two clients which retrieves information from one client and relays it to the other. I had tried to attach and send the port number of the receiving client and then extract it from the message on the server side. After that, I would try and send it to whatever socket with that port number but I ran into some trouble (as port numbers are determined at the point of sending I believe?) so now I am simply just trying to relay the sent message back to all clients. However, the problem is that the message is only being sent to the server and not being relayed to the desired client.
I had previously tried to implement a peer-to-peer system but I ran into trouble so I decided to take a step back and do this instead.
Server.py:
import socket, _thread, threading
import tkinter as tk
SERVERPORT = 8600
HOST = 'localhost'
class Server():
def __init__(self):
self.Connected = True
self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
self.ServerSocket.bind((HOST, SERVERPORT))
self.ServerSocket.listen(2)
self.Clients = []
def Listen(self):
print('Server is now running')
while self.Connected:
ClientSocket, Address = self.ServerSocket.accept()
self.Clients.append(Address)
print('\nNew user connected', Address)
t = threading.Thread(target=self.NewClient, args=(ClientSocket,
Address))
t.daemon = True
t.start()
self.Socket.close()
def NewClient(self, ClientSocket, Address):
while self.Connected:
if ClientSocket:
try:
ReceivedMsg = ClientSocket.recv(4096)
print('Message received from', Address, ':', ReceivedMsg)
self.Acknowledge(ClientSocket, Address)
if ReceivedMsg.decode('utf8').split()[-1] != 'message':
ReceiverPort = self.GetSendPort(ReceivedMsg)
self.SendToClient(ClientSocket,ReceivedMsg,ReceiverPort)
except:
print('Connection closed')
raise Exception
ClientSocket.close()
def Acknowledge(self, Socket, Address):
Socket.sendto(b'The server received your message', Address)
def GetSendPort(self, Msg):
MsgDigest = Msg.decode('utf8').split()
return int(MsgDigest[-1])
def SendToClient(self, Socket, Msg, Port):
Addr = (HOST, Msg)
for Client in self.Clients:
Socket.sendto(Msg, Client)
def NewThread(Func, *args):
if len(args) == 1:
t = threading.Thread(target=Func, args=(args,))
elif len(args) > 1:
t = threading.Thread(target=Func, args=args)
else:
t = threading.Thread(target=Func)
t.daemon = True
t.start()
t.join()
Host = Server()
NewThread(Host.Listen)
And the Client(.py):
import socket, threading
import tkinter as tk
Username = 'Ernest'
PORT = 8601
OtherPORT = 8602
SERVERPORT = 8600
HOST = '127.0.0.1'
class Client():
def __init__(self, Username):
self.Connected, self.Username = False, Username
self.Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def Connect(self):
print('Trying to connect')
try:
self.Socket.connect((HOST, SERVERPORT))
self.Connected = True
print(self.Username, 'connected to server')
Msg = MsgUI(self.Username)
Msg.Display()
except Exception:
print('Could not connect to server')
raise Exception
def SendMsg(self):
if self.Connected:
Msg = '{} sent you a message {}'.format(self.Username, OtherPORT)
self.Socket.sendall(bytes(Msg, encoding='utf8'))
self.GetResponse()
def GetResponse(self, *args):
AckMsg = '\n{} received the message'.format(self.Username)
NMsg = '\n{} did not receive the message'.format(self.Username)
if self.Connected:
Msg = self.Socket.recv(4096)
print(Msg)
if Msg:
self.Socket.sendall(bytes(AckMsg, encoding='utf8'))
else:
self.Socket.sendall(bytes(NMsg, encoding='utf8'))
class MsgUI():
def __init__(self, Username):
self.Username = Username
self.entry = tk.Entry(win)
self.sendbtn = tk.Button(win, text='send', command=Peer.SendMsg)
def Display(self):
self.entry.grid()
self.sendbtn.grid()
win.mainloop()
win = tk.Tk()
Peer = Client(Username)
Peer.Connect()
I want a message to be sent whenever the user presses the send button in the tkinter window, but at the same time, it is continually 'listening' to see if it received any messages.
I also previously tried to run the GetResponse method in the Client in another thread and instead of if self.Connected I used while self.Connected and it still didn't work.
UPDATE
After some helpful comments, I have edited the two files as such:
The server now holds the two sockets for each client which is run first. The server file is imported into the client file as a module. Each client file is then run and each client runs a function in the server file, requesting to use the socket. If the request is allowed (i.e. no error was thrown), the socket is connected, added to a set of clients stored in the server file and then returned to the client file. The client then uses this socket to send and receive messages.
Server.py
import socket, _thread, threading
import tkinter as tk
SERVERPORT = 8600
HOST = 'localhost'
class Server():
def __init__(self):
self.Connected = True
self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
self.ServerSocket.bind((HOST, SERVERPORT))
self.ServerSocket.listen(2)
self.Clients = {}
def ConnectClient(self, Username, Port):
Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.Clients[Username] = [Socket, Port, False]
try:
self.Clients[Username][0].connect((HOST, SERVERPORT))
self.Clients[Username][2] = True
print('Opened port for user', Username)
return Socket
except Exception:
print('Could not open port for user', Username)
raise Exception
def Listen(self):
print('Server is now running')
while self.Connected:
ClientSocket, Address = self.ServerSocket.accept()
print('\nNew user connected', Address)
t = threading.Thread(target=self.NewClient, args=(ClientSocket,
Address))
t.daemon = True
t.start()
self.Socket.close()
def NewClient(self, ClientSocket, Address):
while self.Connected:
if ClientSocket:
try:
ReceivedMsg = ClientSocket.recv(4096)
if b'attempting to connect to the server' in ReceivedMsg:
ClientSocket.send(b'You are now connected to the server')
else:
print('Message received from', Address, ':',ReceivedMsg)
#self.Acknowledge(ClientSocket, Address)
ReceiverPort = self.GetSendPort(ReceivedMsg)
if ReceiverPort != None:
self.SendToClient(ClientSocket,ReceivedMsg,
ReceiverPort)
except:
print('Connection closed')
raise Exception
ClientSocket.close()
def Acknowledge(self, Socket, Address):
Socket.sendto(b'The server received your message', Address)
def GetSendPort(self, Msg):
MsgDigest = Msg.decode('utf8').split()
try:
Port = int(MsgDigest[-1])
except ValueError:
Port = None
return Port
def SendToClient(self, Socket, Msg, Port):
Addr = (HOST, Port)
Receiver = None
for Client, Vars in self.Clients.items():
if Vars[1] == Port:
Receiver = Client
self.Clients[Receiver][0].sendto(Msg, Addr)
def NewThread(Func, *args):
if len(args) == 1:
t = threading.Thread(target=Func, args=(args,))
elif len(args) > 1:
t = threading.Thread(target=Func, args=args)
else:
t = threading.Thread(target=Func)
t.daemon = True
t.start()
t.join()
Host = Server()
if __name__ == '__main__':
NewThread(Host.Listen)
And Client.py
import socket, threading, Server
import tkinter as tk
Username = 'Ernest'
PORT = 8601
OtherPORT = 8602
SERVERPORT = 8600
HOST = '127.0.0.1'
class Client():
def __init__(self, Username):
self.Connected, self.Username = False, Username
def Connect(self):
print('Requesting to connect to server')
try:
self.Socket = Server.Host.ConnectClient(self.Username, PORT)
self.Connected = Server.Host.Clients[self.Username][2]
Msg = '{} is attempting to connect to the server'.format(self.Username)
self.Socket.sendall(bytes(Msg, encoding='utf8'))
ReceivedMsg = self.Socket.recv(4096)
print(ReceivedMsg)
Msg = MsgUI(self.Username)
Msg.Display()
except Exception:
print('Could not connect to server')
raise Exception
def SendMsg(self):
try:
if self.Connected:
Msg = '{} sent you a message {}'.format(self.Username,OtherPORT)
self.Socket.sendall(bytes(Msg, encoding='utf8'))
self.GetResponse()
except Exception:
print('Connection closed')
raise Exception
def GetResponse(self, *args):
AckMsg = '\n{} received the message'.format(self.Username)
NMsg = '\n{} did not receive the message'.format(self.Username)
if self.Connected:
Msg = self.Socket.recv(4096)
print(Msg)
if Msg:
self.Socket.sendall(bytes(AckMsg, encoding='utf8'))
else:
self.Socket.sendall(bytes(NMsg, encoding='utf8'))
class MsgUI():
def __init__(self, Username):
self.Username = Username
self.entry = tk.Entry(win)
self.sendbtn = tk.Button(win, text='send', command=Peer.SendMsg)
def Display(self):
self.entry.grid()
self.sendbtn.grid()
win.mainloop()
win = tk.Tk()
Peer = Client(Username)
Peer.Connect()
Now the problem is more of a python and scope problem. When trying to relay the message back to the client, I was getting a KeyError as the Clients dictionary was still empty. When making the function call to the server in the client file, it's clear that the update to the dictionary happens in the client file rather than the server file - which is in a different instance. I need a method of changing the contents of the Clients dictionary that is called to action by the client file but takes effect in the server file.

Are you committed to multithreading? Threads don't run concurrently in python ( due to the GIL), and while they are one way to handle concurrent operations, they aren't the only way and usually they're not the best way, unless they're the only way. Consider this code, which doesn't handle failure cases well, but seems to work as a starting point.
import socket, select, Queue
svrsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
svrsock.setblocking(0)
svrsock.bind(('', 17654))
svrsock.listen(16)
client_queues = {}
write_ready=[] # we'll update this for clients only that have things in the queue
while client_queues.keys() + [svrsock] :
readable, writable, exceptional = select.select(client_queues.keys() + [svrsock] , write_ready, [])
for rd in readable:
if rd is svrsock: # reading listening socket == accepting connection
conn, addr = svrsock.accept()
print("Connection from {}".format(addr))
conn.setblocking(0)
client_queues[conn] = Queue.Queue()
else:
data = rd.recv(1024)
if data:
# TODO: send to all queues
print("Message from {}".format(rd.getpeername()))
for sock, q in client_queues.iteritems():
q.put("From {}: {}".format( rd.getpeername(), data))
if sock not in write_ready:
write_ready.append(sock)
for rw in writable:
try:
data = client_queues[rw].get_nowait()
rw.send(data)
except Queue.Empty:
write_ready.remove(rw)
continue
The concept is pretty simple. The server accepts connections; each connection (socket) is associated with a queue of pending messages. Each socket that's ready for reading is read from, and its message is added to each client's queue. The recipient client is added into the write_ready list of clients with data pending, if it's not already in there. Then each socket that's ready for writing has its next queued message written to it. If there are no more messages, the recipient is removed from the write_ready list.
This is very easy to orchestrate if you don't use multithreading because all coordination is inherent in the order of the application. With threads it would be more difficult and a lot more code, but probably not more performance due to the gil.
The secret to handling multiple I/O streams concurrently without multithreading is select. In principle it's pretty easy; we pass select() a list of possible sockets for reading, another list of possible sockets for writing, and a final list that for this simplified demo I completely ignore . The results of the select call will include one or more sockets that are actually ready for reading or writing, which allows me to block until one or more sockets are ready for activity. I then process all the sockets ready for activity every pass ( but they've already been filtered down to just those which wouldn't block).
There's a ton still to be done here. I don't cleanup after myself, don't track closed connections, don't handle any exceptions, and so on. but without having to worry about threading and concurrency guarantees, it's pretty easy to start addressing these deficiencies.
Here it is "in action". Here for the client side I use netcat, which is perfect for layer 3 testing without layer 4+ protocols ( in other words, raw tcp so to speak). It simply opens a socket to the given destination and port and sends its stdin through the socket and sends its socket data to stdout, which makes it perfect for demoing this server app!
I also wanted to point out, coupling code between server and client is inadvisable because you won't be able to roll out changes to either without breaking the other. It's ideal to have a "contract" so to speak between server and client and maintain it. Even if you implement the behavior of server and client in the same code base, you should use the tcp communications contract to drive your implementation, not code sharing. Just my 2 cents, but once you start sharing code you often start coupling server/client versions in ways you didn't anticipate.
the server:
$ python ./svr.py
Connection from ('127.0.0.1', 52059)
Connection from ('127.0.0.1', 52061)
Message from ('127.0.0.1', 52061)
Message from ('127.0.0.1', 52059)
Message from ('127.0.0.1', 52059)
First client ( 52059):
$ nc localhost 17654
hello
From ('127.0.0.1', 52061): hello
From ('127.0.0.1', 52059): hello
From ('127.0.0.1', 52059): hello
Second client:
$ nc localhost 17654
From ('127.0.0.1', 52061): hello
hello
From ('127.0.0.1', 52059): hello
hello
From ('127.0.0.1', 52059): hello
If you need more convincing on why select is way more compelling than concurrent execution, consider this: Apache is based on a threading model, in other words, the connections each get a worker thread . nginx is based on a select model, so you can see how much faster that can potentially be. Not to say that nginx is inherently better, as Apache benefits from the threading model because of its heavy use of modules to extend capabilities ( mod_php for example), whereas nginx doesn't have this limitation and can handle all requests from any thread. But the raw performance of nginx is typically considered far higher and far more efficient, and a big reason for this is that it avoids almost all the cpu context switches inherent in apache. It's a valid approach!
A word on scaling. Obviously, this wouldn't scale forever. Neither would a threading model; eventually you run out of threads. A more distributed and high throughput system would likely use a Pub/Sub mechanism of some kind, offloading the client connection tracking and message queueing from the server to a pub/sub data tier and allowing connections to be restored and queued data to be sent, as well as adding multiple servers behind a load balancer. Just throwing it out there. You might be pleasantly surprised how well select can scale ( cpu is so much faster than network anyway that it's likely not the bottleneck).

Related

Implementing TCP Server with Python-How to?

I have searched a lot on the forums for implementing TCP server using Python. All that I could find is a multi-threaded approach to TCP Server Implementation on a single port for interacting with clients.
I am looking for Server sample code for creating sockets using different ports.I have clients with distinct port numbers.For example, One socket binding IP and portNo-2000 and second one binding IP and another port No. 3000 and so on. Could anybody provide pointers?
import socket
from typing import Union
SERVERS_PORT_LIST = []
def start_server(port) -> Union[socket.socket, None]:
server_socket = socket.socket()
try:
server_socket.bind(("0.0.0.0", port))
server_socket.listen()
return server_socket
except (socket.error, OSError): # in case the port is taken
return None
def accept_client(server_sock: socket.socket) -> Union[tuple[socket.socket, tuple[str, str]], tuple[None, None]]:
# wait 5 seconds for a client to connect, if no one
# tries to connect within 5 seconds it will return None
server_sock.settimeout(5)
try:
client_socket, client_addr = server_sock.accept()
return client_socket, client_addr
except (socket.error, ConnectionRefusedError):
return None, None
def main():
servers_client_sockets = {}
# create all the servers
for port in SERVERS_PORT_LIST:
server_sock = start_server(port)
if server_sock is not None:
servers_client_sockets[(server_sock, port)] = []
else:
print(f"Port {port} is taken.")
# accept 1 client for each server and append the client to the list inside the dict
# that contains {server_sock: [list of clients], server_sock2: [list of clients], ...}
for server_sock, port in servers_client_sockets.keys():
client_socket, client_addr = accept_client(server_sock)
if client_socket is not None: # someone connected
servers_client_sockets[(server_sock, port)] = servers_client_sockets[server_sock].append(client_socket)
else:
print(f"No client on port {port}.")
# handle the client in each server sock
if __name__ == '__main__':
main()
answer to your question:
I have made a little change to the code.
to receive a client data you need to go through the dict servers_client_sockets keys, and search for the port you want to receive from.
the dict is built like the following: {(server_sock, port): [clients], (server_2_sock, port): [clients], ..., (server_x_sock, port): [clients]}
example:
for server_sock, port in servers_client_sockets.keys():
if port == the_port_you_want:
client_list = servers_client_sockets[(server_sock, port)]
now client_list should contains all the clients that connected to the port you were looking for.
if there is only one client in each port you can do this:
data clients_list[1].recv(a_number_that_says_how_many_bytes_to_recv).decode()
# with out '.decode()' if you want to keep the data as bytes
if there is more than one client you will need to do a loop on the code from above (receive the data for each one of the clients)
NOTE:
if you want to accept more than 1 client you will need to add more code, and insert the client socket to the dict servers_client_sockets (I explained above how the dict is built).
use this code to accept 1 more client for each server port
# loops on all the server sockets
for server_sock, port in servers_client_sockets.keys():
# tries to accept a client for each server socket
client_socket, client_addr = accept_client(server_sock)
# if there is a new client it adds it to the dict servers_client_sockets
if client_socket is not None: # someone connected
servers_client_sockets[(server_sock, port)] = servers_client_sockets[server_sock].append(client_socket)
# if there is no new client, it prints no client on port x
else:
print(f"No client on port {port}.")
Using asyncio you can write elegant multi-servers and clients without having to use more than one thread or having to invert the control flow for efficient single-threaded non-blocking I/O.
asyncio allows one to:
write I/O code in straight-forward sequential/blocking manner, which in the past required using one thread per connection;
execute this code using only one thread of execution in non-blocking fashion, which in the past required inverting the control flow of your sequential blocking I/O code into callback-hell based programming model.
You can find example TCP server and client in Python Standard library documentation, asyncio examples.
Server:
import asyncio
class EchoServerProtocol(asyncio.Protocol):
def connection_made(self, transport):
peername = transport.get_extra_info('peername')
print('Connection from {}'.format(peername))
self.transport = transport
def data_received(self, data):
message = data.decode()
print('Data received: {!r}'.format(message))
print('Send: {!r}'.format(message))
self.transport.write(data)
print('Close the client socket')
self.transport.close()
async def main():
# Get a reference to the event loop as we plan to use
# low-level APIs.
loop = asyncio.get_running_loop()
server = await loop.create_server(
lambda: EchoServerProtocol(),
'127.0.0.1', 8888)
async with server:
await server.serve_forever()
asyncio.run(main())

python can't get a socket server to listen in a thread

Having a small problem with a multithreaded socket server in Python 3. I can't seem to start my socket listening in its own thread. I'm probably way off base.
Borrowed the code here: How to make a simple multithreaded socket server in Python that remembers clients
But I need to listen for clients within a thread or at least in the background. Can't figure out what I'm doing wrong.
Here's my code:
import socket
from threading import Thread
from cmd import Cmd
class ThreadedServer(Thread):
def __init__(self):
self.host = "127.0.0.1"
self.port = int(8080)
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind((self.host, self.port))
def listen(self):
self.sock.listen(5)
print("[Info]: Listening for connections on {0}, port {1}".format(self.host,self.port))
while True:
print("Hello?") # Just debug for now
client, address = self.sock.accept()
client.settimeout(60)
threading.Thread(target = self.listenToClient,args = (client,address)).start()
def listenToClient(self, client, address):
size = 1024
while True:
try:
data = client.recv(size)
if data:
# Set the response to echo back the recieved data
response = data
client.send(response)
else:
raise error('Client disconnected')
except:
client.close()
return False
class CommandInput(Cmd):
# Able to accept user input here but is irrelevant right now
pass
print("[Info]: Loading complete.")
clientThread = ThreadedServer().listen()
clientThread.start()
print("[Info]: Server ready!")
prompt = CommandInput()
prompt.prompt = '> '
prompt.cmdloop("[Info]: Type \'help\' for a list of commands and their descriptions/use")
As you can see I have some code after my listening part where I need to be able to accept input on the terminal. However, the code never gets there.
Here's the output quite simply:
[Info]: Loading complete.
[Info]: Listening for connections on 127.0.0.1, port 8080
Hello?
I'm expecting:
[Info]: Loading complete.
[Info]: Listening for connections on 127.0.0.1, port 8080
Hello?
[Info]: Type \'help\' for a list of commands and their descriptions/use
>
with a cursor ready for me to type.
So how can I properly get my program to listen for clients in that loop in a proper thread so that I can enter prompts at the command line and process user input (for example one of the commands I want to implement is that of a client "send" which I would be able to send debug information to connected clients)
Thanks for any assistance you can provide.
This may not be the ideal structure for setting up what you want, but seems to solve the requirement you stated.
After launching this script on a terminal, you can make sample client connections by opening one, or more, browser tabs to localhost:8080
import socket
from threading import Thread
from cmd import Cmd
# basic threading tutorial: https://www.tutorialspoint.com/python3/python_multithreading.htm
class ThreadedServer(Thread):
def __init__(self):
Thread.__init__(self) # change here
self.host = "127.0.0.1"
self.port = int(8080)
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind((self.host, self.port))
def run(self): # change here
self.sock.listen(5)
print("[Info]: Listening for connections on {0}, port {1}".format(self.host,self.port))
while True:
print("Hello?") # Just debug for now
client, address = self.sock.accept()
client.settimeout(60)
Thread(target = self.listenToClient, args = (client,address)).start() # change here
def listenToClient(self, client, address):
size = 1024
while True:
try:
data = client.recv(size)
if data:
# Set the response to echo back the recieved data
response = data
client.send(response)
else:
raise error('Client disconnected')
except:
client.close()
return False
class CommandInput(Cmd):
# Able to accept user input here but is irrelevant right now
pass
if __name__ == "__main__":
print("[Info]: Loading complete.")
server = ThreadedServer() # change here
server.start() # change here
print("[Info]: Server ready!")
prompt = CommandInput()
prompt.prompt = '> '
prompt.cmdloop("[Info]: Type \'help\' for a list of commands and their descriptions/use")

Socket Server with multiply Clients

I just started programming Python.
My goal is to built a digital Picture Frame with three Screens. Therefore I use 3 Raspis, one for each Monitor.
For the communication of these Raspis I need to program a server and a Client.
For a first test I want to built a server which is able to send and receive messages to/from multiple clients.
So I started with a few socket tutorials an created the following program.
Server Class (TcpServer.py)
class TcpServer:
clients = []
serverIsRunning = 0
port = 0
def __init__(self, port):
self.port = port
self.serverIsRunning = 0
self.serverRunning = 0
def startServer (self):
print("start Server...")
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.bind(("", self.port))
self.server.listen(1)
self.serverRunning = 1
while self.serverRunning:
read, write, oob = select.select([self.server] + self.clients, [], [])
for sock in read:
if sock is self.server:
client, addr = self.server.accept()
self.clients.append(client)
print ("+++ Client ", addr[0], " verbunden")
else:
nachricht = sock.recv(1024)
ip = sock.getpeername()[0]
if nachricht:
print (ip, nachricht)
else:
print ("+++ Verbindung zu ", ip , " beendet")
sock.close()
self.clients.remove(sock)
for c in self.clients:
c.close()
self.clients.remove(c)
self.server.close()
def send(self, message):
message = message.encode()
self.server.send(message)
Client class (TcpClient.py)
import socket
class TcpClient:
def __init__(self, ip, port):
self.serverAdress = (ip, port)
self.connected = 0
self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.connection.connect(self.serverAdress)
print ("connectet to ", self.serverAdress)
def send(self, message):
message = message.encode()
self.connection.send(message)
Server:
import threading
import TcpServer
tcpServer = TcpServer.TcpServer(50000)
threadTcpServer = threading.Thread(target = tcpServer.startServer)
threadTcpServer.start()
while True:
tcpServer.send(input("Nachricht eingeben: "))
Client:
import threading
import TcpClient
tcpClient = TcpClient.TcpClient("192.168.178.49", 50000)
while True:
tcpClient.send(input("Nachricht eingeben: "))
I can send messages from the Client to the server, but when I want to send a Message from the server to the client it generates the following error:
BrokenPipeError: [Errno 32] Broken pipe
I assume it is because the server thread blocks the socket while waiting of a incoming message. But I have no idea how to handle this.
How can I program a server who can send and receive messages? Can you recommend a tutorial? I didn't found a tutorial who describes a solution for my problem.
Edit:
Now I tried to solve the problem with the socketserver library, but I still can't solve may problem.
here is my new code for the server:
import socketserver
import threading
import time
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
The RequestHandler class for our server.
It is instantiated once per connection to the server, and must
override the handle() method to implement communication to the
client.
"""
def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Create the server, binding to localhost on port 9999
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
threadTcpServer = threading.Thread(target = server.serve_forever)
threadTcpServer.start()
print("server started")
time.sleep(10)
print("sending Data")
server.request.sendall("Server is sending...")
it generates the error:
AttributeError: 'TCPServer' object has no attribute 'request'
My goal is to write a server with a thread who receives Data and still be able to send data from a other thread.
Is this even possible with only one socket?
You should use the provided socketserver rather than writing all the handling of sockets and select etc.
There are multiple problems with your code -
1 - The server is trying to write to the listening socket!! The client communication socket is the one that you get from the accept() call and that is the one you have to use for reading and writing.
2 - The client is sending the data and completing immediately, but it should really wait for getting a response. Otherwise, the python / OS will close the client socket as soon as the program completes and it will mostly be before the server gets a chance to respond.
I believe with the Handler code you are able to receive the data sent by the client on the server and are also able to send some data back from the Handler to the client? You must have understood that the server cannot send any data back unless there is a client connected to it?
Now, to send data to the client (or clients) from "another" thread, you will need a way to make the handler objects or the client sockets (available inside the Handler object as self.request) available to the "another" thread.
One way is to override the def __init__(self, request, client_address, server): method and save this object's reference in a global list. Remember to do the below as the last line of the overridden init -
# BaseRequestHandler __init__ must be the last statement as all request processing happens in this method
socketserver.BaseRequestHandler.__init__(self, request, client_address, server)
Once you have all the client handlers in the global list, you can easily write to all the clients from any thread as per your needs. You must read about synchronization (Locks) and understand that using same object / socket from multiple threads can create some logical / data issues with your application.
Another thing that you have to worry about and code for is cleaning up this global list whenever a client closes the connection.

Python Sockets, Advanced Chat Box

I want to create a server that handles a lot of clients at the same time (handles: receiving data from clients and sending data to all clients at the same time!!!)
Actually i'm trying to create a chat box. The program will work like this:
1) There's going to be a server which handles the clients.
2) More than one clients can join the server.
3) Clients send messages (Strings) to the server.
4) The server receive's the message from a client and then it send's it to all
the clients except the one he got it from.
And this is how the clients will communicate each other. No private messages available. When someone hits the enter all the clients will see the message on their screen.
The client module is easy to make, because the client communicates only with one socket (The Server).
The server module from the other hand is really complicated, i don't know how to do it (I also know about threads).
This is my atempt:
import socket, threading
class Server:
def __init__(self, ip = "", port = 5050):
'''Server Constructor. If __init__ return None, then you can use
self.error to print the specified error message.'''
#Error message.
self.error = ""
#Creating a socket object.
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#Trying to bind it.
try:
self.server.bind( (ip, port) )
pass
#Failed, because socket has been shuted down.
except OSError :
self.error = "The server socket has been shuted down."
return None
#Failed, because socket has been forcibly reseted.
except ConnectionResetError:
self.error = "The server socket has been forcibly reseted."
return None
#Start Listening.
self.server.listen()
#_____Other Variables_____#
#A flag to know when to shut down thread loops.
self.running = True
#Store clients here.
self.clients = []
#_____Other Variables_____#
#Start accepting clients.
thread = threading.thread(target = self.acceptClients)
thread.start()
#Start handling the client.
self.clientHandler()
#Accept Clients.
def acceptClients(self):
while self.running:
self.clients.append( self.server.accept() )
#Close the server.
self.server.close()
#Handle clients.
def clientHandler(self):
while self.running:
for client in self.clients:
sock = client[0]
addr = client[1]
#Receive at most 1 mb of data.
#The problem is that recv will block the loop!!!
data = sock.recv(1024 ** 2)
As you can see, i accept clients using a thread so the server.accept() won't block the program. And then i store the clients into a list.
But the problem is on the clientHandler. How am i going to recv from all
clients at the same time? The first recv will block the loop!!!
I also tried to start new threads (clientHandlers) for every new client
but the problem was the synchronization.
And what about the send? The server must send data to all the clients, so the clientHandler is not yet finished. But if i mix the methods recv and send then the problem become's more complicated.
So what is the proper and best way to do this?
I'd like to give me an example too.
Multithreading is great when the different clients are independant of each other: you write your code as if only one client existed and you start a thread for each client.
But here, what comes from one client must be send to the others. One thread per client will certainly lead to a synchronization nightmare. So let's call select to the rescue! select.select allows to poll a list of sockets and returns as soon as one is ready. Here you can just build a list containing the listening socket and all the accepted ones (that part is initially empty...):
when the listening socket is ready for read, accept a new socket and add it to the list
when another socket is ready for read, read some data from it. If you read 0 bytes, its peer has been shut down or closed: close it and remove it from the list
if you have read something from one accepted socket, loop on the list, skipping the listening socket and the one from which you have read and send data to any other one
Code could be (more or less):
main = socket.socket() # create the listening socket
main.bind((addr, port))
main.listen(5)
socks = [main] # initialize the list and optionaly count the accepted sockets
count = 0
while True:
r, w, x = select.select(socks, [], socks)
if main in r: # a new client
s, addr = main.accept()
if count == mx: # reject (optionaly) if max number of clients reached
s.close()
else:
socks.append(s) # appends the new socket to the list
elif len(r) > 0:
data = r[0].recv(1024) # an accepted socket is ready: read
if len(data) == 0: # nothing to read: close it
r[0].close()
socks.remove(r[0])
else:
for s in socks[1:]: # send the data to any other socket
if s != r[0]:
s.send(data)
elif main in x: # close if exceptional condition met (optional)
break
elif len(x) > 0:
x[0].close()
socks.remove(x[0])
# if the loop ends, close everything
for s in socks[1:]:
s.close()
main.close()
You will certainly need to implement a mechanism to ask the server to stop, and to test all that but it should be a starting place
This my final program and works like a charm.
Server.py
import socket, select
class Server:
def __init__(self, ip = "", port = 5050):
'''Server Constructor. If __init__ return None, then you can use
self.error to print the specified error message.'''
#Error message.
self.error = ""
#Creating a socket object.
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#Trying to bind it.
try:
self.server.bind( (ip, port) )
pass
#Failed, because socket has been shuted down.
except OSError :
self.error = "The server socket has been shuted down."
#Failed, because socket has been forcibly reseted.
except ConnectionResetError:
self.error = "The server socket has been forcibly reseted."
#Start Listening.
self.server.listen()
#_____Other Variables_____#
#A flag to know when to shut down thread loops.
self.running = True
#Store clients here.
self.sockets = [self.server]
#_____Other Variables_____#
#Start Handling the sockets.
self.handleSockets()
#Handle Sockets.
def handleSockets(self):
while True:
r, w, x = select.select(self.sockets, [], self.sockets)
#If server is ready to accept.
if self.server in r:
client, address = self.server.accept()
self.sockets.append(client)
#Elif a client send data.
elif len(r) > 0:
#Receive data.
try:
data = r[0].recv( 1024 )
#If the client disconnects suddenly.
except ConnectionResetError:
r[0].close()
self.sockets.remove( r[0] )
print("A user has been disconnected forcible.")
continue
#Connection has been closed or lost.
if len(data) == 0:
r[0].close()
self.sockets.remove( r[0] )
print("A user has been disconnected.")
#Else send the data to all users.
else:
#For all sockets except server.
for client in self.sockets[1:]:
#Do not send to yourself.
if client != r[0]:
client.send(data)
server = Server()
print("Errors:",server.error)
Client.py
import socket, threading
from tkinter import *
class Client:
def __init__(self, ip = "192.168.1.3", port = 5050):
'''Client Constructor. If __init__ return None, then you can use
self.error to print the specified error message.'''
#Error message.
self.error = ""
#Creating a socket object.
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#Trying to bind it.
try:
self.server.connect( (ip, port) )
pass
#Failed, because socket has been shuted down.
except OSError :
self.error = "The client socket has been shuted down."
return
#Failed, because socket has been forcibly reseted.
except ConnectionResetError:
self.error = "The client socket has been forcibly reseted."
return
#Failed, because socket has been forcibly reseted.
except ConnectionRefusedError:
self.error = "The server socket refuses the connection."
return
#_____Other Variables_____#
#A flag to know when to shut down thread loops.
self.running = True
#_____Other Variables_____#
#Start the GUI Interface.
def startGUI(self):
#Initialiazing tk.
screen = Tk()
screen.geometry("200x100")
#Tk variable.
self.msg = StringVar()
#Creating widgets.
entry = Entry( textvariable = self.msg )
button = Button( text = "Send", command = self.sendMSG )
#Packing widgets.
entry.pack()
button.pack()
screen.mainloop()
#Send the message.
def sendMSG(self):
self.server.send( str.encode( self.msg.get() ) )
#Receive message.
def recvMSG(self):
while self.running:
data = self.server.recv(1024)
print( bytes.decode(data) )
#New client.
main = Client()
print("Errors:", main.error)
#Start a thread with the recvMSG method.
thread = threading.Thread(target = main.recvMSG)
thread.start()
#Start the gui.
main.startGUI()
#Close the connection when the program terminates and stop threads.
main.running = False
main.server.close()
The program works fine exactly as i wanted.
But i have some more questions.
r, w, x = select.select(self.sockets, [], self.sockets)
r is a list which contains all the ready sockets.
But i did not undesrand what w and x are.
The first parameter is the sockets list, the second the accepted clients
and the third parameter what is it? Why am i giving again the sockets list?

How to send and receive from the same socket in Python?

I'm am trying to write a client program in Python that can send and receive from the same socket, but it is always giving me the same error which address is already in use. Here is the function I'm trying to write.
def Login():
username=raw_input()
password=raw_input()
message=raw_input()
array=[username,password,message]
TCP_IP = '127.0.0.1'
TCP_PORT = 5563
BUFFER_SIZE = 1024 # Normally 1024, but we want fast response
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((TCP_IP, TCP_PORT))
array_string=pickle.dumps(array)
sock.send(array_string)
sock.close()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((TCP_IP, TCP_PORT))
sock.listen(1)
conn, info = sock.accept()
while 1:
data = serverSocket.recv(1024)
if not data:break
conn.send(data)
conn.close()
There is a bunch of truly newbie errors here.
You can't ever connect a TCP socket to itself. There must be two different sockets.
If you really want to get the data you sent earlier at a listening socket, this listening socket must be created, bound and configured to listen before the client side connects (or, at least, in parallel to this connect attempt, in a few seconds, so the connect attempt will try - but this very likely won't work on localhost).
You can't wait on connect and on accept in the same thread if both are blocking. The simplest approach is to separate the client side and the server side to 2 different programs and run them manually in parallel. Then, after successful debugging, you will be able to do this in different threads of the same process, or using an event-driven engine.
While you may not be able to connect a socket to itself to send and receive data, you might be able to learn from the following example inspired by your code that attempts to do something similar.
import _thread
import pickle
import socket
import time
def main():
"""Run a server in a thread and start a client to talk to it."""
_thread.start_new_thread(run_server, ('', 5563))
run_client('localhost', 5563)
def run_server(host, port):
"""Handle all incoming connections by spawning worker threads."""
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
server.listen(5)
while True:
_thread.start_new_thread(handle_connection, server.accept())
def handle_connection(client, address):
"""Answer an incoming question from the connected client."""
print('Incoming connection from', address)
client.settimeout(0.1)
data = recvall(client)
client.shutdown(socket.SHUT_RD)
question = pickle.loads(data)
answer = '''len(username) = {}
len(password) = {}
len(message) = {}'''.format(*map(len, question))
client.sendall(answer.encode())
client.shutdown(socket.SHUT_WR)
client.close()
print('Finished with', address)
def recvall(connection):
"""Receive all data from a socket and return as a bytes object."""
buffer = bytearray()
while True:
try:
data = connection.recv(1 << 12)
except socket.timeout:
pass
else:
if data:
buffer.extend(data)
else:
return bytes(buffer)
def run_client(host, port):
"""Collect information from question and display returned answer."""
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
time.sleep(0.1) # wait for server to start listening for clients
client.connect((host, port))
time.sleep(0.1) # wait for handler thread to display connection
username = input('Username: ')
password = input('Password: ')
message = input('Message: ')
question = pickle.dumps((username, password, message))
client.sendall(question)
client.shutdown(socket.SHUT_WR)
answer = recvall(client)
client.shutdown(socket.SHUT_RD)
client.close()
print(answer.decode())
time.sleep(0.1) # wait for handler to cleanly terminate execution
if __name__ == '__main__':
main()

Categories

Resources