Node.js websocket 使用 socket.io库实现实时聊天室

<div id="article_content" class="article_content">
        <div class="markdown_views"><h2 id="认识websocket"><a name="t0"></a><strong>认识websocket</strong></h2>


<p>WebSocket protocol 是<a href="http://lib.youkuaiyun.com/base/html5" class="replace_word" title="HTML5知识库" target="_blank" style="color:#df3434; font-weight:bold;">HTML5</a>一种新的协议。它实现了浏览器与服务器全双工通信(full-duple)。一开始的握手需要借助HTTP请求完成。</p>


<p>其实websocket 并不是很依赖Http协议,它也拥有自己的一套协议机制,但在这里我们需要利用的socket.io 需要依赖到http 。 <br>
之前用<a href="http://lib.youkuaiyun.com/base/java" class="replace_word" title="Java 知识库" target="_blank" style="color:#df3434; font-weight:bold;">Java </a>jsp写过一个聊天,其实实现逻辑并不难,只是大部分时间都用在UI的设计上,其实现原理就是一个基于websocket的通信,要想做一个好的聊天室,我觉得大部分精力可能更应该花在与用户的视觉层交互上。</p>


<p>废话不闲扯,我们先来看一下websocket 与传统的ajax 有什么不同之处。 <br>
在之前,如果我们想要获取到服务器更新的信息,我们可以使用ajax 轮询来完成,然而,这样做的弊端是增大了我们与服务器的交互次数,然而极大部分的交互都是无意义的,因为我们只是做一个询问,如果没有任何新的信息,我们几乎什么都不用做,因此这样会极大的浪费服务器资源和带宽。 <br>
然而使用websocket 会使客户端与服务器建立一个长连接,并且,当服务器有新消息时可以主动推送到客户端,所以我们可以不用一次次的去询问服务器是否有新消息,而是直接由服务器主动推送到客户端,这样在无消息的状态下,客户端不会频繁的去请求服务器。 <br>
使用websocket 的特点在于服务器可以主动推送消息到客户端。</p>






<h2 id="使用socketio-库实现实时聊天"><a name="t1"></a><strong>使用socket.io 库实现实时聊天</strong></h2>


<p>这也是这篇博文的主题之处。socket.io发布到npm 平台上,我们可以直接用npm 来安装到**当前**node_modules目录下。</p>






<pre class="prettyprint" name="code"><code class="hljs lua has-numbering">npm install socket.<span class="hljs-built_in">io</span> <span class="hljs-comment">--save </span></code><ul class="pre-numbering"><li>1</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li></ul></pre>


<p>下面我们就可以直接使用require 方法来将这个模块引入</p>






<pre class="prettyprint" name="code"><code class="hljs javascript has-numbering"><span class="hljs-keyword">const</span> socket = <span class="hljs-built_in">require</span>(<span class="hljs-string">"socket.io"</span>);</code><ul class="pre-numbering"><li>1</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li></ul></pre>


<p>在创建此websocket 服务器之前,它需要依赖于一个已经创建好的http服务器。</p>






<pre class="prettyprint" name="code"><code class="hljs coffeescript has-numbering"><span class="hljs-reserved">let</span> socketServer = socket.listen<span class="hljs-function"><span class="hljs-params">(<span class="hljs-built_in">require</span>(<span class="hljs-string">"http"</span>).createServer((req,resp) =&gt; {
    <span class="hljs-regexp">//</span>返回页面
    resp.end(<span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>).readFileSync(<span class="hljs-string">"./socketIOTest1.html"</span>));
}).listen(<span class="hljs-number">9999</span>,<span class="hljs-string">"localhost"</span>,() =&gt; {<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"listening"</span>);}))</span>;</span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul></pre>


<p>在上述代码中socketIOTest1.html 是在当前目录下的一个html文件,在下面我会贴上详细的代码,这里先稍稍带过。</p>


<p>在websocket 服务器对象中有一个connection事件,这个事件在有客户端连接到socket服务器时被触发。下面我们监听这个事件,打印一句话来表示有用户连接。</p>






<pre class="prettyprint" name="code"><code class="hljs coffeescript has-numbering"><span class="hljs-regexp">//</span>监听connection 事件
socketServer.<span class="hljs-literal">on</span>(<span class="hljs-string">"connection"</span>,socket<span class="hljs-function"> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"有一用户连接"</span>);
}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul></pre>


<p>上述代码中,callback有一个参数socket为连接到客户端的一个socket端口对象,这个对象有一个message 事件,当客户端有消息推送到服务器时,事件循环会取出这个事件与之对应的回调函数并执行。</p>






<pre class="prettyprint" name="code"><code class="hljs coffeescript has-numbering">socket.<span class="hljs-literal">on</span>(<span class="hljs-string">"message"</span>,msg<span class="hljs-function"> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(msg);
});</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul></pre>


<p>同时,socket对象还可以监听disconnect 事件,来监听用户断开连接的情况</p>






<pre class="prettyprint" name="code"><code class="hljs coffeescript has-numbering">socket.<span class="hljs-literal">on</span><span class="hljs-function"><span class="hljs-params">(<span class="hljs-string">"disconnect"</span>,() =&gt; {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"有一用户退出连接"</span>);
})</span>;</span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul></pre>


<p>因为我们这次的主题是要创建一个能够实时聊天的聊天室,因此光有这些是不够的,我们还需要一个能够与用户交互的客户端。 <br>
下面是我的socketIOTest代码:</p>






<pre class="prettyprint" name="code"><code class="hljs xml has-numbering"><span class="hljs-doctype">&lt;!DOCTYPE html&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">html</span> <span class="hljs-attribute">lang</span>=<span class="hljs-value">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">meta</span> <span class="hljs-attribute">charset</span>=<span class="hljs-value">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">title</span>&gt;</span>Document<span class="hljs-tag">&lt;/<span class="hljs-title">title</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-title">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">textarea</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">""</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"content"</span> <span class="hljs-attribute">cols</span>=<span class="hljs-value">"30"</span> <span class="hljs-attribute">rows</span>=<span class="hljs-value">"10"</span> &gt;</span><span class="hljs-tag">&lt;/<span class="hljs-title">textarea</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">input</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"write"</span> <span class="hljs-attribute">type</span>=<span class="hljs-value">"text"</span> <span class="hljs-attribute">placeholder</span>=<span class="hljs-value">"please write content here"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">input</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"send"</span> <span class="hljs-attribute">type</span>=<span class="hljs-value">"button"</span> <span class="hljs-attribute">value</span>=<span class="hljs-value">"send"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">script</span> <span class="hljs-attribute">src</span>=<span class="hljs-value">"./socket.io/socket.io.js"</span>&gt;</span><span class="javascript"></span><span class="hljs-tag">&lt;/<span class="hljs-title">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">script</span>&gt;</span><span class="javascript">
        <span class="hljs-keyword">let</span> send = document.getElementById(<span class="hljs-string">"send"</span>);
        <span class="hljs-keyword">let</span> write = document.getElementById(<span class="hljs-string">"write"</span>);
        <span class="hljs-keyword">let</span> content = document.getElementById(<span class="hljs-string">"content"</span>);
        <span class="hljs-keyword">let</span> socket = io.connect();
        <span class="hljs-comment">//发送消息</span>
        send.onclick = () =&gt; {
            <span class="hljs-keyword">let</span> msg = write.value;
            <span class="hljs-comment">// content.innerHTML = content.value + msg + "\n";</span>
            socket.send(msg);
        };
        <span class="hljs-comment">//接收到消息</span>
        socket.on(<span class="hljs-string">"message"</span>,msg =&gt; {
            console.log(<span class="hljs-string">"从服务器接收到的消息 : "</span> + msg);
            <span class="hljs-comment">//更新内容</span>
            content.innerHTML = content.value + msg + <span class="hljs-string">"\n"</span>;
        });
        socket.on(<span class="hljs-string">"disconnect"</span>,() =&gt; {
            console.log(<span class="hljs-string">"与服务器断开连接"</span>);
        });
    </span><span class="hljs-tag">&lt;/<span class="hljs-title">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-title">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-title">html</span>&gt;</span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li></ul></pre>


<p>在上述代码中,我用script标签引入了一个socket.io.js文件,这个文件不需要另外去下载,而直接引入即可,因为socket.io.js是被包含于socket.io模块中,在上面node的程序代码中,我们通过require方法引入了socket.io模块,因此我们可以直接通过相对路径访问到它。</p>






<pre class="prettyprint" name="code"><code class="hljs xml has-numbering"><span class="hljs-tag">&lt;<span class="hljs-title">script</span> <span class="hljs-attribute">src</span>=<span class="hljs-value">"./socket.io/socket.io.js"</span>&gt;</span><span class="javascript"></span><span class="hljs-tag">&lt;/<span class="hljs-title">script</span>&gt;</span></code><ul class="pre-numbering"><li>1</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li></ul></pre>


<p>接下来我们就可以在script标签中使用如同服务端的代码。</p>






<pre class="prettyprint" name="code"><code class="hljs perl has-numbering">    let <span class="hljs-keyword">socket</span> = io.<span class="hljs-keyword">connect</span>();</code><ul class="pre-numbering"><li>1</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li></ul></pre>


<p>使用io.connect()方法连接到websocket服务器,该方法返回一个与连接的服务器与之对应的一个socket端口对象。</p>


<p>下面我们同样监听message 和 disconnect事件。</p>






<pre class="prettyprint" name="code"><code class="hljs coffeescript has-numbering"><span class="hljs-regexp">//</span>接收到消息
socket.<span class="hljs-literal">on</span>(<span class="hljs-string">"message"</span>,msg<span class="hljs-function"> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"从服务器接收到的消息 : "</span> + msg);
    <span class="hljs-regexp">//</span>更新内容
    content.innerHTML = content.value + msg + <span class="hljs-string">"\n"</span>;
});
socket.<span class="hljs-literal">on</span><span class="hljs-function"><span class="hljs-params">(<span class="hljs-string">"disconnect"</span>,() =&gt; {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"与服务器断开连接"</span>);
})</span>;</span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul></pre>


<p>为了更能突出websocket的作用,在html代码中,我只使用了一个textarea标签来显示内容,两个input标签用于发送。 <br>
使用socket对象的send方法就能使消息在服务器与客户端进行消息传递。</p>






<h3 id="websocket群聊实现"><a name="t2"></a><strong>websocket群聊实现</strong></h3>


<p>现在我们假设一个场景,有u1和u2两个用户,同时连接到服务器,那么我们怎么使他们互相通信呢,实现的方法及其简单。当u1连接到服务器,在服务器中,使用一个map键值对把与u1对应的socket对象进行保存。</p>






<pre class="prettyprint" name="code"><code class="hljs lasso has-numbering"><span class="hljs-comment">//创建一个用于放置用户对象的map</span>
<span class="hljs-keyword">let</span> <span class="hljs-built_in">map</span> <span class="hljs-subst">=</span> <span class="hljs-literal">new</span> <span class="hljs-built_in">Map</span>();
<span class="hljs-comment">//用于记录用户数量的变量,并初始化为0</span>
<span class="hljs-keyword">let</span> userCount <span class="hljs-subst">=</span> <span class="hljs-number">0</span>;
<span class="hljs-comment">//监听connection 事件</span>
socketServer<span class="hljs-built_in">.</span><span class="hljs-keyword">on</span>(<span class="hljs-string">"connection"</span>,socket <span class="hljs-subst">=&gt;</span> {
    console<span class="hljs-built_in">.</span><span class="hljs-keyword">log</span>(<span class="hljs-string">"有一用户连接"</span>);
    <span class="hljs-built_in">map</span><span class="hljs-built_in">.</span><span class="hljs-built_in">set</span>(<span class="hljs-subst">++</span>userCount,socket);
    <span class="hljs-comment">//...</span>
}); </code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li></ul></pre>


<p>与此同时,u2也连接上服务器,也由该map把与u2与之对应的socket对象进行储存。 <br>
现在,u1点击了send按钮发送一条消息至服务器,服务器收到消息后遍历map,转发给所有socket对象,实现群聊的实时通信。</p>






<pre class="prettyprint" name="code"><code class="hljs coffeescript has-numbering">socketServer.<span class="hljs-literal">on</span>(<span class="hljs-string">"connection"</span>,socket<span class="hljs-function"> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"有一用户连接"</span>);
    map.set(++userCount,socket);
    <span class="hljs-regexp">//</span>监听客户端来的信息
    socket.<span class="hljs-literal">on</span>(<span class="hljs-string">"message"</span>,msg<span class="hljs-function"> =&gt;</span> {
        <span class="hljs-regexp">//</span>从客户端接收的消息
        <span class="hljs-regexp">//</span>遍历所有用户
        map.forEach<span class="hljs-function"><span class="hljs-params">((value,index,arr) =&gt; {
            value.send(msg);
        })</span>;
    });
}); </span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul></pre>


<p>下面我贴上服务端的完整代码,仅供参考</p>






<pre class="prettyprint" name="code"><code class="hljs coffeescript has-numbering"><span class="hljs-reserved">const</span> socket = <span class="hljs-built_in">require</span>(<span class="hljs-string">"socket.io"</span>);
<span class="hljs-regexp">//</span>创建一个websocket服务器
<span class="hljs-reserved">let</span> socketServer = socket.listen<span class="hljs-function"><span class="hljs-params">(<span class="hljs-built_in">require</span>(<span class="hljs-string">"http"</span>).createServer((req,resp) =&gt; {
    <span class="hljs-regexp">//</span>返回页面
    resp.end(<span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>).readFileSync(<span class="hljs-string">"./socketIOTest1.html"</span>));
}).listen(<span class="hljs-number">9999</span>,<span class="hljs-string">"localhost"</span>,() =&gt; {<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"listening"</span>);}))</span>;


//创建一个用于放置用户对象的<span class="hljs-title">map</span>
<span class="hljs-title">let</span> <span class="hljs-title">map</span> = <span class="hljs-title">new</span> <span class="hljs-title">Map</span><span class="hljs-params">()</span>;
//用于记录用户数量的变量,并初始化为0
<span class="hljs-title">let</span> <span class="hljs-title">userCount</span> = 0;


//监听<span class="hljs-title">connection</span> 事件
<span class="hljs-title">socketServer</span>.<span class="hljs-title">on</span><span class="hljs-params">(<span class="hljs-string">"connection"</span>,socket =&gt; {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"有一用户连接"</span>);
    map.set(++userCount,socket);
    <span class="hljs-regexp">//</span>监听客户端来的信息
    socket.<span class="hljs-literal">on</span>(<span class="hljs-string">"message"</span>,msg =&gt; {
        <span class="hljs-regexp">//</span>从客户端接收的消息
        <span class="hljs-regexp">//</span>遍历所有用户
        map.forEach((value,index,arr) =&gt; {
            value.send(msg);
        });
    });
    <span class="hljs-regexp">//</span>监听客户端退出情况
    socket.<span class="hljs-literal">on</span>(<span class="hljs-string">"disconnect"</span>,() =&gt; {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"有一用户退出连接"</span>);
    });
})</span>; </span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li></ul></pre>


<p><img src="https://img-blog.youkuaiyun.com/20170219205801418?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSGFvRGFXYW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title=""> <br>
<img src="https://img-blog.youkuaiyun.com/20170219205817326?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSGFvRGFXYW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title=""></p>






<h3 id="websocket私聊实现"><a name="t3"></a><strong>websocket私聊实现</strong></h3>


<p>在说私聊的实现之前,我们首先要找到对于每一个用户的唯一标识,在通常的项目开发中,我们都使用用户的用户名进行标识,每个用户通过注册获得与之对应的用户名。将用户名保存在<a href="http://lib.youkuaiyun.com/base/mysql" class="replace_word" title="MySQL知识库" target="_blank" style="color:#df3434; font-weight:bold;">数据库</a>中利用主键防止重复。</p>


<p>实现私聊的方法有很多种,这里我的实现方法是这样的: <br>
① 当用户连接时,把用户的socket端口对象使用map进行储存,储存的key 为用户的socket对象,value为用户的用户名,写一个方法用于更新客户端列表 <br>
② 用户默认用户名为 &lt;未命名&gt;,指定自定义用户名时,使用socket.emit方法触发服务端的某个事件,遍历map找到与之对应的key,进行value修改 <br>
③ 发送消息时,根据选择列表来指定要发送的人,在服务端,遍历map,找到要发送到的用户名,进行发送,同时更新到自己的聊天框</p>


<p>以上就是私聊的简单实现。</p>


<p>下面看一下具体代码: <br>
//<a href="http://lib.youkuaiyun.com/base/nodejs" class="replace_word" title="Node.js知识库" target="_blank" style="color:#df3434; font-weight:bold;">Node.js</a></p>


<pre class="prettyprint" name="code"><code class="hljs coffeescript has-numbering"><span class="hljs-reserved">const</span> socket = <span class="hljs-built_in">require</span>(<span class="hljs-string">"socket.io"</span>);
<span class="hljs-regexp">//</span>创建一个websocket服务器
<span class="hljs-reserved">let</span> socketServer = socket.listen<span class="hljs-function"><span class="hljs-params">(<span class="hljs-built_in">require</span>(<span class="hljs-string">"http"</span>).createServer((req,resp) =&gt; {
    <span class="hljs-regexp">//</span>返回页面
    resp.end(<span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>).readFileSync(<span class="hljs-string">"./socketIOTest1.html"</span>));
}).listen(<span class="hljs-number">9999</span>,<span class="hljs-string">"localhost"</span>,() =&gt; {<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"listening"</span>);}))</span>;


//创建一个用于放置用户对象的<span class="hljs-title">map</span>
<span class="hljs-title">let</span> <span class="hljs-title">map</span> = <span class="hljs-title">new</span> <span class="hljs-title">Map</span><span class="hljs-params">()</span>;
//用于记录用户数量的变量,并初始化为0
<span class="hljs-title">let</span> <span class="hljs-title">userCount</span> = 0;
//遍历<span class="hljs-title">map</span> 
<span class="hljs-title">let</span> <span class="hljs-title">scanMap</span> = <span class="hljs-title">func</span> =&gt;</span> {
    <span class="hljs-keyword">try</span>{
        map.forEach<span class="hljs-function"><span class="hljs-params">((value,index,arr) =&gt; {
            func(value,index,arr);
        })</span>;
    }
    <span class="hljs-title">catch</span><span class="hljs-params">(e)</span>{
        <span class="hljs-title">if</span><span class="hljs-params">(e.message == <span class="hljs-string">"break"</span>)</span>{
            <span class="hljs-title">return</span>;
        }
        <span class="hljs-title">else</span>{
            <span class="hljs-title">throw</span> <span class="hljs-title">e</span>;
        }
    }
}


//通知客户端弹出对话框
<span class="hljs-title">let</span> <span class="hljs-title">showDialog</span> = <span class="hljs-params">(socket,msg)</span> =&gt;</span> {
    socket.emit(<span class="hljs-string">"showDialog"</span>,msg);
}


<span class="hljs-regexp">//</span>更新用户列表
<span class="hljs-reserved">let</span> updateList = socket<span class="hljs-function"> =&gt;</span> {
    <span class="hljs-reserved">let</span> userArr = [];
    scanMap<span class="hljs-function"><span class="hljs-params">((value,index) =&gt; {
        <span class="hljs-keyword">if</span>(value != <span class="hljs-literal">undefined</span>){
            userArr.push(value);
        }
    })</span>;
    <span class="hljs-title">socket</span>.<span class="hljs-title">emit</span><span class="hljs-params">(<span class="hljs-string">"newUser"</span>,userArr)</span>;
}


//监听<span class="hljs-title">connection</span> 事件
<span class="hljs-title">socketServer</span>.<span class="hljs-title">on</span><span class="hljs-params">(<span class="hljs-string">"connection"</span>,socket =&gt; {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"有一用户连接"</span>);
    <span class="hljs-regexp">//</span>初始化存储当前socket对象
    map.set(socket,<span class="hljs-string">"&lt;未命名&gt;"</span>);
    <span class="hljs-regexp">//</span>将用户信息写入map
    socket.<span class="hljs-literal">on</span>(<span class="hljs-string">"getUser"</span>,user =&gt; {
        <span class="hljs-regexp">//</span>修改名称
        map.set(socket,user);
        scanMap((value,index) =&gt; {
            updateList(index);
        });
    });
    <span class="hljs-regexp">//</span>通知所有客户端更新列表
    scanMap((value,index) =&gt; {
        updateList(index);
    });
    <span class="hljs-regexp">//</span>监听客户端来的信息
    socket.<span class="hljs-literal">on</span>(<span class="hljs-string">"message"</span>,msg =&gt; {
        <span class="hljs-regexp">//</span>从客户端接收的消息
        <span class="hljs-reserved">let</span> sender;
        <span class="hljs-regexp">//</span>遍历所有用户
        scanMap((value,index) =&gt; {
            <span class="hljs-keyword">if</span>(index == socket){
                sender = value;
            }
        });
        scanMap((value,index) =&gt; {
            <span class="hljs-keyword">if</span>(msg.person == <span class="hljs-string">"all"</span>){
                index.send(sender + <span class="hljs-string">" : "</span> + msg.msg);
            }
            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(msg.person == value){
                socket.send(sender + <span class="hljs-string">" : "</span> +msg.msg);
                index.send(sender + <span class="hljs-string">" : "</span> +msg.msg);
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Error(<span class="hljs-string">"break"</span>);
            }
        });
    });
    <span class="hljs-regexp">//</span>监听客户端退出情况
    socket.<span class="hljs-literal">on</span>(<span class="hljs-string">"disconnect"</span>,() =&gt; {
        <span class="hljs-regexp">//</span>用户退出,从map里删除该用户
        map.set(socket,<span class="hljs-literal">undefined</span>);
        <span class="hljs-regexp">//</span>通知所有用户更新列表
        scanMap((value,index) =&gt; {
            updateList(index);
        });
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"有一用户退出连接"</span>);
    });
})</span>; </span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li><li>82</li><li>83</li><li>84</li><li>85</li><li>86</li><li>87</li><li>88</li><li>89</li><li>90</li><li>91</li><li>92</li><li>93</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li><li>82</li><li>83</li><li>84</li><li>85</li><li>86</li><li>87</li><li>88</li><li>89</li><li>90</li><li>91</li><li>92</li><li>93</li></ul></pre>


<p>客户端:</p>






<pre class="prettyprint" name="code"><code class="hljs xml has-numbering"><span class="hljs-doctype">&lt;!DOCTYPE html&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">html</span> <span class="hljs-attribute">lang</span>=<span class="hljs-value">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">meta</span> <span class="hljs-attribute">charset</span>=<span class="hljs-value">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">title</span>&gt;</span>Document<span class="hljs-tag">&lt;/<span class="hljs-title">title</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-title">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">textarea</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">""</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"content"</span> <span class="hljs-attribute">cols</span>=<span class="hljs-value">"30"</span> <span class="hljs-attribute">rows</span>=<span class="hljs-value">"10"</span> &gt;</span><span class="hljs-tag">&lt;/<span class="hljs-title">textarea</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">input</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"write"</span> <span class="hljs-attribute">type</span>=<span class="hljs-value">"text"</span> <span class="hljs-attribute">placeholder</span>=<span class="hljs-value">"please write content here"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">input</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"send"</span> <span class="hljs-attribute">type</span>=<span class="hljs-value">"button"</span> <span class="hljs-attribute">value</span>=<span class="hljs-value">"send"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">input</span> <span class="hljs-attribute">type</span>=<span class="hljs-value">"text"</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"user"</span> <span class="hljs-attribute">placeholder</span>=<span class="hljs-value">"user"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">select</span> <span class="hljs-attribute">style</span>=<span class="hljs-value">"width: 100px;"</span> <span class="hljs-attribute">size</span>=<span class="hljs-value">"2"</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">""</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"userList"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-title">option</span> <span class="hljs-attribute">value</span>=<span class="hljs-value">"all"</span>&gt;</span>群聊<span class="hljs-tag">&lt;/<span class="hljs-title">option</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-title">select</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">script</span> <span class="hljs-attribute">src</span>=<span class="hljs-value">"./socket.io/socket.io.js"</span>&gt;</span><span class="javascript"></span><span class="hljs-tag">&lt;/<span class="hljs-title">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">script</span>&gt;</span><span class="javascript">
        <span class="hljs-keyword">let</span> send = document.getElementById(<span class="hljs-string">"send"</span>);
        <span class="hljs-keyword">let</span> write = document.getElementById(<span class="hljs-string">"write"</span>);
        <span class="hljs-keyword">let</span> content = document.getElementById(<span class="hljs-string">"content"</span>);
        <span class="hljs-keyword">let</span> user = document.getElementById(<span class="hljs-string">"user"</span>);
        <span class="hljs-comment">//用户列表</span>
        <span class="hljs-keyword">let</span> userList = document.getElementById(<span class="hljs-string">"userList"</span>);
        <span class="hljs-keyword">let</span> socket = io.connect();
        <span class="hljs-comment">//判断用户名是否为空</span>
        <span class="hljs-keyword">let</span> isUserEmpty = () =&gt; {
            <span class="hljs-keyword">if</span>(user.value == <span class="hljs-string">""</span>){
                alert(<span class="hljs-string">"请填写用户名"</span>);
                <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
            }
            <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
            }
        }
        <span class="hljs-comment">//监听用户名变化</span>
        <span class="hljs-keyword">let</span> oldUser;
        user.onblur = () =&gt; {
            <span class="hljs-keyword">if</span>(isUserEmpty()){
                <span class="hljs-comment">//防止重复发射</span>
                <span class="hljs-keyword">if</span>(oldUser == user.value){<span class="hljs-keyword">return</span>;}
                oldUser = user.value;
                socket.emit(<span class="hljs-string">"getUser"</span>,user.value);
            }
        }       
        <span class="hljs-comment">//发送消息</span>
        send.onclick = () =&gt; {
            <span class="hljs-keyword">if</span>(isUserEmpty()){
                <span class="hljs-keyword">let</span> msg = write.value;
                <span class="hljs-comment">// content.innerHTML = content.value + msg + "\n";</span>
                socket.send({msg:msg,person:userList.value});
            }
            <span class="hljs-keyword">if</span>(select.value == <span class="hljs-string">""</span>){
                alert(<span class="hljs-string">"请选择一个聊天对象"</span>);
            }
        };
        <span class="hljs-comment">//接收到消息</span>
        socket.on(<span class="hljs-string">"message"</span>,msg =&gt; {
            console.log(<span class="hljs-string">"从服务器接收到的消息 : "</span> + msg);
            <span class="hljs-comment">//更新内容</span>
            content.innerHTML = content.value + msg + <span class="hljs-string">"\n"</span>;
        });
        socket.on(<span class="hljs-string">"disconnect"</span>,() =&gt; {
            console.log(<span class="hljs-string">"与服务器断开连接"</span>);
        });
        <span class="hljs-comment">//新用户加入聊天室</span>
        socket.on(<span class="hljs-string">"newUser"</span>,arr =&gt; {
            userList.innerHTML = <span class="hljs-string">""</span>;
            <span class="hljs-keyword">let</span> all = document.createElement(<span class="hljs-string">"option"</span>);
            all.innerHTML = <span class="hljs-string">"群聊"</span>;
            all.setAttribute(<span class="hljs-string">"value"</span>,<span class="hljs-string">"all"</span>);
            userList.appendChild(all);
            <span class="hljs-comment">//添加新用户</span>
            arr.forEach((value,index) =&gt; {
                console.log(<span class="hljs-string">"value :"</span> + value + <span class="hljs-string">"index :"</span> + index);
                <span class="hljs-keyword">let</span> option = document.createElement(<span class="hljs-string">"option"</span>);
                option.innerHTML = value;
                option.setAttribute(<span class="hljs-string">"value"</span>,value);
                userList.appendChild(option);
                userList.setAttribute(<span class="hljs-string">"size"</span>,userList.children.length);
            });
            <span class="hljs-comment">//默认选中群聊</span>
            userList.value = <span class="hljs-string">"all"</span>;
        });
        <span class="hljs-comment">//接收服务器需要弹出对话框的需求</span>
        socket.on(<span class="hljs-string">"showDialog"</span>,msg =&gt; {
            alert(msg);
        });
    </span><span class="hljs-tag">&lt;/<span class="hljs-title">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-title">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-title">html</span>&gt;</span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li><li>82</li><li>83</li><li>84</li><li>85</li><li>86</li><li>87</li><li>88</li><li>89</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png"></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li><li>82</li><li>83</li><li>84</li><li>85</li><li>86</li><li>87</li><li>88</li><li>89</li></ul></pre>


<p><img src="https://img-blog.youkuaiyun.com/20170220125251458?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSGFvRGFXYW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title=""></p>


<p>代码的具体我就不在详细讲解,都标有注释,由于只是用于博文,整体代码没有重构优化,大家看不懂的可以回复我,或者有什么地方错误请指出,我会及时改正。</p>


<p>另外在这个聊天室中,当用户刷新频率较快时,websocket会出现伪连接现象。</p>


<p>下面附上我的github地址,大家可以下载我的源码进行修改学习,共勉。 <br>
<a href="https://github.com/HaoDaWang/chat">https://github.com/HaoDaWang/chat</a></p></div>
        <script type="text/javascript">
            $(function () {
                $('pre.prettyprint code').each(function () {
                    var lines = $(this).text().split('\n').length;
                    var $numbering = $('<ul></ul>').addClass('pre-numbering').hide();
                    $(this).addClass('has-numbering').parent().append($numbering);
                    for (i = 1; i <= lines; i++) {
                        $numbering.append($('<li></li>').text(i));
                    };
                    $numbering.fadeIn(1700);
                });
            });
        </script>
   
</div>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值