Summary of web chat rooms: web chat rooms
Preface: Recently I was writing a project in a chat room. I wrote a lot of JS (functions) at the front end, resulting in a bit of code ratio, bugs, and latencies. So I summarized the code I wrote yesterday and wrote it as a blog.
Project Background
Reference blog: http://www.cnblogs.com/alex3714/articles/5337630.html
First, let's look at several images.
Initial interface layout:
Add a bootstrap style:
Real-time chat effect:
Step 1: Click friends on the left-side interface to trigger the event and open the chat interface.
1.1 Add the active attribute to the click friend to highlight it.
Alex Li is a li tag, which has a link type with the user ID of Alex Li.
<li contact-type="single" id="1" class="list-group-item active" onclick="OpenChatWindow(this)"> </li>
1.2. How did the preceding single and id come from? See the following html code.
<Ul class = "list-group"> {% for friend in request. user. userprofile. friends. select_related %} <li contact-type = "single" id = "{friend. id }}" class = "list-group-item" onclick = "OpenChatWindow (this)"> <span class = "badge hide"> 14 </span> <! -- Number of new message reminders --> <span class = "contact-name" >{{ friend. name }}</span> </li >{% endfor %} </ul>
Step 2: Enable the "chatting with Alex Li" text in the interface title box.
Add the contact-id and the contact-type attribute to its div.
<Div class = "chat-box-title" contact-id = "1" contact-type = "single"> <span> chatting with Alex Li </span> </ div>
Step 3: bind events through event delegation (you can refer to the blog "about the benefits of delegate event delegation in JQ"). Call SendMsg (msg_text) as soon as you press the Enter key to send messages.
// Event Delegate $ ("body "). delegate ("textarea", "keydown", function (e) {// e = event if (e. which = 13) {// press the key number (e. which); 13 is the ASCII code of the enter key var msg_text =$ ("textarea "). val (); if ($. trim (msg_text ). length> 0) {console. log (msg_text); // send the message to the recipient SendMsg (msg_text); // print the message to your own window interface AddSendMsgIntoWindow (msg_text); $ ("textarea "). val (""); // clear the input box} else {alert ("Enter the message to be sent ")}}});
Step 4: send messages to the background through ajax
4.1 Message format:
Var msg_item = {"from": "{request. user. userprofile. id }}", "to": contact_id, "type": contact_type, "msg": msg_text // message to be sent };
Type is the sending format:
- Single indicates one-to-one transmission;
- Group indicates the group, "to" at this time, and 3 indicates the group id
There is no difficulty above. Here we use ajax to send the message dictionary (in json format) to the background. What should I do in the background?
Simple. send the message to the user. What if the user does not log on ?? So we have to save the data first. Where does it exist? To meet the requirements of first-in-first-out, you can use the queue. Of course, rabbitmq (python rabbitMQ) is preferred in the production environment.
4.2 Each user in the background has a queue. The Global queue in the background is as follows: the user id is used as the key, and the queue is used as the value.
GLOBAL_MSG_QUEUES = {"id": queue. Queue (),} # queue: global variable
4.3 setMessage dictionaryThe queue is stored in the queue of the user to be received. If the queue does not exist at this time, a queue is generated for the user to receive the message first, then, store the message dictionary in the queue of the user to be received.
1 # if the user queue does not exist, note that if the queue corresponding to id (key) does not exist, the output is None and no error is reported. 2 if not GLOBAL_MSG_QUEUES.get (queue_id ): 3 GLOBAL_MSG_QUEUES [queue_id] = queue. queue () # create a Queue 4 5 GLOBAL_MSG_QUEUES [queue_id]. put (msg_dic) # put the message dictionary (with timestamp) into the queue
4.4 After the message dictionary (including timestamp) is successfully saved to the queue of the user to be received, the ajax request is complete and "--- receive msg ---", indicates that the background has successfully received the message to be sent, indicating that the message dictionary is successfully saved to the queue of the user to be received.
return HttpResponse("---receive msg---")
Step 5: print the sent information to your own window interface and call AddSendMsgIntoWindow (msg_text );
Function AddSendMsgIntoWindow (msg_text) {varnew_msg_ele = "<divclass = 'msg-item'>" + "<span>" + "{request. user. userprofile. name }}" + "</span>" + "<span>" + newDate (). toLocaleDateString () + "</span>" + "<divclass = 'msg-text'>" + msg_text + "</div>" + "</div> "; $ (". chat-box-window "). append (new_msg_ele); $ (". chat-box-window "). animate ({scrollTop: $ (". chat-box-window ") [0]. scrollHeight // automatically scroll down every 0.5s}, 500); // console. log ($ (". chat-box-window ") [0]);}
When you print messages to your own interface, problems may occur at the beginning. For example, if you send a lot of data and the entire div on the interface is no longer installed, the system will "overflow" div. Simple. Just add an "overflow" style to the chat interface.
Overflow: auto;/* Add a scroll bar to the div content */
Great. Now there are a lot of chat content, and the scroll bar will appear automatically !! But the problem comes again. Although there is a scroll bar, every time you send a message, you have to pull the scroll bar to the bottom to see the message just sent. This ......
So I found this blog online: What does scrollTop and scrollHeight mean?
Today, we need to use real-time display of the latest update content, that is, to keep the dialog box at the bottom at any time. Check it and use div. scrollTop = div. scrollHeight. I checked the meaning of the two parameters. Someone on stackoverflow answered this question. If I scroll down 5px in this window, the window's scrollTop value is 5. if I scroll right 10px in a scrollable div, the div's scrollLeft value is 10. when I scroll to the top left corner of this window, both its scrollTop and scrollLeft values are 0. another person added: scrollTop and scrollHeight. in summary, scrollTop is how much it's currently scrolled, and scrollHeight is the total height, including conten T scrolled out of view. In general, scrollTop is the part that is rolled up, that is, the part that is invisible along with the drop-down. ScrollHeight is the sliding height of the entire window.
So, I can solve the problem by using the following method, and automatically scroll down to the bottom every 0.5 s, the animate () method
$(".chat-box-window").animate({scrollTop:$(".chat-box-window")[0].scrollHeight},5000);
Step 6: Call GetNewMsgs () as soon as the interface is loaded; start to retrieve the message and initiate ajax request to the background
How do I retrieve messages ?? That is to say, after A sends data to B and the background receives data from A, how does one return it to B?
{# The browser with the timer will crash (card) #}{# setInterval (function () {#}{# GetNewMsgs () ;#}{#}, 3000 )#}
3. If a timer is used, the message is not real-time. For example, user A quickly sends A lot of data, but user B obtains data in the background every 3 seconds, with A latency of up to 3 seconds in the middle. In this case, the message is not real-time.
4. You can check whether there is any data in your queue. If there is no data, the backend is suspended.
# Timeout pending # If the queue is empty, the queue will be exposed 60 seconds later. empty exception (equivalent to the suspension in 60 seconds) try: msg_list.append (q_obj.get (timeout = 60) wait t queue. empty: # If the queue is Empty, the queue will be exposed 60 seconds later. empty exception (equivalent to suspending within 60 seconds) print ("\ 033 [41; 1mno msg for [% s] [% s], timeout \ 033 [0 m "% (request. user. userprofile. id, request. user ))
Although the backend can be suspended when the queue has no data, the front-end initiates an ajax request every 3 seconds with a timer and asks, "Is there any new data in my queue ?? ", Each request is equivalent to a browser thread, a long time, the browser will be stuck, unable to support. What should we do ?? Can't I use a timer ?? What other methods can be used to blow up the sky?
According to my best practice, the frontend initiates a request once. If the queue has data, it immediately retrieves the data. If there is no data, it suspends for 60 seconds (the time can be set in the background ), if there is data in the queue within 60 seconds, the data is also retrieved from the queue. If there is no data in the queue within 60 seconds, the queue is generated. empty exception. The front-end then initiates an ajax request.
Before solving the problem of too many browser threads, let's take a look at how the background handles the function of suspending the queue when the queue is empty. See the figure below and add the code at above. You should understand it.
Okay. Back to the browser, too. I used the recursive method. The Code is as follows:
1 // The user obtains the Message 2 function GetNewMsgs () {3 console. log ("---- getting new msg ----"); 4 $. getJSON ("{% url 'get _ new_msgs '%}", 5 function (callback) {6 // callback is a list object, object, each element in the list is a message dictionary 7 console. log (callback, typeof callback); // Array [Object] object 8 // parse the message. The user may receive a message from the user currently chatting, it may also be the message sent by other users 9 ParseNewMsgs (callback); 10 11 return GetNewMsgs (); // recursive 12}) 13}
No timer. The frontend initiates an ajax request, and the backend queue is suspended if no data exists. When the background returns data to the front-end, there are two situations: one is timeout. For example, no new message is sent to the queue within 60 seconds, and a queue is generated. empty returns data after an exception. The second is that the user receives the data sent to him by another user. When the queue has data, the data is returned to the front-end. After the front-end receives the data, the callback function initiates an ajax request.
If the front-end does not receive data (within the time when the backend is suspended), no ajax request will be initiated. In this case, the front-end is suspended. Not too many threads.
(Note: A mechanism specially set by python is used to prevent infinite recursion from causing Python overflow and crash. There are layer-data restrictions in python recursion, namely layer-4. If the value is exceeded, "RuntimeError: maximum recursion depth exceeded" is thrown ")
Step 7
The backend queries whether a message (data dictionary) exists in the user queue. If no message exists, the message is suspended. If no message exists for one minute, the front-end initiates an ajax request. If a message exists in the queue, it is returned to the user in the form of a list. Each element in the list is in the form of a data dictionary.
Eg: {"from": "3", "to": "2", "type": "single", "msg ": & quot; 1111 & quot;} <class 'str'> return HttpResponse (json. dumps (msg_list) # serialize, convert to json format
Step 8:
Q: User A is chatting with Goddess B. Click A friend on the left to switch to Goddess C and chat with Goddess C. But the chat window is still the content of chatting with Goddess B.
Next, you need to complete the view switching function, and save the html elements of the original view to the global dictionary before switching.
// Global dictionary, used to store the html element GLOBAL_CHAT_RECORD_DIC = {"single" :{}, "group" :{}} before switching views ":{}};
The function of the global dictionary can store the messages sent by Goddess C in the value (dictionary) of "single ), set a dictionary for user id and user chat data (C user id is 3, xxx is the html element that will be processed ).
For example: "single": {"3": "xxx "},
Before switching the view, add the html elements (data) in the chat window with Goddess B to the user's global dictionary:
// View switching. Save the html element of the original view to the global dictionary before switching. // the contact-id attribute of the original box is not empty, that is, the existing click object if ($ (". chat-box-title "). attr ("contact-id") {// retrieve the user id before switching from the title box, and contact-type var session_id = $ (". chat-box-title "). attr ("contact-id"); var session_type = $ (". chat-box-title "). attr ("contact-type"); // Save the pre-switched View to the global dictionary GLOBAL_CHAT_RECORD_DIC [session_type] [session_id] = $ (". chat-box-window "pai.html ();}
After "chatting with Goddess C" is displayed in the box, the chat data with Goddess C is retrieved from the global dictionary and displayed in the chat window.
// Save the html element of chat-window from the global dictionary // when you click this contact for the first time, chat_record is undefined because no id (key) has been generated under the single dictionary) valuevar chat_record = GLOBAL_CHAT_RECORD_DIC [contact_type] [contact_id]; console. log (chat_record, typeof chat_record); if (typeof chat_record = "undefined") {$ (". chat-box-window ").html (" ");} else {// If chat_record is undefined, the following code cannot clear the dialog interface (important) $ (". chat-box-window ").html (chat_record); console. log ("haha>", chat_record )}
The view switching function is basically complete.
Step 9: After receiving data from the background through ajax, the front end renders the data into an html style and displays it in the chat window.
Here we will analyze the situation.
If the user receives a message that is currently chatting with the user, add the html element directly to the chat window $ (". chat-box-window "). append (new_msg_ele); otherwise, for example, user A is chatting with user B and receives A message from user C. Where is the message sent from C? Of course it is the global variable set above !!
// The user receives the message if (current_session_id = callback [msg_item] ["from"] & current_session_type = callback [msg_item] ["type" ]) {// Add the html element of the message to the chat window $ (". chat-box-window "). append (new_msg_ele);} else {// The message sender dialog box is not opened and the message is saved to the memory (in the global variable) if (typeof GLOBAL_CHAT_RECORD_DIC [callback [msg_item] ["type"] [callback [msg_item] ["from"] = "undefined") {GLOBAL_CHAT_RECORD_DIC [callback [msg_item]. type] [callback [msg_item]. from] = new_msg_ele;} else {// If GLOBAL_CHAT_RECORD_DIC [current_session_type] [current_session_id] is not undefined GLOBAL_CHAT_RECORD_DIC [callback [msg_item]. type] [callback [msg_item]. from] + = new_msg_ele ;}}
Write so much first. Forward notes published: http://www.cnblogs.com/0zcl/p/6903017.html
I learned git a few days ago. Do you want to do this small project together ?? It is always too slow for a person to write code. If you want to complete it together, please check out my git project https://github.com/0zcl/bbs_projectand you can give it to me by pull. Welcome to the discussion.