Skip to content

Send to two clients in one server?How can I do? #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
zaazbb opened this issue Apr 4, 2014 · 9 comments
Closed

Send to two clients in one server?How can I do? #17

zaazbb opened this issue Apr 4, 2014 · 9 comments

Comments

@zaazbb
Copy link

zaazbb commented Apr 4, 2014

I want send a message to two clients, how can i do? I can not find the client connect handle or client addr!

import asyncio
import websockets

@asyncio.coroutine
def hello(websocket, uri):
    name = yield from websocket.recv()
    print(uri)
    print("< {}".format(name))
    greeting = "Hello {}!".format(name)
    print("> {}".format(greeting))
    yield from websocket.send(greeting)

start_server = websockets.serve(hello, 'localhost', 8765)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
@aaugustin
Copy link
Member

Well it's up to you to implement this, most likely with a variant of the Subscriber pattern.

See https://github.com/aaugustin/django-c10k-demo/blob/master/gameoflife/views.py for an example.

@zaazbb
Copy link
Author

zaazbb commented Apr 5, 2014

sorry, the django-c10k-demo is to complex to me, it mixed django code, i can't understand it.
i want a simple broadcast example( a server broadcast to several clients).
thank you.

@aaugustin
Copy link
Member

Have a look at https://static.myks.org/data/20130905-DjangoCon-Real-time_Web.pdf starting at slide 32. It's a simpler example.

@zaazbb
Copy link
Author

zaazbb commented Apr 5, 2014

I can't open this link, maybe that site was banned by the government. :(

@aaugustin
Copy link
Member

Well there's plenty of resources on the Internet to learn concurrent programming patterns, I'm not the right person to ask. Good luck.

@zaazbb
Copy link
Author

zaazbb commented Apr 11, 2014

this demo make a websocket server, but I don't know how to rewrite it use websockets lib.
because i can't find where the websocket server accept a connection and save it to clients set.
code as below:

#!/usr/bin/env python

import socket, hashlib, base64, threading

class PyWSock:
    MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
    HSHAKE_RESP = "HTTP/1.1 101 Switching Protocols\r\n" + \
                "Upgrade: websocket\r\n" + \
                "Connection: Upgrade\r\n" + \
                "Sec-WebSocket-Accept: %s\r\n" + \
                "\r\n"
    LOCK = threading.Lock()

    clients = []

    def recv_data (self, client):
        # as a simple server, we expect to receive:
        #    - all data at one go and one frame
        #    - one frame at a time
        #    - text protocol
        #    - no ping pong messages
        data = client.recv(512)
        if(len(data) < 6):
            raise Exception("Error reading data")
        # FIN bit must be set to indicate end of frame
        assert(0x1 == (0xFF & data[0]) >> 7)
        # data must be a text frame
        # 0x8 (close connection) is handled with assertion failure
        assert(0x1 == (0xF & data[0]))

        # assert that data is masked
        assert(0x1 == (0xFF & data[1]) >> 7)
        datalen = (0x7F & data[1])

        #print("received data len %d" %(datalen,))

        str_data = ''
        if(datalen > 0):
            mask_key = data[2:6]
            masked_data = data[6:(6+datalen)]
            unmasked_data = [masked_data[i] ^ mask_key[i%4] for i in range(len(masked_data))]
            str_data = bytearray(unmasked_data).decode('utf8')
        return str_data

    def broadcast_resp(self, data):
        # 1st byte: fin bit set. text frame bits set.
        # 2nd byte: no mask. length set in 1 byte. 
        resp = bytearray([0b10000001, len(data)])
        # append the data bytes
        for d in data.encode('utf8'):
            resp.append(d)

        self.LOCK.acquire()
        for client in self.clients:
            try:
                client.send(resp)
            except:
                print("error sending to a client")
        self.LOCK.release()

    def parse_headers (self, data):
        headers = {}
        lines = data.decode().splitlines()
        for l in lines:
            parts = l.split(": ", 1)
            if len(parts) == 2:
                headers[parts[0]] = parts[1]
        headers['code'] = lines[len(lines) - 1]
        return headers

    def handshake (self, client):
        print('Handshaking...')
        data = client.recv(2048)
        headers = self.parse_headers(data)
        print('Got headers:')
        for k, v in headers.items():
            print(k, ':', v)

        key = headers['Sec-WebSocket-Key']
        resp_data = self.HSHAKE_RESP % base64.b64encode(hashlib.sha1(\
                (key+self.MAGIC).encode()).digest()).decode()
        print(('Response: [%s]' % (resp_data,)))
        return client.send(resp_data.encode())

    def handle_client (self, client, addr):
        self.handshake(client)
        try:
            while 1:            
                data = self.recv_data(client)
                print(("received [%s]" % (data,)))
                self.broadcast_resp(data)
        except Exception as e:
            print(("Exception %s" % (str(e))))
        print(('Client closed: ' + str(addr)))
        self.LOCK.acquire()
        self.clients.remove(client)
        self.LOCK.release()
        client.close()

    def start_server (self, port):
        s = socket.socket()
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.bind(('', port))
        s.listen(5)
        while(1):
            print ('Waiting for connection...')
            conn, addr = s.accept()
            print(('Connection from: ' + str(addr)))
            threading.Thread(target = self.handle_client, args = (conn, addr)).start()
            self.LOCK.acquire()
            self.clients.append(conn)
            self.LOCK.release()

ws = PyWSock()
ws.start_server(4545)

@aaugustin
Copy link
Member

Connections are handled by asyncio. Learn first to do it with asyncio; websocket behaves similarly.

@zaazbb
Copy link
Author

zaazbb commented Apr 12, 2014

I modify c10k demo, add a broadcast url, it running ok, code as below:

clients = set()
@websocket
def broadcast(ws):
    while True:
        msg = yield from ws.recv()
        clients.add(ws)
        for client in clients:
            if client.open:
                yield from client.send(msg)

I want make a broadcast server without django. so i modify server.py in example dir. when i open one webpage, it running ok. but when i open two webpages, the broadcast message appear twice at one page(the current focus page). code as below:

import asyncio
import websockets

clients = set()

@asyncio.coroutine
def hello(websocket, uri):
    while True:
        msg = yield from websocket.recv()
        clients.add(websocket)
        for client in clients:
            if client.open:
                yield from websocket.send(msg)

start_server = websockets.serve(hello, 'localhost', 4545)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

if i rewrite it use ws4py lib, it running all ok. code as below:

import asyncio
from ws4py.async_websocket import WebSocket
from ws4py.server.tulipserver import WebSocketProtocol

loop = asyncio.get_event_loop()
clients = set()

class EchoWebSocket(WebSocket):
    def received_message(self, message):
        clients.add(self)
        for client in clients:
            client.send(message.data, message.is_binary)

def start_server():
    proto_factory = lambda: WebSocketProtocol(EchoWebSocket)
    return loop.create_server(proto_factory, 'localhost', 4545)

s = loop.run_until_complete(start_server())
print('serving on', s.sockets[0].getsockname())
loop.run_forever()

client html file as below:

<!DOCTYPE html>
<html>
  <head>
        <title>Websocket Demo</title>
        <style>
            #messages {
                border: dotted 1px #444444;
                font: 12px arial,sans-serif;
            }

            #messages > p {
                padding: 0px;
                margin: 0px;
            }
        </style>

        <script>
            var messages;
            var form;
            var inputBox;

            function log_msg(msg) {
                var p = document.createElement("p");
                p.innerHTML = msg;
                messages.appendChild(p);
            }

            function doInit() {
                inputBox = document.getElementById("message");
                messages = document.getElementById("messages");
                form = document.getElementById("form");
                var s;
                try {
                    var host = "ws://localhost:4545/";
                    if(window.location.hostname) {
                        host = "ws://" + window.location.hostname + ":4545/";
                    }

                    s = new WebSocket(host);

                    s.onopen = function (e) { log_msg("connected..."); };
                    s.onclose = function (e) { log_msg("connection closed."); };
                    s.onerror = function (e) { log_msg("connection error."); };
                    s.onmessage = function (e) { log_msg("message: " + e.data); };
                } catch (ex) {
                    log_msg("connection exception:" + ex);
                }

                form.addEventListener("submit", function (e) {
                    e.preventDefault();
                    s.send(inputBox.value);
                    inputBox.value = "";
                }, false);
            }
        </script>       
    </head>
<body onload="doInit()">
    <form id="form">
        <input type="text" id="message">
        <button type="submit">Send</button>
    </form>
    <br/>
    <div id="messages"></div> 
</body>
</html>

@aaugustin
Copy link
Member

Cool, use ws4py then!

@aaugustin aaugustin mentioned this issue Jun 23, 2019
18 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants