通过http长连接和http短连接及websocket实现在线即时通讯聊天
聊天室使我们日常生活中经常用到的,比如微信群聊,今天我带大家了解一下通过三种技术手段可以实现聊天室的功能,主要分为HTTP长连接、HTTP短连接、websocket。UDP通讯、TCP通讯,今天我们主要来讲怎么用http长短连接及websocket来实现即时通讯。
一、什么是HTTP长连接、HTTP短连接、websocket
在HTTP/1.0中默认使用http短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。
而从HTTP/1.1起,默认使用长连接,用以保持连接特性,提高传输的效率和减低服务器的开销。使用长连接的HTTP协议,会在响应头加入这行代码:
Connection:keep-alive
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
websocket是html5的新特性,WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
二、http长连接实现即时聊天
主要通过ajax长连接后端php,后端php一直定时监控消息,有新消息就即时反馈给浏览器,重新开始新的长连接,及时性相对比较高。<?php function writefile($file_name, $text) { $handle = fopen($file_name, "a"); if (flock($handle, LOCK_EX)) { fwrite($handle, $text.'\n'); flock($handle, LOCK_UN); } fclose($handle); } $_talklistfile = "../Data/talklist.txt"; if (!file_exists($_talklistfile)) { file_put_contents($_talklistfile, ""); } if (!empty($_POST['action'])) { switch ($_POST['action']) { case "getwords": $_mtime = $_POST['mtime']; $i = 0; while (true) { usleep(500000); //0.5秒 $i++; if ($i >= 40) { echo json_encode(array('error' => true)); exit(); } //清空文件缓存 clearstatcache(); //如果文件修改时间大于用户那边的时间,就说明有更新 $_newmtime = filemtime($_talklistfile); if ($_newmtime > $_mtime || $_mtime == 0) { echo json_encode(array('error' => false, "data" => file_get_contents($_talklistfile), "mtime" => $_newmtime)); exit(); } } break; case "sayword": $_words = $_POST['words']; $_uid = $_POST['uid']; writefile($_talklistfile, '用户'.$_uid." ".date('H:i:s', time())."说".$_words); //写入聊天文件 echo json_encode(array('error' => false)); exit(); break; default: break; } } else { ?> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>ajax长轮询实现聊天</title> <script id="bfwone" data="dep=jquery.17&err=0" type="text/javascript" src="http://repo.bfw.wiki/bfwrepo/js/bfwone.js"></script> <script type="text/javascript"> var mtime = 0; //生成唯一字符串 function randomString(len) { len = len || 32; var $chars = '2345678'; var maxPos = $chars.length; var pwd = ''; for (i = 0; i < len; i++) { pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); } return pwd; } //发送消息 function saywords() { var words = $("#words").val(); if (words == "") { alert('说点啥'); return; } $.ajax({ type: "POST", dataType: "json", url: "/ajaxtalk2.php", data: { action: "sayword", words: words, uid: $("#username").val() }, //40秒后无论结果服务器都返回数据 success: function(ret, textStatus) { if (ret.error == false) { $("#words").val(""); } }, }); } //长连接监听有没有新消息 function listenwords() { $.ajax({ type: "POST", dataType: "json", url: "/ajaxtalk2.php", timeout: 30000, //ajax请求超时时间30秒 data: { action: "getwords", mtime: mtime, }, //40秒后无论结果服务器都返回数据 success: function(ret, textStatus) { if (ret.error == false) { mtime = ret.mtime; $("#msg").html(ret.data.replace(/\\n/g, "</br>")); } listenwords(); //循环监听 }, //Ajax请求超时,继续查询 error: function(XMLHttpRequest, textStatus, errorThrown) { if (textStatus == "timeout") { $("#msg").append("<br>[超时]"); listenwords(); } } }); } bready(function() { $("#username").val(randomString(6)); listenwords(); $("#btn").bind("click", function() { saywords(); }); }); </script> <style> </style> </head> <body> <h1>http长连接实现聊天室</h1> <div id="msg" style="height:300px;border:1px solid grey;"></div> <input type="text" id="username" style="width:50px;" /><input type="text" id="words" placeholder="请输入文字" /> <input id="btn" type="button" value="发送" /> </body> </html> <?php } ?>
三、http短连接实现即时聊天
11短连接主要通过浏览器ajax定时轮训服务器查看是否有新消息,及时性取决于浏览器查询的时间间隔,间隔越短,及时性越高。
<?php function writefile($file_name, $text) { $handle = fopen($file_name, "a"); if (flock($handle, LOCK_EX)) { fwrite($handle, $text.'\n'); flock($handle, LOCK_UN); } fclose($handle); } $_talklistfile = "../Data/talklist.txt"; if (!file_exists($_talklistfile)) { file_put_contents($_talklistfile, ""); } if (!empty($_POST['action'])) { switch ($_POST['action']) { case "getwords": $_mtime = $_POST['mtime']; //清空文件缓存 clearstatcache(); //如果文件修改时间大于用户那边的时间,就说明有更新 $_newmtime = filemtime($_talklistfile); if ($_newmtime > $_mtime || $_mtime == 0) { echo json_encode(array('error' => false, "data" => file_get_contents($_talklistfile), "mtime" => $_newmtime)); } else { echo json_encode(array('error' => true)); } exit(); break; case "sayword": $_words = $_POST['words']; $_uid = $_POST['uid']; writefile($_talklistfile, '用户'.$_uid." ".date('H:i:s', time())."说".$_words); //写入聊天文件 echo json_encode(array('error' => false)); exit(); break; default: break; } } else { ?> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>ajax短轮询实现聊天</title> <script id="bfwone" data="dep=jquery.17&err=0" type="text/javascript" src="http://repo.bfw.wiki/bfwrepo/js/bfwone.js"></script> <script type="text/javascript"> var mtime = 0; //生成唯一字符串 function randomString(len) { len = len || 32; var $chars = '2345678'; var maxPos = $chars.length; var pwd = ''; for (i = 0; i < len; i++) { pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); } return pwd; } //发送消息 function saywords() { var words = $("#words").val(); if (words == "") { alert('说点啥'); return; } $.ajax({ type: "POST", dataType: "json", url: "/ajaxtalk.php", data: { action: "sayword", words: words, uid: $("#username").val() }, //40秒后无论结果服务器都返回数据 success: function(ret, textStatus) { if (ret.error == false) { $("#words").val(""); } }, }); } //短连接定时获取有没有新消息 function getwords() { $.ajax({ type: "POST", dataType: "json", url: "/ajaxtalk.php", timeout: 30000, //ajax请求超时时间30秒 data: { action: "getwords", mtime: mtime, }, //40秒后无论结果服务器都返回数据 success: function(ret, textStatus) { if (ret.error == false) { mtime = ret.mtime; $("#msg").html(ret.data.replace(/\\n/g, "</br>")); } }, //Ajax请求超时,继续查询 error: function(XMLHttpRequest, textStatus, errorThrown) { if (textStatus == "timeout") { $("#msg").append("<br>[超时]"); } } }); } bready(function() { $("#username").val(randomString(6)); setInterval(getwords, 2000); //2秒钟拉一次 $("#btn").bind("click", function() { saywords(); }); }); </script> <style> </style> </head> <body> <h1>http短连接实现聊天室</h1> <div id="msg" style="height:300px;border:1px solid grey;"></div> <input type="text" id="username" style="width:50px;" /><input type="text" id="words" placeholder="请输入文字"/> <input id="btn" type="button" value="发送" /> </body> </html> <?php } ?>
四、websocket实现即时聊天通讯
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>websocket实现聊天室</title> <script id="bfwone" data="dep=jquery.17&err=0" type="text/javascript" src="http://repo.bfw.wiki/bfwrepo/js/bfwone.js"></script> <script type="text/javascript"> var ws = null; //生成唯一字符串 function randomString(len) { len = len || 32; var $chars = '2345678'; var maxPos = $chars.length; var pwd = ''; for (i = 0; i < len; i++) { pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); } return pwd; } //发送消息 function saywords() { var words = $("#words").val(); if (words == "") { alert('说点啥'); return; } var data = JSON.stringify({ "uid": $("#username").val(), "word": words }); ws.send(data); $("#msg").append("我说:"+words+"</br>"); $("#words").val(""); } bready(function() { // 创建一个 websocket 连接 ws = new WebSocket("ws://web.debug.only.bfw.wiki:8089"); // websocket 创建成功事件 ws.onopen = function () { console.log("建立连接成功."); }; // websocket 接收到消息事件 ws.onmessage = function (e) { var ret = JSON.parse(e.data); console.log(ret); $("#msg").append("用户"+ret.uid+"说:"+ret.word+"</br>"); //收到新消息就新增 } // websocket 错误事件 ws.onerror = function () { alert("出错了,请重试."); }; $("#username").val(randomString(6)); $("#btn").bind("click", function() { saywords(); }); }); </script> <style> </style> </head> <body> <h1>websocket实现聊天室</h1> <div id="msg" style="height:300px;border:1px solid grey;"></div> <input type="text" id="username" style="width:50px;" /><input type="text" id="words" placeholder="请输入文字" /> <input id="btn" type="button" value="发送" /> </body> </html>后端我们使用workerman简历websocket,代码如下
<?php use Workerman\Connection\AsyncTcpConnection; use Workerman\Worker; require_once __DIR__ . '/Autoloader.php'; $users = []; // 创建一个Worker监听8088端口,使用websocket协议通讯 $ws_worker = new Worker("websocket://0.0.0.0:8089"); // 启动4个进程对外提供服务 $ws_worker->count = 4; // 当收到客户端发来的数据后 $ws_worker->onConnect=function($connection){ $_key = $connection->getRemoteIp() . $connection->getRemotePort(); global $users; if (!isset($users[$_key])) { $users[$_key] = $connection; } }; // 当收到客户端发来的数据后 $ws_worker->onMessage = function ($connection, $data) { $_key = $connection->getRemoteIp() . $connection->getRemotePort(); global $users; if (!isset($users[$_key])) { $users[$_key] = $connection; } foreach ($users as $key => $user) { if ($key != $_key) { echo "send data"; $user->send($data); } } // 设置连接的onClose回调 $connection->onClose = function ($connection) // 客户端主动关闭 { $_key = $connection->getRemoteIp() . $connection->getRemotePort(); global $users; if (isset($users[$_key])) { unset($users[$_key]); } echo "connection closed\n"; }; }; // 运行worker Worker::runAll();
五、总结
消息接收及时性:http短连接<http长连接<websocket webscket的及时性最高。
资源消耗率:websocket<http短连接<http长连接 http长连接消耗服务器资源最大。
兼容性:websocket<http长短连接 由于websocket是html5新推出的技术规范,老的浏览器不支持,所以兼容性不是太好
三个技术方式demp例子代码的WEBIDE地址http://studio.bfw.wiki/Studio/Open.html?projectid=15771671791332100010,点开后直接运行和编辑代码,无需下载
网友评论0