关键词:
2.4用Socket.IO处理与聊天相关的消息
我们前面说过程序必须要做三件事,其中第一个提供静态文件已经做了,现在来解决第二个,处理浏览器和服务器之间的通信。现代浏览器能用WebSocket处理浏览器跟服务器两者之间的通信。
Socket.IO为Node及客户端JavaScript提供了基于WebSocket以及其他传输方式的封装,它提供了一个抽象层。如果浏览器没有实现WebSocket,Socket.IO会自动去自动一个备选方案,而外提供的API还是一样的。本节将会:
1:简要介绍下Socket.IO,并确定要在服务器端使用的Socket.IO功能;
2:添加代码设置Socket.IO服务器;
3:添加代码处理各种聊天程序的事件;
Socket.IO提供了开箱即用的虚拟通道,所有程序不用吧每条消息都向已连接的用户广播,而是只向那些预定了某个通道的用户广播。用这个功能实现程序里的聊天室功能非常简单。
Socket.IO还是事件发射器的好例子。时间发射器本质上是组织异步逻辑的一种很方便的设计模式。本章中会有一些事件发射器的代码,但下一章才会做更深入的讨论。
事件发射器是跟某种资源相关联的,它能向这个资源发送消息,也能从这个资源接收消息。
我们先开始做服务器上的功能,并确立处理连接的逻辑。然后会定义服务器所需要的功能。
2.4.1设置Socket.IO服务器
在lib下创建chat._server.js:
//声明被提供使用的Socket.IO,并初始化部分定义聊天状态的变量 var socketio = require(‘socket.io‘); var io; var guestNumber = 1; var nickNames = ; var namesUsed = ; var currentRoom = ; //加载一个定制的Node模块,提供处理基于Socket.IO的服务端聊天功能的,暂未定义。 var chatServer = require(‘./lib/chat_server‘); //启动Socket.IO服务器,给她提供一个已经定义好的HTTP服务器,跟HTTP服务器共享同一个TCP/IP端口 chatServer.listen(server); exports.listen = function(server) //启动Socket.IO服务器允许它搭载在已有的HTTP服务器上 io = socketio.listen(server); io.set(‘log level‘,1); //定义每个用户连接处理的逻辑 io.sockets.on(‘connection‘,function(socket) //在用户连接上来时赋予一个访问名 guestNumber = assignGuestName(socket, guestNumber, nickNames, namesUsed); //在用户连接上来时把他放入聊天室Lobby里 joinRoom(socket,‘Lobby‘); //处理用户的消息,更名,以及聊天室的创建和变更 handleMessageBroadcasting(socket, nickNames); handleNameChangeAttempts(socket, nickNames, namesUsed); handleRoomJoining(socket); //用户发出请求时,向其提供已经被占用的聊天室的列表 socket.on(‘rooms‘,function() socket.emit(‘rooms‘, io.sockets.manager.rooms); ); //定义用户断开连接后的清楚逻辑 handleClientDisconnection(socket, nickNames, namesUsed); ); ;
已经确定了连接的处理逻辑,现在该添加用来处理程序需求的所有辅助函数了。
2.4.2 处理程序场景及事件
聊天程序需要处理下面这些场景和事件
1:分配昵称
2:房间更换请求
3:昵称更换请求
4:发送聊天消息
5:房间创建
6:用户断开连接
要实现这些功能得添加几个辅助函数,如下文所述。
1.分配昵称
要添加的第一个辅助函数是assignGuestName,用来处理新用户的昵称。当用户第一次连接到聊天服务器上时,用户会被放到一个叫做Lobby的聊天室中,并调用assignGuestName给他们分配一个昵称,以便可以相互区分开来。
//分配用户昵称 function assignGuestName(socket, guestNumber, nickNames, namesUsed) //生成新的昵称 var name = ‘Guest‘ + guestNumber; //把用户的昵称跟客户端连接ID关联上 nickNames[socket.id] = name; //让用户知道他们的昵称 socket.emit(‘nameResult‘, success:true, name:name ); //存放已被占用的昵称 namesUsed.push(name); //增加用来生产昵称的计数器 return guestNumber + 1;
程序分配的所有昵称基本上都是在Guest后面加上一个数字,,有新用户连接进来时这个数字就会增长。用户昵称存在变量nickNames中以便于引用,并且会跟一个内部socketID关联。昵称还会被添加到namesUsed中,这个变量中保存的是已经被占用的昵称。把下面清单中的代码添加到lib/chat_server.js中实现这个功能。
2.进入聊天室相关的逻辑
要添加到chat_server.js中的第二个辅助行数是joinRoom。处理逻辑跟用户加入聊天室有关。
//进入聊天室相关的逻辑 function joinRoom(socket, room) //让用户进入房间 socket.join(room); //记录用户的当前房间 currentRoom[socket.id] = room; //让用户知道他们进入了新的房间 socket.emit(‘joinResult‘,room: room); //让房间里的其他用户知道有新用户进入了房间 socket.broadcast.to(room).emit(‘message‘, text:nickNames[socket.id] + ‘has joined‘ + room +‘.‘ ); //确定有哪些用户在这个房间里 var usersInRoom = io.sockets.clients(room); //如果不止一个用户在这个房间里,汇总下有哪些用户 if(usersInRoom.length > 1) var usersInRoomSummary = ‘Userd currently in ‘ + room + ‘:‘; for(var index in userdInRoom) var userSocketId = usersInRoom[index].id; if(userSocketId != socket.id) if(index > 0) usersInRoomSummary += ‘, ‘; usersInRoomSummary +=nickNames[userSocketId]; usersInRoomSummary += ‘.‘; //将房间里其他用户的汇总发送给这个用户 socket.emit(‘message‘, text:usersInRoomSummary);
调用socket对象上的join方法就可以将用户加入Socket.IO房间。然后程序会吧相关的细节向这个用户及同一房间中的其他用户发送。程序会让用户知道有哪些用户在这个房间里,还会让其他用户知道这个用户进来了。
3.处理昵称变更请求
如果用户都用程序分配的昵称,很难记住谁是谁。因此聊天程序允许用户发起更名请求。更名需要用户的浏览器通过Socket.IO发送一个请求,并接收表示成功或失败的响应。
以下定义了一个出来用户更名请求的函数,加入到lib/chat_server.js中,用户不能将你从改成以Guest开头,或改成其他已经被占用的昵称。
//更名请求的处理逻辑 function handleNameChangeAttempts(socket, nickNames, namesUsed) //添加nameAttempt事件的监听器 socket.on(‘nameAttempt‘, function(name) //昵称不能以Guest开头 if(name.indexOf(‘Guest‘) == 0) socket.emit(‘nameResult‘, success : false, message: ‘Names cannot begin with "Guest".‘ ); else //如果昵称还没注册就允许注册 if(namesUsed.indexOf(name) == -1) var previousName = nickNames[socket.id] var previousNameIndex = namesUsed.indexOf(previousName); namesUsed.push(name); nickNames[socket.id] = name; //删除之前用的昵称,让其他用户可以使用 delete namesUsed[previousNameIndex]; socket.emit(‘nameResult‘, success: true, name:name ); socket.broadcast.to(currentRoom[socket.id]).emit(‘message‘, text:previousName + ‘is now known as ‘ + name + ‘.‘ ); else //如果昵称已经被占用,则给客户端发送错误信息 socket.emit(‘nameResult‘, success: false, message: ‘That name is already in use.‘ ) );
4.发送聊天消息
用户昵称没问题了,现在需要加个函数处理用户发过了的聊天消息。基本流程是:用户发射一个事件,表明消息是从哪个房间发出来的,已经消息的内容是什么;然后服务器将这条消息,转发给同一房间的所有用户。
将下面的代码加入到lib/chat_server.js中。Socket.IO的broadcast函数是用来转发消息的。
//发送聊天消息 function handleMessageBroadcasting(socket) socket.on(‘message‘,function(message) socket.broadcast.to(message.room).emit(‘message‘, text: nickNames[socket.id] + ‘: ‘ + message.text ); );
5.创建房间
如果还没有房间的话则创建一个房间,以下代码加入到lib/chat_server.js中,实现更好房间的功能。注意leave方法的使用。
//创建房间 function handleRoomJoining(socket) socket.on(‘join‘,function(room) socket.leave(currentRoom[socket.id]); joinRoom(socket, room.newRoom); );
6.用户断开连接
当用户离开聊天程序时,从NickNames和namesUsed中移除用户的昵称,将下面的代码加入到lib/chat_server.js中。
//用户断开连接 function handleClientDisconnection(socket) socket.on(‘disconnect‘,function() var nameIndex = namesUsed.indexOf(nickNames[socket.id]); delete namesUsed(nameIndex); delete nickNames[socket.id]; );
服务端的逻辑已经做好了,现在可以回过头去继续做客户端的逻辑了。
第二十二节,tensorflow中rnn实现一些其它知识补充(代码片段)
一初始化RNN上一节中介绍了 通过cell类构建RNN的函数,其中有一个参数initial_state,即cell初始状态参数,TensorFlow中封装了对其初始化的方法。1.初始化为0对于正向或反向,第一个cell传入时没有之前的序列输出值,所以需要对... 查看详情
第二十九节:asp.netcore零散获取总结(不断补充)(代码片段)
1. IWebHostEnvironment获取常用属性(1).获取项目的根目录 _env.ContentRootPath等价于Directory.GetCurrentDirectory()(2).获取项目下wwwroot目录 _env.WebRootPath(3).获取项目最终dll的目录(拼接) _env.ContentRootPath+@"inDebugetcoreapp3.1"(4)... 查看详情
第二十九节:asp.netcore零散总结(各种目录内外网ip)[不断补充](代码片段)
...:第二十九节:Asp.NetCore零散总结(各种目录、内外网ip)[不断补充]1. IWebHostEnvironment获取常用属性(1).获取项目的根目录 _env.ContentRootPath等价于Directory.GetCurrentDirectory()(2).获取项目下wwwroot目录 _env.WebRootPath(3).获取项目最... 查看详情
补充作业20165221
第二章视频三的第一个代码敲打代码时打引号未切换中英文最后切换才进行了下一步修改文件名格式错误未考虑到文件名要与类文件保持一致,应该以Example_20165221开头最后运行成功!这个代码实现的收获是,敲代码熟能生巧,... 查看详情
第二次作业(代码片段)
...sp; 第三步:运行,检查程序对错,加以补充。(2)流程图:2.实验代码#include"stdio.h"voiddelnum(char*s);intm 查看详情
网络编程-第二节(代码片段)
...客户端导入程序MyServer.java、MyClient.java、ClientThread.java。补充程序MyClient.java的代码(在“//TODO添加代码”提示的位置),使得1) 查看详情
flask-论坛开发-4-知识点补充(代码片段)
对Flask感兴趣的,可以看下这个视频教程:http://study.163.com/course/courseLearn.htm?courseId=10040910021.WTForms表单使用WTForms是一个支持多web框架的一个插件,主要功能有两个:第一个是做表单的验证,验证用户提交上来的信息是否合法,第... 查看详情
实验吧之whoareyou?(时间盲注)补充(代码片段)
第二种方法 使用brup进行盲注 也是一个道理 不多贴了这里提一下 burp怎么判断超时 Options->Connections->Tiimeouts->Normal这一空改成你想要的超时时间(默认为120秒)。 在进行Intruder攻击时,如果连接超时... 查看详情
(王道408考研操作系统)第二章进程管理-第一节3:进程控制(配合linux讲解)(代码片段)
文章目录一:如何实现进程控制二:进程控制原语(1)进程创建A:概述B:补充-Linux中的创建进程操作①:fork()②:fork()相关问题(2)进程终止A:概述B:补充-僵尸进程与孤儿进程... 查看详情
servlet第二篇servlet实现线程安全及其他细节补充(代码片段)
一、Servlet是单例的(一)?浏览器多次对Servlet的请求,一般情况下,服务器只创建一个Servlet对象,也就是说,Servlet对象一旦创建了,就会驻留在内存中,为后续的请求做服务,直到服务器关闭。(二)但是对于每次访问的请求... 查看详情
《opencl异构并行编程实战》补充笔记散点,第十二至十四章(代码片段)
?第十二章,在其他语言中使用OpenCL●JOCL(JavaBuildingforOpenCL),PyOpenCL●一个PyOpenCL的例子代码,需要pyopencl包1importpyopenclascl2importnumpyasnp3importnumpy.linalgasla45a=np.random.rand(50000).astype(np.float32)6b=np.random. 查看详情
第二次作业补充
一、课上的代码1#include<stdio.h>2intmain()3{voidfunstr(charstr);4charstr[12]={"helloworld!"};5str[0]=‘H‘;6funstr(str);7return0;8}9voidfunstr(charstr)10{11intt;12printf("%s ",str);13while(t!=‘ 查看详情
第二节,基础知识之更多的例子(代码片段)
...来源于网上,并亲手实践复现一遍,也有部分内容是自己补充本文会列出所参考文章,如有版权问题,请联系我,我会及时删除#-*-coding:utf-8-*-"""CreatedonFriMar2316:53:192018@author:zy"""‘‘‘Theano2.1.3-基础知识之更多的例子http://www.cnblog... 查看详情
js面向对象——object对象的方法补充原型继承关系图(代码片段)
一、Object.create()这个方法用于创建一个新对象。被创建的对象的__proto__指向create函数第一个参数的原型对象prototype,在创建新对象时可以通过create函数第二个参数指定一些属性。二、Object.hasOwnProperty()对象是否有某一个属于... 查看详情
《数据结构》第二章|线性表知识梳理(应对期末考)(代码片段)
文章目录一、线性表的定义和特点及案例引入1.线性表的定义和特点2.案例引入(1)一元多项式的运算(2)稀疏多项式的运算(3)图书信息管理系统二、线性表的类型定义三、线性表的顺序表示和实现1.初始化顺序表2.顺序表取值3.顺序... 查看详情
20165214结队编程项目-四则运算(第二周)(代码片段)
...比如语言支。设计思路我需要在上周的基础上对程序进行补充。在题目的生成上,应该再加上括号、÷、/本周达成:①能够 查看详情
docker补充(代码片段)
dockerfiledockerfile就是由一系列命令和参数构成的脚本,通过这个文件构建镜像,dockerfile就是一个文件,占得空间非常小。可以理解成一个文件里面写了很多命令。是什么作用,你把东西都写好在这个文件里了,执行这个文件,一... 查看详情
matlab补充(代码片段)
matlab在linux里运行.m文件其他:matlab-nodesktop-nosplash-rfile#file不含.m 查看详情