Websocket server(server/Websocket.php and server/Client.php) is forked from https://github.com/heminei/php-websocket and modified according to the requirements.
- Authentication using jwt cookie instead of php sessions.
- Supports multiple rooms.
- Display online users for each room on the client.
- Scroll positions between rooms will be preserved when switching between rooms(client side).
- Application works with sharding collection in mongodb.
- Deploy sharded clusters using docker containers.
- Mongodb
- Composer (to download required php libraries)
- Mongodb extension for php
- Docker (optional for sharding)
- Clone the repo.
git clone https://github.com/AndeYashwanth/PHP-Websocket-Chatrooms cd PHP-Websocket-Chatrooms/
- Install dependencies from composer.json
composer install
Folder Sharding/ is a submodule for this repo. Go through it's readme to create and configure docker instances of shards.(Its very simple)
In order to shard a collection, first you need to shard a database.
-
Connect to query router(mongos)
mongo --port 60000
-
Select chat database
use chat
-
enable sharding for chat db
sh.enableSharding("chat")
db.messages.createIndex({"room_id":1, "from.user_id":1})
db.messages.createIndex({"room_id":1, "_id":1})
sh.shardCollection("chat.messages", {"room_id":1, "from.user_id":1})
- start server.php in server/server.php
php server/server.php
- open login.php in client/login.php and login/register. User will be created if he/she doesn't exist.
- You will be redirected to client/client.php after successful login/sign-up.
- Pressing shift+enter to increase text-box size on the client will lag.
- Upon testing a single client can send upto 80 messages/sec. Not sure if it is true for single client at a time or multiple clients.
- In mongodb you a lookup cannot be done to another collection which is sharded from a collection which is unsharded/sharded. So seperate queries need to be made instead of using $lookup.
Affected Collections:-- messages :
- Problem: 'from' field stores json with 'user_name' and 'user_id' fields. So if a user changes his 'user_name' it should be updated in every message he sent till date.
- Solution: Shard key for messages collection is used as (room_id, from.user_id).
- rooms :
- Problem: 'online_users' stores json similar to messages.from
- Solution: When user updates username, update the online_users array for every room he has access to.
- messages :
- Due to limitation of unsigned 64bit integer, message_ids are only 63 bits. Somewhat similar to twitter snowflake.
- 42 bit timestamp in milli seconds
- 12 bit server id
- 9 bit increment (i.e., for each ms server can generate upto 29 message ids)
- $server_id in server/MessageIdGenerator.php should be manually set between 0 and 212-1 when used in distributed environment.
- Some features like displaying online users on client might break when not used with single app server.
- Add password authentication to mongo instances in docker containers.
- Edit message.(to be implemented on client side)
- Upload files(upload maybe handled and stored by another service, and just the url is stored in DB).
- Delivery report.
- No.of users seen report.
Making application work with sharding in mongodb.- Support application to deploy on multiple instances of web servers.
- Rate limit number of messages/sec for each room.
Deploy sharded clusters using docker containers.
Database: chat
-
users collection
{ "_id": String, "user_name": String, "rooms_access":["$rooms._id"] //to which chat room _ids user has access to. }
-
rooms collection
{ "_id": int, //room_id "room_name": String, "banned_users":["$users._id"], //_id of users banned "rate_limit": int, // messages/sec similar to discord. "online_users":[{'user_id': "$users._id", 'user_name': "$users.user_name"}] //Users who are currently online. }
-
messages collection
{ '_id': int, //63 bit integer 'from':{'user_id': '$users._id', 'user_name': '$users.user_name'}, 'message': String, 'last_edited': int, 'attachments':[String], //list of urls of files 'stats':[ //user is added only after message is delivered to some user. { 'user_id': "$users_id", 'user_name': "$users.user_name", 'delivered_on': int, //time of delivery 'read_on': int //time of read. }, ] }