老雷socket编程之websocket实现

我们主要实现私聊和群聊两个功能,要在web端实现想微信QQ那样的即时通讯的功能,我们需要了解一下websocket。
websocket是一种可以双向通讯的长连接协议,http是获取完数据就关闭,websocket则可以一直连接,就像铺了一条管道一样,水可以一直流着。

一、websocket前端

    var ws = new WebSocket("ws://127.0.0.1.com:8282");
ws.onopen=function(){
var msg = JSON.stringify({
type: "login",
content: "login"
});
ws.send(msg);
} ws.onmessage = function (e){
console.log(e);
//服务器发送的内容
var res = JSON.parse(e.data);
switch(res.type){
case "login": break;
case "pm": break;
case "groupPm": break; }
}
ws.onerror=function (e){
console.log(e);
}
ws.onclose=function (e){
console.log(e);
}

二、服务端


客户端发送http请求,带上Sec-WebSocket-Key,
服务端握手 加密key,发送给客户端。
双方能进行交流。

发送接收消息需要进行打包encode 解包decode。

<?php

class SocketService
{
public $host="tcp://0.0.0.0:8000";
private $address;
private $port;
private $_sockets;
public $clients;
public $maxid=1000;
public function __construct($address = '', $port='')
{
if(!empty($address)){
$this->address = $address;
}
if(!empty($port)) {
$this->port = $port;
}
} public function onConnect($client_id){
echo "Client client_id:{$client_id} \n"; } public function onMessage($client_id,$msg){
//发给所有的
foreach($this->clients as $kk=>$cc){
if($kk>0){
$this->send($cc, $msg);
}
}
} public function onClose($client_id){
echo "$client_id close \n";
} public function service(){
//获取tcp协议号码。
$tcp = getprotobyname("tcp");
$sock = stream_socket_server($this->host, $errno, $errstr);; if(!$sock)
{
throw new Exception("failed to create socket: ".socket_strerror($sock)."\n");
}
stream_set_blocking($sock,0);
$this->_sockets = $sock;
echo "listen on $this->address $this->host ... \n";
} public function run(){
$this->service();
$this->clients[] = $this->_sockets;
while (true){
$changes = $this->clients;
//$write = NULL;
//$except = NULL;
stream_select($changes, $write, $except, NULL);
foreach ($changes as $key => $_sock){
if($this->_sockets == $_sock){ //判断是不是新接入的socket
if(($newClient = stream_socket_accept($_sock)) === false){
unset($this->clients[$key]);
continue;
}
$line = trim(stream_socket_recvfrom($newClient, 1024));
//握手
$this->handshaking($newClient, $line);
$this->maxid++;
$this->clients[$this->maxid] = $newClient;
$this->onConnect($this->maxid);
} else {
$res=@stream_socket_recvfrom($_sock, 2048);
//客户端主动关闭
if(strlen($res) < 9) {
stream_socket_shutdown($this->clients[$key],STREAM_SHUT_RDWR);
unset($this->clients[$key]);
$this->onClose($key);
}else{
//解密
$msg = $this->decode($res);
$this->onMessage($key,$msg);
} }
}
}
} /**
* 握手处理
* @param $newClient socket
* @return int 接收到的信息
*/
public function handshaking($newClient, $line){ $headers = array();
$lines = preg_split("/\r\n/", $line);
foreach($lines as $line)
{
$line = chop($line);
if(preg_match('/\A(\S+): (.*)\z/', $line, $matches))
{
$headers[$matches[1]] = $matches[2];
}
}
$secKey = $headers['Sec-WebSocket-Key'];
$secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
$upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"WebSocket-Origin: $this->address\r\n" .
"WebSocket-Location: ws://$this->address:$this->port/websocket/websocket\r\n".
"Sec-WebSocket-Accept:$secAccept\r\n\r\n";
return stream_socket_sendto($newClient, $upgrade);
} /**
* 发送数据
* @param $newClinet 新接入的socket
* @param $msg 要发送的数据
* @return int|string
*/
public function send($newClinet, $msg){
$msg = $this->encode($msg);
stream_socket_sendto($newClinet, $msg);
}
/**
* 解析接收数据
* @param $buffer
* @return null|string
*/
public function decode($buffer){
$len = $masks = $data = $decoded = null;
$len = ord($buffer[1]) & 127;
if ($len === 126) {
$masks = substr($buffer, 4, 4);
$data = substr($buffer, 8);
} else if ($len === 127) {
$masks = substr($buffer, 10, 4);
$data = substr($buffer, 14);
} else {
$masks = substr($buffer, 2, 4);
$data = substr($buffer, 6);
}
for ($index = 0; $index < strlen($data); $index++) {
$decoded .= $data[$index] ^ $masks[$index % 4];
}
return $decoded;
} /**
*打包消息
**/
public function encode($buffer) {
$first_byte="\x81";
$len=strlen($buffer);
if ($len <= 125) {
$encode_buffer = $first_byte . chr($len) . $buffer;
} else {
if ($len <= 65535) {
$encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer;
} else {
$encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer;
}
}
return $encode_buffer;
} /**
* 关闭socket
*/
public function close(){
return socket_close($this->_sockets);
}
} $sock = new SocketService('127.0.0.1','9000');
$sock->run();

三、常见应用

1.聊天室、群聊 实现类似QQ群的web版本

2.im私聊、客服 实现类似qq聊天,和即时客服交流

3.消息推送 建立即时的web消息推送

课后练习
实现聊天室 跟 个人聊天
前端格式

var msg = JSON.stringify({
type: "login",
content: "login"
});
var msg = JSON.stringify({
type: "group",
content: "login",
gid:123
}); var msg = JSON.stringify({
type: "pm",
content: "login",
uid:123
});

老雷socket编程之websocket实现的更多相关文章

  1. 老雷socket编程之PHP利用socket扩展实现聊天服务

    老雷socket编程之PHP利用socket扩展实现聊天服务 socket聊天服务原理 PHP有两个socket的扩展 sockets和streamssockets socket_create(AF_ ...

  2. 老雷socket编程之认识常用协议

    老雷socket编程之常见网络协议 1.ip IP协议是将多个包交换网络连接起来,它在源地址和目的地址之间传送一种称之为数据包的东西, 它还提供对数据大小的重新组装功能,以适应不同网络对包大小的要求. ...

  3. [深入浅出WP8.1(Runtime)]Socket编程之UDP协议

    13.3 Socket编程之UDP协议 UDP协议和TCP协议都是Socket编程的协议,但是与TCP协议不同,UDP协议并不提供超时重传,出错重传等功能,也就是说其是不可靠的协议.UDP适用于一次只 ...

  4. iPhone socket 编程之BSD Socket篇

    iPhone socket 编程之BSD Socket篇 收藏在进行iPhone网络通讯程序的开发中,不可避免的要利用Socket套接字.iPhone提供了Socket网络编程的接口CFSocket, ...

  5. PHP Socket 编程之9个主要函数的使用之测试案例

    php的socket编程算是比较难以理解的东西吧,不过,我们只要理解socket几个函数之间的关系,以及它们所扮演的角色,那么理解起来应该不是很难了,在笔者看来,socket编程,其实就是建立一个网络 ...

  6. Linux系统编程(33)—— socket编程之TCP程序的错误处理

    上一篇的例子不仅功能简单,而且简单到几乎没有什么错误处理,我们知道,系统调用不能保证每次都成功,必须进行出错处理,这样一方面可以保证程序逻辑正常,另一方面可以迅速得到故障信息. 为使错误处理的代码不影 ...

  7. Python socket编程之二:【struct.pack】&【struct.unpack】

    import struct """通过 socket 的 send 和 recv 只能传输 str 格式的数据""" "" ...

  8. Linux系统编程(37)—— socket编程之UDP服务器与客户端

    典型的UDP客户端/服务器通讯过程: 编写UDP Client程序的步骤 1.初始化sockaddr_in结构的变量,并赋值.这里使用"8888"作为连接的服务程序的端口,从命令行 ...

  9. Linux系统编程(35)—— socket编程之TCP服务器的并发处理

    我们知道,服务器通常是要同时服务多个客户端的,如果我们运行上一篇实现的server和client之后,再开一个终端运行client试试,新的client就不能能得到服务了.因为服务器之支持一个连接. ...

随机推荐

  1. MySQL于ON DUPLICATE KEY UPDATE采用

    今天我们做的推断插入用途MySQL于ON DUPLICATE KEY UPDATE.现在,Mark下面! 假设你想做的事,再有就是在数据库中插入数据没有数据.如果有数据更新数据,然后你可以选择ON D ...

  2. 陈硕 - Linux 多线程服务端编程 - muduo 网络库作者

    http://chenshuo.com/book/ Muduo网络库源码分析(一) EventLoop事件循环(Poller和Channel)http://blog.csdn.net/nk_test/ ...

  3. 计时器timer的使用

    https://www.cnblogs.com/ILoveSleep/archive/2013/06/12/3133322.html

  4. .Net中使用数据库(sqlite)的大体流程(简单向)

    说来数据库,各种语言各种数据库在操作上大体无异,基本都是连接数据库.操作数据库.关闭数据库连接的流程,不过Sqlite由于是单文件数据库,相比其他服务器的数据库连接更简单,只需要给定数据库文件的路径即 ...

  5. ELINK编程器能用来做什么

    以前   产品量产与测试的时候,在电脑上用JATG/SWD编程器或串口下载器等工具下载程序到产品中,效率低且操作复杂 现在  可以用ELINK脱机编程器来摆脱电脑并降低操作复杂度,只需把程序文件下载到 ...

  6. p标签内不能嵌套div(注解)

    相关知识: 内联元素可以嵌套内联元素,块级元素可以嵌套部分块级元素并也能嵌套内联元素,但内联元素不能嵌套块级元素.块级元素为block,内联元素为inline,拥有“inline”特性的同时又拥有“b ...

  7. SQL Server 命名实例更改端口进行发布订阅

    原文:SQL Server 命名实例更改端口进行发布订阅 两台数据库服务器,都没有加入域,都安装多实例,端口也不一样了.现在使用命名实例进行复制,折腾了好久,才发现解决方法. 服务器A:myserve ...

  8. 关于Tiff图片的编解码

    TiffBitmapEncoder 类 (System.Windows.Media.Imaging)https://msdn.microsoft.com/zh-cn/library/ms635161( ...

  9. 用JavaScriptSerializer解析JSON

    引用System.Web.Extensions using System.Web.Script.Serialization; var serializer = new JavaScriptSerial ...

  10. C++ 标准库概览(一分钟就看完了)

    C++ 标准库以若干头文件的方式提供. 下面简单介绍一个各头文件的内容. 第一部分 容器 Containers <array> C++11 新增.提供了容器类模板 std::array,固 ...