因为本人的工作需要,偶尔被要求实现一些市面上已经有的成熟接口功能。这里要转折一下,不是说我实现的功能的稳定性和成熟度已经达到了可以商用的标准,只是被用作一个给客户展示的demo而已,有点小尴尬。

进入正题吧,用web实现im功能,目前主流的解决方案总结为如下两点:

1.使用服务器轮询技术实现。

2.使用websocket技术实现。

恰巧以上两种方式,我都没研究过,轮询技术大致懂,但是这样做容易徒废服务器资源,并且公司提供的demo服务器配置也很low,我怕给客户演示的时候,它宕掉了,我特么的工作也就被宕掉了。再说到websocket,这个基本是市面上我了解的webim接扣的技术基础,看了看websocket的php(对,我特么也是个光荣的php开发者~php是世界上最好的语言~轻喷)服务端转发代码,有点耽搁时间,遂抛弃了它。

我百度了一圈,可悲的发现,没有一个是对我有用的,可能说是没有一个在我想耗费的时间内完成。这个时候大多牛逼程序员会灵光乍现,然后手速飞快的自己敲出了一套市面上没有的新技术,看到这里,你们就懂了,我特么不是这样的程序员,好吧,在我放弃了这些技术后,我想起来以前给一个电力能效的项目用mqtt(不懂mqtt的同学可以自行百度下)挂过长连接,实时反馈电力设备数据到web管理端,反正大家都是长连接,我用来干干,也是不碍事的吧。

一、准备工作:

1.客户端:mqttws31.min.js mqtt官方给的js客户端。

2.服务端:phpMQtt.php mqttphp端脚本。

recive.php 接收信息,实例化mqtt进行转发脚本。

mqttws31.min.js可以再mqtt官网进行下载,php端代码在这里粘贴出来,需要用的可以自取(因为我也是借鉴别人的)

<?php/*phpMQTTA simple php class to connect/publish/subscribe to an MQTT broker*//*LicenceCopyright (c) 2010 Blue Rhinos Consulting | Andrew Milstedandrew@bluerhinos.co.uk | http://www.bluerhinos.co.ukPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included inall copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS INTHE SOFTWARE.*//* phpMQTT */
class phpMQTT {private $socket;             /* holds the socket */private $msgid = 1;          /* counter for message id */public $keepalive = 10;        /* default keepalive timmer */public $timesinceping;        /* host unix time, used to detect disconects */public $topics = array();   /* used to store currently subscribed topics */public $debug = false;      /* should output debug messages */public $address;          /* broker address */public $port;               /* broker port */public $clientid;          /* client id sent to brocker */public $will;                /* stores the will of the client */private $username;           /* stores username */private $password;         /* stores password */function __construct($address, $port, $clientid){$this->broker($address, $port, $clientid);}/* sets the broker details */function broker($address, $port, $clientid){$this->address = $address;$this->port = $port;$this->clientid = $clientid;     }function connect_auto($clean = true, $will = NULL, $username = NULL, $password = NULL){while($this->connect($clean, $will, $username, $password)==false){sleep(10);}return true;}/* connects to the broker inputs: $clean: should the client send a clean session flag */function connect($clean = true, $will = NULL, $username = NULL, $password = NULL){if($will) $this->will = $will;if($username) $this->username = $username;if($password) $this->password = $password;$address = gethostbyname($this->address);    $this->socket = fsockopen($address, $this->port, $errno, $errstr, 60);if (!$this->socket ) {if($this->debug) error_log("fsockopen() $errno, $errstr \n");return false;}stream_set_timeout($this->socket, 5);stream_set_blocking($this->socket, 0);$i = 0;$buffer = "";$buffer .= chr(0x00); $i++;$buffer .= chr(0x06); $i++;$buffer .= chr(0x4d); $i++;$buffer .= chr(0x51); $i++;$buffer .= chr(0x49); $i++;$buffer .= chr(0x73); $i++;$buffer .= chr(0x64); $i++;$buffer .= chr(0x70); $i++;$buffer .= chr(0x03); $i++;//No Will$var = 0;if($clean) $var+=2;//Add will info to headerif($this->will != NULL){$var += 4; // Set will flag$var += ($this->will['qos'] << 3); //Set will qosif($this->will['retain']) $var += 32; //Set will retain}if($this->username != NULL) $var += 128;  //Add username to headerif($this->password != NULL) $var += 64;   //Add password to header$buffer .= chr($var); $i++;//Keep alive$buffer .= chr($this->keepalive >> 8); $i++;$buffer .= chr($this->keepalive & 0xff); $i++;$buffer .= $this->strwritestring($this->clientid,$i);//Adding will to payloadif($this->will != NULL){$buffer .= $this->strwritestring($this->will['topic'],$i);  $buffer .= $this->strwritestring($this->will['content'],$i);}if($this->username) $buffer .= $this->strwritestring($this->username,$i);if($this->password) $buffer .= $this->strwritestring($this->password,$i);$head = "  ";$head{0} = chr(0x10);$head{1} = chr($i);fwrite($this->socket, $head, 2);fwrite($this->socket,  $buffer);$string = $this->read(4);if(ord($string{0})>>4 == 2 && $string{3} == chr(0)){if($this->debug) echo "Connected to Broker\n"; }else{    error_log(sprintf("Connection failed! (Error: 0x%02x 0x%02x)\n", ord($string{0}),ord($string{3})));return false;}$this->timesinceping = time();return true;}/* read: reads in so many bytes */function read($int = 8192, $nb = false){//    print_r(socket_get_status($this->socket));$string="";$togo = $int;if($nb){return fread($this->socket, $togo);}while (!feof($this->socket) && $togo>0) {$fread = fread($this->socket, $togo);$string .= $fread;$togo = $int - strlen($string);}return $string;}/* subscribe: subscribes to topics */function subscribe($topics, $qos = 0){$i = 0;$buffer = "";$id = $this->msgid;$buffer .= chr($id >> 8);  $i++;$buffer .= chr($id % 256);  $i++;foreach($topics as $key => $topic){$buffer .= $this->strwritestring($key,$i);$buffer .= chr($topic["qos"]);  $i++;$this->topics[$key] = $topic; }$cmd = 0x80;//$qos$cmd +=  ($qos << 1);$head = chr($cmd);$head .= chr($i);fwrite($this->socket, $head, 2);fwrite($this->socket, $buffer, $i);$string = $this->read(2);$bytes = ord(substr($string,1,1));$string = $this->read($bytes);}/* ping: sends a keep alive ping */function ping(){$head = " ";$head = chr(0xc0);        $head .= chr(0x00);fwrite($this->socket, $head, 2);if($this->debug) echo "ping sent\n";}/* disconnect: sends a proper disconect cmd */function disconnect(){$head = " ";$head{0} = chr(0xe0);      $head{1} = chr(0x00);fwrite($this->socket, $head, 2);}/* close: sends a proper disconect, then closes the socket */function close(){$this->disconnect();fclose($this->socket);    }/* publish: publishes $content on a $topic */function publish($topic, $content, $qos = 0, $retain = 0){$i = 0;$buffer = "";$buffer .= $this->strwritestring($topic,$i);//$buffer .= $this->strwritestring($content,$i);if($qos){$id = $this->msgid++;$buffer .= chr($id >> 8);  $i++;$buffer .= chr($id % 256);  $i++;}$buffer .= $content;$i+=strlen($content);$head = " ";$cmd = 0x30;if($qos) $cmd += $qos << 1;if($retain) $cmd += 1;$head{0} = chr($cmd);       $head .= $this->setmsglength($i);fwrite($this->socket, $head, strlen($head));fwrite($this->socket, $buffer, $i);}/* message: processes a recieved topic */function message($msg){$tlen = (ord($msg{0})<<8) + ord($msg{1});$topic = substr($msg,2,$tlen);$msg = substr($msg,($tlen+2));$found = 0;foreach($this->topics as $key=>$top){if( preg_match("/^".str_replace("#",".*",str_replace("+","[^\/]*",str_replace("/","\/",str_replace("$",'\$',$key))))."$/",$topic) ){if(is_callable($top['function'])){call_user_func($top['function'],$topic,$msg);$found = 1;}}}if($this->debug && !$found) echo "msg recieved but no match in subscriptions\n";}/* proc: the processing loop for an "allways on" client set true when you are doing other stuff in the loop good for watching something else at the same time */  function proc( $loop = true){if(1){$sockets = array($this->socket);$w = $e = NULL;$cmd = 0;//$byte = fgetc($this->socket);if(feof($this->socket)){if($this->debug) echo "eof receive going to reconnect for good measure\n";fclose($this->socket);$this->connect_auto(false);if(count($this->topics))$this->subscribe($this->topics);    }$byte = $this->read(1, true);if(!strlen($byte)){if($loop){usleep(100000);}}else{ $cmd = (int)(ord($byte)/16);if($this->debug) echo "Recevid: $cmd\n";$multiplier = 1; $value = 0;do{$digit = ord($this->read(1));$value += ($digit & 127) * $multiplier; $multiplier *= 128;}while (($digit & 128) != 0);if($this->debug) echo "Fetching: $value\n";if($value)$string = $this->read($value,"fetch");if($cmd){switch($cmd){case 3:$this->message($string);break;}$this->timesinceping = time();}}if($this->timesinceping < (time() - $this->keepalive )){if($this->debug) echo "not found something so ping\n";$this->ping();    }if($this->timesinceping<(time()-($this->keepalive*2))){if($this->debug) echo "not seen a package in a while, disconnecting\n";fclose($this->socket);$this->connect_auto(false);if(count($this->topics))$this->subscribe($this->topics);}}return 1;}/* getmsglength: */function getmsglength(&$msg, &$i){$multiplier = 1; $value = 0 ;do{$digit = ord($msg{$i});$value += ($digit & 127) * $multiplier; $multiplier *= 128;$i++;}while (($digit & 128) != 0);return $value;}/* setmsglength: */function setmsglength($len){$string = "";do{$digit = $len % 128;$len = $len >> 7;// if there are more digits to encode, set the top bit of this digitif ( $len > 0 )$digit = ($digit | 0x80);$string .= chr($digit);}while ( $len > 0 );return $string;}/* strwritestring: writes a string to a buffer */function strwritestring($str, &$i){$ret = " ";$len = strlen($str);$msb = $len >> 8;$lsb = $len % 256;$ret = chr($msb);$ret .= chr($lsb);$ret .= $str;$i += ($len+2);return $ret;}function printstr($string){$strlen = strlen($string);for($j=0;$j<$strlen;$j++){$num = ord($string{$j});if($num > 31) $chr = $string{$j}; else $chr = " ";printf("%4d: %08b : 0x%02x : %s \n",$j,$num,$num,$chr);}}
}?>

以上是mqttphp服务端的代码。

二、思路详解

说了这么多,萌新同学可能有点疑惑,这里为萌新同学们讲解下思路。

百度百科的mqtt有6大特性,最主要也是我们最必须了解的特性就是:使用发布/订阅消息模式,提供一对多的消息模式

这个怎么解释呢?,微信玩过吧,里面不是有很多公众号嘛,假设你是A,你朋友是B,有个公众号C,A和B都订阅了C,此时C在微信后台发布文章,A和B都能接收到。

而我们要实现的IM的思路是:每个人都订阅自己,什么意思呢?就是A订阅A,B订阅B,C订阅C,当A要给B发送消息的时候,A从客户端使用ajax给服务器异步一条数据,例如:

msg = {to  : "B",con : "你好啊",from : "A",time : 2015698856
}

msg里面记录了接受者(to),发送内容(con),发送者(from),发送时间戳(time),当服务端接收到消息后,对消息进行解析,获取到to后对B进行消息发送,例如

include_once("phpMQTT.php");
$clientid = mt_rand(0,10000);
$mqtt = new phpMQTT("127.0.0.1", 1883, $clientid."a"); //127.0.0.1是连接本机,1883端口为服务端mqtt默认代理端口,$clinetid不唯一即可
if ($mqtt->connect(true, NULL,username, password)) { //mqtt服务进行连接,username,password只要跟客户端设置一致即可$mqtt->publish($to,json_encode($msg), 0); //$to就是解析到的to,此时就是B,$msg就是原信息$mqtt->close();$msg = '发送成功';
} else {$msg = '连接失败';
}

B登陆客户端后,用mqttjs端对自己进行订阅,然后将接受到的信息进行解析:例如

function subscribe(user){ //开启mqtt客户端,并订阅自己,user为自己var client = new Paho.MQTT.Client("服务端地址/IP或者域名", Number(9001), uuid); //web客户端开启的端口必须是9001,每个客户端的uuid必须要不一样client.connect({onSuccess:function(){console.log("connect success");client.subscribe(user);//接收订阅的主题},onFailure:function() {}, timeout:100,userName:'xxxx', //mqtt服务端连接验证,用户名密码只要与服务端设置相同即可password:'xxxx' //mqtt服务端连接验证,用户名密码只要与服务端设置相同即可});client.onMessageArrived = function(message) {console.log(message); //消息接收到的处理}client.onConnectionLost = function(responseObject) {console.log(responseObject) //连接断开的处理}
}

一条完整的路线走了下来,一个简易的Im就做出来了,此时还只是文本聊天,如果你想进行表情,图片,语音,地图,请移步我下一篇文章。

严重警告:一个客户端最好只开一个mqtt,如果开多了,就会莫名的崩溃掉,尽量使用逻辑手段去实现功能,而不要呆板的以数量来实现。

三、效果展示:

视频连接地址:

点击打开链接

漫漫的webim(一) web实现简易im功能相关推荐

  1. 智慧零售erp通用版管理系统+门店管理+商品管理+厂商管理+财务管理+销售管理+仓储管理+Axure高保真交互ERP通用版零售行业web端简易版管理系统

    作品介绍:智慧零售erp通用版管理系统+门店管理+商品管理+厂商管理+财务管理+销售管理+仓储管理+Axure高保真交互ERP通用版零售行业web端简易版管理系统 原型交互及下载请点击:https:/ ...

  2. web图书销售管理系统_Java Web实现简易的图书管理系统

    Java Web实现简易的图书管理系统 这是一个使用Java Web相关的知识做出来的网页图书管理系统,使用的数据库为mysql. 程序中实现了登录功能和对图书表.图书类别表的增删查改功能. 因为我对 ...

  3. web制作简易百度网页

    web制作简易百度网页 代码如下: <!-- edu_5_4_1.html --> <body> <p align="center"><a ...

  4. web安全简易规范123

    web安全,大公司往往有专门的安全开发流程去保证,有专门的安全团队去维护,而对于中小网络公司,本身体量小,开发同时兼带运维工作,时间精力有限,但是,同样需要做一些力所能及的必要的事情.有时候,安全威胁 ...

  5. 使用Go内置库实现简易httpbin功能

    简介 通过学习「Go语言圣经」的入门部分,了解到 go 内置库里提供了一个简单的 http 功能.于是想模拟下httpbin[1]的 get 方法显示 header 头信息的功能来练手. 本人 Go ...

  6. python表达式计算器_Python正则表达式实现简易计算器功能示例

    本文实例讲述了Python正则表达式实现简易计算器功能.分享给大家供大家参考,具体如下: 需求:使用正则表达式完成一个简易计算器. 功能:能够计算简单的表达式. 如:1*2*((1+2)/(2+3)+ ...

  7. 让jQuery Tools Scrollable控件在Mobile Web里面支持resize功能

    让jQuery Tools Scrollable控件在Mobile Web里面支持resize功能 项目中有两份代码,一份是Main Site,一份是Mobile Site.Main Site里面主页 ...

  8. vue怎么获取用户的位置经纬度_vue 实现Web端的定位功能 获取经纬度

    首先我这里的需求呢, 是获取当前用户的经纬度 经过无数次的测试, 先后用了 腾讯/百度地图的api,最后绝对还是高德的js APi 废话不多说, 直接上代码. 首先在 index.html 里面 引入 ...

  9. web安全检查_如何利用现代Web检查器的功能

    web安全检查 by Craig Fitzpatrick 克雷格·菲茨帕特里克(Craig Fitzpatrick) 如何利用现代Web检查器的功能 (How to leverage the powe ...

  10. pythontkinter做计算器_Python Tkinter实现简易计算器功能

    闲暇时间用tkinter写了个简易计算器,可实现简单的加减乘除运算,用了Button和Entry2个控件,下面是代码,只是简单的用了偏函数partial,因为那么多button的大部分参数都是一样的, ...

最新文章

  1. 成功解决OpenCV Error: Assertion failed (scn == 3 || scn == 4) in cv::cvtColor
  2. 好习惯!pandas 8 个常用的 index 设置
  3. 最简单的基于FFmpeg的编码器-纯净版(不包含libavformat)
  4. 关于sqlserver中xml数据的操作
  5. 前端类名优秀命名例子_这是一篇需要花费你15分钟阅读的干货!浅谈前端工程化...
  6. 洛谷——P1001 A+B Problem
  7. kylin通过API增量build
  8. Java面试题及答案整理(2021最新版)
  9. windows11中文语言包|windows11中文汉化包
  10. 免费赠品发布:Wintry Blue Wallpaper
  11. 关于欧拉四面体公式的推导及证明过程
  12. TeamViewer作为个人用途免费,但仅可使用在有限数量的设备上。您已经到达可使用设备的上线
  13. 迁移学习癌医学影像检测
  14. Copy and Paste GAN: Face Hallucination from Shaded Thumbnails
  15. html多行注释如何实现,javascript多行注释如何实现
  16. 商家商品上架流程(没有)
  17. CAD图纸导入REVIT内并精准建模
  18. 2190: 【USACO】Farmer John has no Large Brown Cow
  19. ILSpy反编译工具的介绍
  20. 解决win10无法远程连接公司服务器

热门文章

  1. java游戏实例_Java游戏俄罗斯方块的实现实例
  2. 跳跃游戏 改 dfs
  3. Android桌面插件的开发
  4. 美国纽约大学超级计算机中心,美国纽约最好的八所大学介绍
  5. 微型计算机鸡兔同笼,奥数鸡兔同笼问题
  6. 【JY】STKO助力OpenSEES系列:结构模态分析以及动力特性(MDOF与等效SDOF验证)
  7. 8款精致的纯CSS3按钮特效
  8. Win7 32 不能安装STM32 虚拟串口驱动解决方法
  9. 群晖NAS教程(二十三)、利用Docker安装mysql8,并使用ipv6和域名访问
  10. allegro怎么修改文本_allegro user preferences常见设置及说明