Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python sockets multiple messages on same connection

Tags:

python

sockets

My question is more general rather than specific. I want to implement a simple client server application just to deliver messages from the client to the server and get acknowledgments back from the server.

I was wondering what do I have to take into consideration when doing so with sockets, do I have to implement my own communication interface and manage the messaging on the same connection or create a new connection on for each message?

(please make assumption that the message for right now is less than BUFFER_SIZE)

The code is something like this:

server.py

server_info = (HOST, PORT)
sock = socket.socket(family=AF_INET, type=SOCK_STREAM)
sock.bind(server_info)
sock.listen(NUMBER_OF_SOCKETS)
try:
    while True:
        connection, client_address = sock.accept()
        try:
            while True:
                data = connection.recv(BUFFER_SIZE)
                print('message received: {data}'.format(data=data))
                connection.send("ok")
        finally:
            connection.close()

client.py

server_info = (HOST, PORT)
sock = socket.socket(family=AF_INET, type=SOCK_STREAM)
sock.connect(server_info)
try:
    print("connection established")
    while True:
        print("Please enter a message you want to pass to the server")
        msg = raw_input()

        print('sending "{message}"'.format(message=msg))
        sock.send(msg)

        while True:
            data = sock.recv(constants.BUFFER_SIZE)
            print('received "{data}"'.format(data=data))
            break

finally:
    print('closing socket')
    sock.close()

this code gives me the ability to receive multiple message on the server side and send multiple messages from the client side. Is it the right way to do it? I had to make 2 infinite loops on the client side in order to do so, what about closing the connection? when I send a 0 byte message both server and client get stuck.

thank you very much!

like image 279
Dima Gimburg Avatar asked Feb 14 '17 09:02

Dima Gimburg


People also ask

Can a socket listen to multiple ports?

With the fork() you prepare the socket in the main process and then call handle_request() in a child process to call the accept() function. That way you may have any number of ports and one or more children to listen on each.

How do I send a message to multiple sockets?

The sendmmsg() system call is an extension of sendmsg(2) that allows the caller to transmit multiple messages on a socket using a single system call. (This has performance benefits for some applications.) The sockfd argument is the file descriptor of the socket on which data is to be transmitted.

How many sockets can a client have?

Maximum number of sockets. For most socket interfaces, the maximum number of sockets allowed per each connection between an application and the TCP/IP sockets interface is 65535.


2 Answers

Adding a two types of server-client one is over multi process and the other is asynchronous, they do almost the same thing, the asynchronous one is more robust, read why here: Threads vs. Async.

My examples: Using multi process:

import multiprocessing
import socket
import time

HOST = "0.0.0.0"
PORT = 9000


def handle(connection, address):

    try:
        while True:
            data = connection.recv(1024)
            connection.sendall(data + ' server time {}'.format(time.time()))
    except:
        pass
    finally:
        connection.close()


class Server(object):

    def __init__(self, hostname, port):
        self.hostname = hostname
        self.port = port

    def start(self):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.bind((self.hostname, self.port))
        self.socket.listen(1)

        while True:
            conn, address = self.socket.accept()
            process = multiprocessing.Process(
                target=handle, args=(conn, address))
            process.daemon = True
            process.start()


if __name__ == "__main__":
    server = Server(HOST, PORT)
    try:
        print 'start'
        server.start()
    except:
        print 'something wrong happened, a keyboard break ?'
    finally:
        for process in multiprocessing.active_children():
            process.terminate()
            process.join()
    print 'Goodbye'

And the client for it :

    import sys
import threading
import time
import socket

SOCKET_AMOUNT = 100
HOST = "localhost"
PORT = 9000


def myclient(ip, port, message):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((ip, port))
    sock.sendall(message)
    result = sock.recv(1024)
    print result + ' final clnt time {}'.format(time.time())
    sock.close()

if __name__ == "__main__":
    thread_list = []
    for i in range(SOCKET_AMOUNT):
        msg = "Thread #{}, clnt time {}".format(i, time.time())
        client_thread = threading.Thread(
            target=myclient, args=(HOST, PORT, msg))
        thread_list.append(client_thread)
        client_thread.start()

    waiting = time.time()
    [x.join() for x in thread_list]
    done = time.time()
    print 'DONE {}. Waiting for {} seconds'.format(done, done-waiting)

The next server is a lot more robust !!! data is not getting lost !!! the server:

import asyncore
import socket
import time
import logging
import json


class Server(asyncore.dispatcher):

    def __init__(self, host, port):

        self.logger = logging.getLogger('SERVER')
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind(('', port))
        self.listen(confjson.get('SERVER_QUEUE_SIZE', None))
        self.logger.debug('binding to {}'.format(self.socket.getsockname()))

    def handle_accept(self):
        socket, address = self.accept()
        self.logger.debug('new connection accepted')
        EchoHandler(socket)


class EchoHandler(asyncore.dispatcher_with_send):

    def handle_read(self):

        msg = self.recv(confjson.get('RATE', None))
        self.out_buffer = msg
        self.out_buffer += ' server recieve: {}'.format(time.time())
        if not self.out_buffer:
            self.close()


if __name__ == "__main__":

    logging.basicConfig(level=logging.DEBUG,
                        format='%(name)s: %(message)s',
                        )
    with open('config.json', 'r') as jfile:
        confjson = json.load(jfile)
    try:
        logging.debug('Server start')
        server = Server(confjson.get('HOST', None),
                        confjson.get('PORT', None))
        asyncore.loop()
    except:
        logging.error('Something happened,\n'
                      'if it was not a keyboard break...\n'
                      'check if address taken, '
                      'or another instance is running. Exit')
    finally:
        logging.debug('Goodbye')

And the async client:

import asyncore
import socket
import time
import logging
import json


class Client(asyncore.dispatcher_with_send):

    def __init__(self, host, port, message, pk):
        self.logger = logging.getLogger('CLIENT')
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.host = host
        self.port = port
        self.connect((host, port))
        self.out_buffer = message
        self.clientID = pk
        self.logger.debug('Connected #{}'.format(self.clientID))

    def handle_close(self):
        self.close()

    def handle_read(self):
        rec_msg = self.recv(confjson.get('RATE', None))
        self.logger.debug('#{}, {} back at client {}'.format(self.clientID,
                                                             rec_msg,
                                                             time.time()
                                                             )
                          )
        self.close()


if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG,
                        format='%(name)s: %(message)s',
                        )

    with open('config.json', 'r') as jfile:
        confjson = json.load(jfile)
    clients = []
    for idx in range(confjson.get('SOCKET_AMOUNT', None)):
        msg = "Start: {}".format(time.time())
        clients.append(Client(confjson.get('HOST', None),
                              confjson.get('PORT', None),
                              msg,
                              idx)
                       )
    start = time.time()
    logging.debug(
        'Starting async loop for all connections, unix time {}'.format(start))
    asyncore.loop()
    logging.debug('{}'.format(time.time() - start))

and a small config file:

{
    "HOST": "127.0.0.1",
    "PORT": 5007,
    "RATE": 8096,
    "SERVER_QUEUE_SIZE": 16,
    "SOCKET_AMOUNT": 100
}
like image 81
Ohad the Lad Avatar answered Oct 19 '22 13:10

Ohad the Lad


In bi-directional communication, by default, a client can know when it is done sending, but it cannot know if it is done receiving. And, also the server cannot know whether the client is done sending.

Code :

def recv_end(the_socket):
    End='SERVER WRONG MARKER'
    total_data=[];data='';got_end=False
    while True:
            data=the_socket.recv(8192)
            if not data: break
            if End in data:
                total_data.append(data[:data.find(End)])
                got_end=True
                break
            total_data.append(data)
            if len(total_data)>1:
                #check if end_of_data was split
                last_pair=total_data[-2]+total_data[-1]
                if End in last_pair:
                    total_data[-2]=last_pair[:last_pair.find(End)]
                    total_data.pop()
                    got_end=True
                    break
    return (got_end,''.join(total_data))

def basic_server(sock):
    got=[]
    got_end,data = recv_end(sock)
    if not got_end:  
        sock.send('ERROR:no end!') #<--- not possible w/close()
    else: sock.sendall(data*2)
    sock.shutdown(1)
    sock.close()

import socket
Port=4444
def start_server():
    sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sock.bind(('',Port))
    sock.listen(5)
    print 'started on',Port
    while True:
        newsock,address=sock.accept()
        basic_server(newsock)

def send_data(data):
    sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sock.connect(('localhost',Port))
    print 'connected'
    sock.sendall(data+'CLIENT WRONG MARKER')
    print 'sent',data
    sock.shutdown(1)
    print 'shutdown'
    result=[]
    while True:
       got=sock.recv(2)
       if not got: break
       result.append(got)
    sock.close()
    return ''.join(result)

if __name__=='__main__':
    start_server()

You could do something like put a byte count in front of the data, or have an end marker so the server can know if it got all of the bytes.

However, that introduces a problem. What if the byte count is wrong or the end marker never arrives? With a socket.close() the server cannot tell the client, "Strange. You are done sending data to me, but I didn't get all the data", since the client connection is not left open after the client is done sending.

With a socket.shutdown(1) the client can still be told by the server that something was wrong and take appropriate measures.

The shutdown command has three options: 0 = done receiving, 1 = done sending, 2 = both

In the code above focuses on 1, to get rid of the implict send in a close operation. Notice how in send_data the close operation is (relatively) far away from the shutdown. This allows the server to tell the client any parting comment.

Just run the code to start the server. The server is set to recv only 2 bytes at a time for demonstration purposes (it should be something like 8192). To send data to it import it (call it shut_srv or whatever) and call send_data for the client side.

data=('a1234','b1234','c1234','d1234','e1234') for d in data: print shut_srv.send_data(d)

You will get a response like: connected sent a1234 shutdown ERROR:no end! connected sent b1234 shutdown ERROR:no end! connected sent c1234 shutdown ERROR:no end! connected sent d1234 shutdown ERROR:no end! connected sent e1234 shutdown ERROR:no end!

If you make the markers the same. The response should be: connected sent a123456789 shutdown a1234a1234 connected sent b1234 shutdown b1234b1234 connected sent c1234 shutdown c1234c1234 connected sent d1234 shutdown d1234d1234 connected sent e1234 shutdown e1234e1234

like image 24
Shivkumar kondi Avatar answered Oct 19 '22 13:10

Shivkumar kondi