forked from evanw/socket.io-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsocket_io.py
More file actions
239 lines (201 loc) · 7.49 KB
/
socket_io.py
File metadata and controls
239 lines (201 loc) · 7.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
'''
A socket.io bridge for Python
This gives Python users access to socket.io, a node.js library. This library
provides simple and efficient bidirectional communication between browsers
and servers over a message-oriented socket. Transport is normalized over
various technologies including WebSockets, Flash sockets, and AJAX polling.
For Evan Wallace's latest source, visit https://github.com/evanw/socket.io-python
For the latest source, visit https://github.com/behrat/socket.io-python
'''
import os
import json
import atexit
import socket
import tempfile
import subprocess
# A socket.io template that connects to a Python socket over TCP and forwards
# messages as JSON strings separated by null characters. TCP was chosen over
# UDP to allow for arbitrarily large packets.
_js = '''
var net = require('net');
var io = require('socket.io').listen(%d);
io.set('log level', 1);
// tcp connection to server
var tcp = net.createConnection(%d, 'localhost');
var buffer = '';
tcp.addListener('data', function(data) {
var i = 0;
while (i < data.length) {
if (data[i] == 0) {
sendToClient(JSON.parse(buffer + data.toString('utf8', 0, i)));
data = data.slice(i + 1);
buffer = '';
i = 0;
} else {
i++;
}
}
buffer += data.toString('utf8');
});
function sendToServer(client, command, data) {
data = JSON.stringify({
session: client.id,
command: command,
data: data,
address: client.handshake.address.address,
port: client.handshake.address.port
});
tcp.write(data + '\0');
}
function sendToClient(json) {
if (json.broadcast) {
io.sockets.send(json.data);
} else if (json.session in io.sockets.sockets) {
io.sockets.sockets[json.session].send(json.data);
}
}
io.sockets.on('connection', function(client) {
sendToServer(client, 'connect', null);
client.on('message', function(data) {
sendToServer(client, 'message', data);
});
client.on('disconnect', function() {
sendToServer(client, 'disconnect', null);
});
});
'''
class Client:
'''
Represents a client connection. Each client has these properties:
server - the Socket instance that owns this client
session - the session id used by node (a string of numbers)
address - the remote address of the client
port - the remote port of the client
'''
def __init__(self, server, session, address, port):
self.server = server
self.session = session
self.address = address
self.port = port
def send(self, data):
'''
Send a message to this client.
data - a string with the data to transmit
'''
self.server._send(data, { 'session': self.session })
def __str__(self):
'''
Returns "client-ADDRESS:PORT", where ADDRESS and PORT are the
remote address and port of the client.
'''
return 'client-%s:%s' % (self.address, self.port)
class Server:
'''
This is a socket.io server, and is meant to be subclassed. A subclass
might look like this:
import socket_io as io
class Server(io.Server):
def on_connect(self, client):
print client, 'connected'
self.broadcast(str(client) + ' connected')
print 'there are now', len(self.clients), 'clients'
def on_message(self, client, message):
print client, 'sent', message
client.send(message)
def on_disconnect(self, client):
print client, 'disconnected'
self.broadcast(str(client) + ' disconnected')
print 'there are now', len(self.clients), 'clients'
Server().listen(5000)
The server has self.clients, a dictionary of client session ids to
Client instances.
'''
def __init__(self):
self.clients = {}
def _handle(self, info):
command = info['command']
session = info['session']
if command == 'connect':
self.clients[session] = Client(self, session, info['address'], info['port'])
self.on_connect(self.clients[session])
elif command == 'message':
if session in self.clients:
self.on_message(self.clients[session], info['data'])
elif command == 'disconnect':
if session in self.clients:
client = self.clients[session]
del self.clients[session]
self.on_disconnect(client)
def on_connect(self, client):
'''
Called after a client connects. Override this in a subclass to
be notified of connections.
client - a Client instance representing the connection
'''
pass
def on_message(self, client, data):
'''
Called when client sends a message. Override this in a subclass to
be notified of sent messages.
client - a Client instance representing the connection
data - a string with the transmitted data
'''
pass
def on_disconnect(self, client):
'''
Called after a client disconnects. Override this in a subclass to
be notified of disconnections.
client - a Client instance representing the connection
'''
pass
def broadcast(self, data):
'''
Send a message to all connected clients.
data - a string with the data to transmit
'''
self._send(data, { 'broadcast': True })
def listen(self, ws_port, py_port=None):
'''
Run the server on the port given by ws_port. We actually need two
ports, an external one for the browser (ws_port) and an internal
one to communicate with node.js (py_port):
browser: node.js: this module:
--------- ---------------------- ---------------------
io.Socket <-> ws_port <-> TCP socket <-> py_port <-> io.Socket
ws_port - the port that the browser will connect to
py_port - the port that python will use to talk to node.js
(defaults to ws_port + 1)
'''
# set default port
if py_port is None:
py_port = ws_port + 1
# create a custom node.js script
js = _js % (ws_port, py_port)
handle, path = tempfile.mkstemp(suffix='.js')
os.write(handle, js)
os.close(handle)
# run that script in node.js
process = subprocess.Popen(['node', path])
def cleanup():
process.kill()
os.remove(path)
atexit.register(cleanup)
# make sure we can communicate with node.js
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('localhost', py_port))
sock.listen(0)
sock, addr = sock.accept()
def send(data, info):
info['data'] = data
sock.send(json.dumps(info) + '\0')
self._send = send
# run the server
buffer = ''
while 1:
buffer += sock.recv(4096)
index = buffer.find('\0')
while index >= 0:
data, buffer = buffer[0:index], buffer[index+1:]
self._handle(json.loads(data))
index = buffer.find('\0')