Home
Github
Personal blog
Weibo
To subscribe to

  • Posts – 119, Articles – 20, Comments – 1155
  • Cnblogs
  • Dashboard
  • Login
  • The home page
  • Github
  • Personal blog
  • About me
  • To subscribe to
  • RSS

Barret Lee console.log( ” Hi, I’m Barret, a Web Developer, try to be Excellent~ ” );

More on websocket-Node

2013-12-20 13:42 by Barret Lee, 22526 Read, 20 commentscollection.The editor

In the last article, various methods of Web communication were improved, including polling, long connections, and various methods mentioned in HTML5. This article will describe the implementation of WebSocket protocol in Web communication in detail.

WebSocket protocol

1. An overview of the

The WebSocket protocol allows untrusted client code to control remote hosts in a controlled network environment. The protocol consists of a handshake and a basic message frame, layered through TCP. To put it simply, a handshake response followed by a secure information pipeline is superior to the XMLHttprequest-based IFrame data stream and long polling described above. There are two aspects of the protocol, Handshake and data transfer.

2. Handshake connection

This part is easy, like saying hello to someone you know on the road.

Client: Hey, buddy, do you have a light? (Cigarette passes) Server: Ha, yes, come on. (Light) Client: Matches, ok! (Smoke point, verified)Copy the code

During the handshake connection, the client extends its hand first:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13Copy the code

The client sends a Base64 encrypted Key, the sec-websocket-key you see above. When the Server sees the greeting from the Client, he quietly tells the Client that he knows about it and also says hello.

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chatCopy the code

The Server returns the sec-websocket-accept reply, which is generated in a certain way. The generation algorithm is:

mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; Accept = base64(sha1(key + mask));Copy the code

After the key and mask are connected in series, they are processed by SHA-1, and the processed data is encrypted by Base64. Break down the action:

1. t = "GhlIHNhbXBsZSBub25jZQ==" + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
   -> "GhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
2. s = sha1(t) 
   -> 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 
      0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea
3. base64(s) 
   -> "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="Copy the code

The HTTP status code returned by the Server is 101. If it is not 101, the handshake failed at the beginning

Here’s a demo, shake hands with the server:

var crypto = require('crypto'); require('net').createServer(function(o){ var key; o.on('data',function(e){ if(! Key){// handshake // reply, omit console.log(e.tostring ()); }else{ }; }); }).listen(8000);Copy the code

Client code:

Var ws = new WebSocket (ws: / / 127.0.0.1: "8000"); ws.onerror=function(e){ console.log(e); };Copy the code

The above is, of course, a string of incomplete code designed to demonstrate that the client greets the server during a handshake. On the console we can see:

An HTTP request is sent, and this can also be seen in the Network browser:

But WebSocket protocol is not HTTP protocol, at the beginning of the verification of the use of HTTP headers, the connection after the success of the communication is not HTTP, do not believe you use Fiddler2 to capture the packet, certainly can not get, behind the communication part is based on TCP connection.

For the server to communicate successfully, there must be a reply. Look below:

Var crypto = require('crypto'); var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; require('net').createServer(function(o){ var key; o.on('data',function(e){ if(! Key) {/ / handshake key = e. oString () the match (/ Sec - WebSocket - key: (. +) /) [1]. key = crypto.createHash('sha1').update(key + WS).digest('base64'); O.w rite (' HTTP / 1.1 101 Switching separate Protocols \ r \ n '); o.write('Upgrade: websocket\r\n'); o.write('Connection: Upgrade\r\n'); o.write('Sec-WebSocket-Accept: ' + key + '\r\n'); o.write('\r\n'); }else{ console.log(e); }; }); }).listen(8000);Copy the code

The crypto module, you can see the official documentation, the above code should be very easy to understand, after the server responds, the Client gets sec-websocket-Accept, and then does a local verification, if the verification passes, will trigger the onopen function.

Var ws=new WebSocket("ws://127.0.0.1:8000 "); Ws. onopen=function(e){console.log(" handshake succeeded "); };Copy the code

You can see

3. Data frame format

The official documentation provides a structure

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload  Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+Copy the code

The first time you look at this picture, you’re probably going to vomit blood. If you’re modifying computer networks in college, you’re going to be familiar with this. Data transfer protocols need to define the length of bytes and what that means.

FIN 1bit Indicates the last frame of the message. RSV 1-3 1bit Each is 0 by default. Opcode 4bit indicates the frame type. Payload 7bit length of data Masking-key 1 or 4 bit mask Payload data (x + y) bytes data Extension data x bytes extended data Application Data Y Bytes Indicates program dataCopy the code

Every frame is transmitted in accordance with the protocol rules, know this protocol, then the parsing is not too difficult, I will directly take the cobalt carbonate students code.

4. Data frame parsing and coding

Data frame parsing code:


Function decodeDataFrame(e){var I =0,j,s,frame={FIN:e[I]>>7,Opcode:e[I ++]&15,Mask:e[I]>>7, PayloadLength:e[i++]&0x7F }; If (frame.PayloadLength==126) frame.length=(e[i++]<<8)+e[i++]; If (frame.PayloadLength==127) I +=4, The first four bytes are usually long plastic blank frame. The length = ([i++] < < 24) + e (e/i++ < < 16) + (e/i++ < < 8) + e [i++]; / / determine whether use Mask if (frame. Mask) {/ / frame to Mask item. MaskingKey = [[i++], [i++], e e e [i++], e [i++]]. For (j=0,s=[]; j<frame.PayloadLength; j++) s.push(e[i+j]^frame.MaskingKey[j%4]); }else s=e.slice(i,frame.PayloadLength); S =new Buffer(s); // Convert the buffer to a string if necessary using if(frame.opcode ==1)s= s.tostring (); // Set the data part on frame.PayloadData=s; // Return frame; }Copy the code

Data frame encoding:


//NodeJS function encodeDataFrame(e){ var s=[],o=new Buffer(e.PayloadData),l=o.length; // enter the first byte s.pen ((e.fin <<7)+ e.opcode); // Never use the mask if(l<126) s.ush (l); else if(l<0x10000)s.push(126,(l&0xFF00)>>2,l&0xFF); Else s.ush (127, 0,0,0,0, //8 bytes data, the first 4 bytes are not left blank (l&0xFF000000)>>6,(l&0xFF0000)>>4,(l&0xFF00)>>2,l&0xFF); // Return buffer. concat([new Buffer(s),o]); }Copy the code

Some children may not understand which data to parse. The parse task is mainly handled by the server. The data sent by the client is in binary stream form, for example:

Var ws = new WebSocket("ws://127.0.0.1:8000/");

ws.onopen = function(){

Ws. send(" Handshake successful ");

};Copy the code

The Server receives the following message:

A binary stream in Buffer format. Parse the binary stream as we output:

Var crypto = require('crypto'); var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; require('net').createServer(function(o){ var key; o.on('data',function(e){ if(! Key) {/ / handshake key = e. oString () the match (/ Sec - WebSocket - key: (. +) /) [1]. key = crypto.createHash('sha1').update(key + WS).digest('base64'); O.w rite (' HTTP / 1.1 101 Switching separate Protocols \ r \ n '); o.write('Upgrade: websocket\r\n'); o.write('Connection: Upgrade\r\n'); o.write('Sec-WebSocket-Accept: ' + key + '\r\n'); o.write('\r\n'); }else{console.log(decodeDataFrame(e)); }; }); }).listen(8000);Copy the code

The output is an object with very clear frame information:

5. Connection control

Above I bought a mystery, mentioned the Opcode, did not elaborate, the official document also gave a table:

|Opcode | Meaning | Reference | -+--------+-------------------------------------+-----------| | 0 | Continuation Frame |  RFC 6455 | -+--------+-------------------------------------+-----------| | 1 | Text Frame | RFC 6455 | -+--------+-------------------------------------+-----------| | 2 | Binary Frame | RFC 6455 | -+--------+-------------------------------------+-----------| | 8 | Connection Close Frame | RFC 6455 | -+--------+-------------------------------------+-----------| | 9 | Ping Frame | RFC 6455 | -+--------+-------------------------------------+-----------| | 10 | Pong Frame | RFC 6455 | -+--------+-------------------------------------+-----------|Copy the code

DecodeDataFrame parses data in the following format:

{FIN: 1, Opcode: 1, Mask: 1, PayloadLength: 4, MaskingKey: [159, 18, 207, 93], PayLoadData: 'Handshake successful'}Copy the code

So as you can see above, the purpose of this frame is to send text, which is a text frame. The connection is based on TCP, so if you close the TCP connection, the channel will be closed. However, WebSocket is designed to be humanized, and we will notify you before closing it. On the server side, you can determine the frame Opcode:

var frame=decodeDataFrame(e); console.log(frame); if(frame.Opcode==8){ o.end(); // Disconnect}Copy the code

The format of the data (frames) that the client and server interact with is the same, as long as the client sends Ws.close (), the server performs the above operation. Conversely, if the server sends the same close frame to the client:

o.write(encodeDataFrame({
    FIN:1,
    Opcode:8,
    PayloadData:buf
}));Copy the code

The client will respond to the onclose function, so that the interaction is orderly and not prone to error.

Two, matters needing attention

1. WebSocket URIs

Many people may only know ws://text.com:8888, but in fact websocket addresses can be added with path and query.

ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]Copy the code

If the WSS protocol is used, urIs will be connected in a secure manner. The WSS is case insensitive here.

2. The “redundant” part of the agreement

The handshake request contains the sec-websocket-key field, which is a WebSocket connection, and the encryption method of this field is fixed on the server, so if someone wants to hack you, it won’t be too difficult.

Then there is the MaskingKey Mask, since the encryption is mandatory (Mask 1 means encryption, MaskingKey and PayLoadData are xor processing), is there any need for developers to handle this thing? Why don’t we just encapsulate it internally?

3. Relationship with TCP and HTTP

The WebSocket protocol is a TCP-based protocol, which is associated with HTTP (sending an HTTP request) when shaking a connection. This request has been switched to (Upgrade) WebSocket by the Server. Websocket uses port 80 as the default webSocket connection port, and WebSocket runs on port 443.

Iii. Reference materials

  • Tools.ietf.org/html/rfc645… web standard – The WebSocket Protocol
  • www.w3.org/TR/websocke… W3.ORG – WebSockets

Iv. Special thanks

Thanks again for talking to me for a few hours:), part of this node code is from his blog.

 

Next time, WE will use PHP as the background to explain websocket.

Elaborate the WebSocket – PHP

 

 


To admire the

Copyright notice: Signature – Non-commercial Use – No Deductive 3.0 International (CC BY-NC-ND 3.0)

  • Categories: HTML + CSS [3] [5], JavaScript, and Others
  • Tags: javascript, communication, webSocket


Let me say something

  1. # 1 / f, simonleung  The 2013-12-20 o

    Websocket starts the handshake with the HTTP onUpgrade event

    Not onData with NET.

    Nodejs.org/api/http.ht… Support (0) Against (0)

  2. # 2 floor[I] Barret Lee  In 2013-12-20 he

    @ simonleung

    Net data is more low-level than HTTP onUpgrade.

    From the description of the WebSocket protocol to the presentation of the test results, there was no problem.Support (0)Against (0) http://pic.cnblogs.com/face/387325/20150805014702.png

  3. The # 3 floor Nimah Fan Bingbing  The 2013-12-20 and

    Well written, I admireSupport (0)Against (0) http://pic.cnblogs.com/face/395638/20140818132358.png

  4. # 4 floor Boolean  The 2013-12-20 22:04

    When using Node-WebKit, I do not know why the number of connections (more than 100) slow accessSupport (0)Against (0) http://pic.cnblogs.com/face/u17705.jpg

  5. The # 5 floor Two teacher younger brother Tony  The 2013-12-21 11:56

    I’m looking for this kind of articleSupport (0)Against (0) http://pic.cnblogs.com/face/348990/20160120104927.png

  6. # 6 building Ace8793  The 2013-12-22 22:00

    mark Support (0)Against (0) http://pic.cnblogs.com/face/377458/20161107194642.png

  7. # 7 floor flyher  The 2013-12-23 08:26

    @BarretLee

    Good article, I saw it on Monday, I really met you too late.Support (0)Against (0) http://pic.cnblogs.com/face/u218282.jpg

  8. # 8 floor, dotNetDR_  The inscription 2013-12-24

    Good ~Support (0)Against (0) http://pic.cnblogs.com/face/u59691.jpg

  9. # 9 / f, Calculus g  The 2013-12-26 21:07

    Good, study!Support (0)Against (0)

  10. # 10 floor It the Smurfs  The 2014-03-26 20:08

    learningSupport (0)Against (0)

  11. # 11 floor The topic leaf  The 2014-04-02 10:54

    Honey, the picture kneelsSupport (0)Against (0)

  12. # 12 floor[I] Barret Lee  The 2014-04-02 thus

    @The topic leaf

    I have a good, you refresh is not good?Support (0)Against (0) http://pic.cnblogs.com/face/387325/20150805014702.png

  13. # 13 floor mcarzx  The 2014-04-21 17:46

    T =” GhlIHNhbXBsZSBub25jZQ==

    . 2, the console log (crypto createHash (‘ sha1). Update (‘ dGhlIHNhbXBsZSBub25jZQ = = 258 eafa5 E914-47 – da – 95 ca – C5AB0DC85B11 ‘). The digest (‘ s ha1’)); // outputs: <SlowBuffer b3 7a 4f 2c c0 62 4f 16 90 f6 46 06 cf 38 59 45 b2 be c4 EA >



    If (frame.PayloadLength==126) frame.length=(e[2]<<8) + e[3], I =4; // frame. Length should be frame.Payloadlength. And also in the next line. Check it out. Cobalt subcarbonate. It’s changed over there.



    S.ush (126,(l&0xFF00)>>2, L &0xFF); Where >>2, it seems to be >>8. The next line is similar.Support (0)Against (0)

  14. # 14 floor prof  The 2014-11-12 21:03

    Could you please tell me, moustache brother, how does postMessage communicate across domainsSupport (0)Against (0)

  15. # 15 floor[I] Barret Lee  The 2014-11-12 21:51

    @prof

    Page A communicates with iframe B, A foreign domain of the page.

    PostMessage and onMessage are both fired in the same window object.



    Handle to the window in A for B, use the biggest ontentWindow. PostMessage sends A message to B, B in onmessage listening in.



    B sends messages to A based on the principle that B obtains A’s window handle and sends messages to A using A.parent.postMessage, where A has onMessage listening.



    The same window sends a message to itself, but if we get the handle from another window, such as ifame. ContentWindow, or if we get the window from another window, window.parent.



    It’s not your ability to understand, it’s your inability to understand these concepts. Still do not understand please read the MSDN/MDN document yourself.Support (0)Against (0) http://pic.cnblogs.com/face/387325/20150805014702.png

  16. # 16 floor riskers  The 2015-03-04 14:24

    After the reference key and mask are concatenated, they are processed by SHA-1, and the processed data is encrypted by Base64.



    Base64 is an encoding method, not encryptionSupport (0)Against (0)

  17. # 17 floor CoderZ  The 2015-06-29 at 12:05

    Please ask little beard brother, just implemented your method is good. IO request header does not contain any websocket information, but it is based on webSocket.Support (0)Against (0) http://pic.cnblogs.com/face/579305/20131227132708.png

  18. # 18 building Got kicked male. Orz  The 2015-07-20 15:55

    mark Support (0)Against (0) http://pic.cnblogs.com/face/520225/20131008114345.png

  19. # the 19th floor gongmaolan123  The 2017-01-13 thou

    GoEasy Web implements real-time web push in three easy steps

    1. The introduction of goeasy. Js



    2. Client subscription,

    Var goeasy = new goeasy ({appKey: ‘your appkey’});

    Goeasy. Subscribe (channel: “your channel”, the onMessage: function (the message)

    {alert (‘ a received message ‘+ message. The content)}

    )



    3. Three push modes

    Goeasy. Publish ({channel: ‘your channel’, message: ‘your publish MSG’});



    Java SDK:

    GoEasy GoEasy = new GoEasy(” appkey “);

    Goeasy. The publish (” your channel “, “your MSG”);



    RestAPI: Goeasy. IO/goeasy/publ…

    Easy web push and receive in three steps. Website:goeasy.io, complete documentationSupport (0)Against (0)

  20. # 20 buildings38077282017/10/11 18:57:09 Cinux,  The 2017-10-11 18:57

    @ simonleung

    HTTP module is based on NET, NET does not have this eventSupport (0)Against (0)


Refresh the comments
Refresh the page
Return to the top
The login
registered
access
[recommendation] 500,000 VC++ source code: large configuration industrial control, power simulation CAD and GIS source code library



[Activity] Aliyun Double 11 activity began to warm up the cloud server time limit 2 fold



[Investigation] You are a program ape!



Vue. Js 2.x quick start, lots of efficient practical examples




Latest IT News



The rise of technical education



The US Supreme Court has rejected an appeal by Samsung to pay Apple $120m in damages



Adobe Hack looks more chemical than a chemical element list



Unbalance of express delivery industry: labor shortage caused by punishment and agency management



SONY mobile phone good reputation why not good sales



More news…

Latest Knowledge Base articles



3+10 Habits to improve your Programmer’s life



NASA’s 10 Code Writing Principles



How come you’ve had all that training, but you’re still mediocre?



A letter to a beginning front-end engineer



This section describes VPC virtual private cloud design principles



More knowledge base articles…

Blog readdress: http://barretlee.com


@Barret
A mustache and elder brother



barretlee







Barret Lee



Five years and seven months



Recommend the blog



2731



20
+ add attention

The latest essays

  • Nginx configuration overview
  • Talk about my growth in Ali
  • Revealed 0.1 + 0.2! = 0.3
  • How to be a good intern
  • Talk about my technical growth in the last three years
  • Work for five years and repeat the first year for the next four?
  • ECMAScript 6 literacy
  • The current end also has Server capabilities
  • Describe the operation process of OAuth 2.0
  • Front-end technology inventory in recent years and technology development direction in 2016
  • NodeJS code debugging and performance tuning
  • New app comes on Snippet
  • What about these two days of Apple software poisoning?
  • Site SEO and the secrets between it and webmaster tools
  • Blogs have migrated to http://barretlee.com/entry/, share here from time to time synchronization

The latest comments

  • Re: More on websocket-PHP

    Learn, thank you — Stormpass
  • Re: Talk about my growth in Ali

    Moustache, we’ll meet again
  • Re: More on Websocket-Node

    @simonleung HTTP module is written based on NET, NET does not have this event — Cinux,
  • Re: collects and monitors front-end code exception logs

    Even the browser source are moved out, have to praise — cloud –
  • Re: Implementation of javascript chained invocation

    You’re good! They are very inadequate, still need to work hard. Doomed to be abused by code

Archives of essays

  • November 2016 (1)
  • October 2016 (3)
  • July 2016 (3)
  • February 2016 (1)
  • January 2016 (2)
  • October 2015 (1)
  • September 2015 (4)
  • August 2015 (6)
  • July 2015 (1)
  • May 2015 (2)
  • April 2015 (4)
  • December 2014 (1)
  • November 2014 (1)
  • October 2014 (1)
  • September 2014 (1)
  • August 2014 (4)
  • July 2014 (1)
  • May 2014 (6)
  • April 2014 (4)
  • March 2014 (8)
  • February 2014 (7)
  • January 2014 (4)
  • December 2013 (6)
  • November 2013 (5)
  • October 2013 (2)
  • September 2013 (3)
  • August 2013 (3)
  • July 2013 (3)
  • June 2013 (6)
  • May 2013 (5)
  • April 2013 (12)
  • March 2013 (6)
  • September 2012 (2)

javascript

  • ajaxian
  • alistapart
  • developer.mozilla.org
  • diveintohtml5
  • ecmascript
  • Eric Meyer
  • es5
  • nanto
  • perfectionkills
  • ppk
  • sitepoint
  • webfx
  • Webkit documentation
  • High performance Website construction

The calendar

< In November 2017 >
day one two three four five six
29 30 31 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 1 2
3 4 5 6 7 8 9

Classification of essays

  • AJAX(5)
  • Cross Domain(3)
  • Design patterns(4)
  • DOM(2)
  • ECMAScript specification (8)
  • HTML[5]+CSS[3](17)
  • JavaScript(62)
  • Javascript plugins(7)
  • jQuery and plugins(1)
  • Linux(2)
  • Node(5)
  • Others(52)
  • PHP(4)
  • translation(4)

Recommended leaderboard

  • 1. View the front end from the Login box (136)
  • 2. Front-end Technology Inventory in recent years and technology development Direction in 2016 (85)
  • 3. Front-end code anomaly log collection and monitoring (63)
  • 4. I stayed with Ali for three months (62)
  • 5. Principle of Asynchronous JavaScript Programming (43)
  • 6. JavaScript Template Engine Principles, A Few lines of Code (36)
  • 7. Why does the modification of Hosts not take effect? DNS cache? (29)
  • 8. How to Make your JavaScript Code more semantic (25)
  • 9. Websocket-node in Detail (23)
  • 10. JavaScript Multi-file Download (23)

Reading leaderboards

  • 1. Websocket-php in detail (40514)
  • 2. Front View from the Login box (29573)
  • 3. Implementation and Application of PJAX (27324)
  • 4. Make the browser no longer display HTTP request alerts in HTTPS pages (25059)
  • 5. JavaScript Template Engine principle, a few lines of code (23234)
  • 6. Websocket-node in detail (22523)
  • 7. Front-end code anomaly log collection and monitoring (21883)
  • 8. JavaScript Multi-file Download (21859)
  • 9. Principle of Asynchronous JavaScript Programming (17802)
  • 10. Why does the modification of Hosts not take effect? DNS cache? (16597).

css

  • css-tricks
  • CSS manual
  • css-infos.net/
  • sketchpad

friends

  • Aaron
  • Rss Barret Lee
  • guangwong
  • Rss Time cobalt carbonate
  • The sea cast net
  • Tencent AlloyTeam
  • Small flying fish with jade face
  • Rss Zhang Xinxu

Tools

  • cleancss
  • css-validator
  • proxyie.cn/
  • jsbeautifier
  • JSLint
  • Packer-Decoder
  • regexper
  • RunJS
  • stackoverflow
  • User Agent
  • Formatting HTML online

Ready to do

  • Learning front-end architecture ideas
  • ECMAScript6 research
  • Read ECMA – 262

Recently in the…

  • Micro share
  • The animation library
  • – _ – please _ please work

advertising


The source code
feedback

  • Current 201 group chat 0