写了websocket个聊天室,然后终于弄懂了php的socket

原文网址:http://www.jnecw.com/p/1523

要理解socket就要先理解http和tcp的区别,简单说就是一个是短链,一个是长链,一个是去服务器拉数据,一个是服务器可以主动推数据。

而socket就是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。-来自网络。

那么如何用php+js做到服务器推呢?

客户端

客户端非常简单,利用现代浏览器的WebSocket API,这里介绍的很详细:http://msdn.microsoft.com/zh-cn/library/ie/hh673567

核心代码:

JAVASCRIPT

1
2
3
4
5
var wsServer = ‘ws://127.0.0.1:8080‘;
var ws = new WebSocket(wsServer);
ws.onmessage = function (evt) {
    do sth
};

前两行会向指定服务器发送一个握手请求,如果服务器返回合法的http头,则握手成功,之后可通过监听onmessage事件来处理服务器发来的消息。还有很多其他事件可监听,见前面的url。

服务器

思路

难点是服务器,没有了apache和nginx这些http服务器在前面顶着,只用php该怎么写?

这里有个教程讲的很深入http://blog.csdn.net/shagoo/article/details/6396089

写之前捋一捋思路:

1 监听:首先要挂起一个进程来监听来自客户端的请求
2 握手:对于第一次合法的请求,发送合法的header回去
3 保持连接:有新消息到了就广播出去。直到客户端断开
4 接受另一个请求,重复2和3

关键代码如下:

PHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public function start_server() {
    $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    //允许使用本地地址
    socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, TRUE);
    socket_bind($this->socket, $this->host, $this->port);
    //最多10个人连接,超过的客户端连接会返回WSAECONNREFUSED错误
    socket_listen($this->socket, $this->maxuser);
    while(TRUE) {
        $this->cycle = $this->accept;
        $this->cycle[] = $this->socket;
        //阻塞用,有新连接时才会结束
        socket_select($this->cycle, $write, $except, null);
        foreach ($this->cycle as $k => $v) {
            if($v === $this->socket) {
                if (($accept = socket_accept($v)) < 0) {
                    continue;
                }
                //如果请求来自监听端口那个套接字,则创建一个新的套接字用于通信
                $this->add_accept($accept);
                continue;
            }
            $index = array_search($v, $this->accept);
            if ($index === NULL) {
                continue;
            }
            if ([email protected]_recv($v, $data, 1024, 0) || !$data) {//没消息的socket就跳过
                $this->close($v);
                continue;
            }
            if (!$this->isHand[$index]) {
                $this->upgrade($v, $data, $index);
                if(!empty($this->function[‘add‘])) {
                    call_user_func_array($this->function[‘add‘], array($this));
                }
                continue;
            }
            $data = $this->decode($data);
            if(!empty($this->function[‘send‘])) {
                call_user_func_array($this->function[‘send‘], array($data, $index, $this));
            }
        }
        sleep(1);
    }
}
//增加一个初次连接的用户
private function add_accept($accept) {
    $this->accept[] = $accept;
    $index = array_keys($this->accept);
    $index = end($index);
    $this->isHand[$index] = FALSE;
}
//关闭一个连接
private function close($accept) {
    $index = array_search($accept, $this->accept);
    socket_close($accept);
    unset($this->accept[$index]);
    unset($this->isHand[$index]);
    if(!empty($this->function[‘close‘])) {
        call_user_func_array($this->function[‘close‘], array($this));
    }
}
//响应升级协议
private function upgrade($accept, $data, $index) {
    if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$data,$match)) {
        $key = base64_encode(sha1($match[1] . ‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11‘, true));
        $upgrade  = "HTTP/1.1 101 Switching Protocol\r\n" .
                "Upgrade: websocket\r\n" .
                "Connection: Upgrade\r\n" .
                "Sec-WebSocket-Accept: " . $key . "\r\n\r\n";  //必须以两个回车结尾
        socket_write($accept, $upgrade, strlen($upgrade));
        $this->isHand[$index] = TRUE;
    }
}

关键地方有那么几个,一是while(true)挂起进程,不然执行一次后进程就退出了。二是socket_select和socket_accept函数的使用。三是客户端第一次请求时握手。

socket_select

这个函数是同时接受多个连接的关键,我的理解它是为了阻塞程序继续往下执行和自动选择当前有活动的连接。

socket_select ($sockets, $write = NULL, $except = NULL, NULL);

$sockets可以理解为一个数组,这个数组中存放的是文件描述符。当它有变化(就是有新消息到或者有客户端连接/断开)时,socket_select函数才会返回,继续往下执行。
$write是监听是否有客户端写数据,传入NULL是不关心是否有写变化。
$except是$sockets里面要被排除的元素,传入NULL是”监听”全部。
最后一个参数是超时时间
如果为0:则立即结束
如果为n>1: 则最多在n秒后结束,如遇某一个连接有新动态,则提前返回
如果为null:如遇某一个连接有新动态,则返回

为了理解,dump测试一下:

PHP

1
2
3
4
5
$this->cycle = $this->accept;
$this->cycle[] = $this->socket;
var_dump($this->cycle);//array(n),n>=1
socket_select($this->cycle, $write, $except, null);//有活动后继续往下
var_dump($this->cycle);//array(0),n==0

这一测就完全明白了,socket_select之前把所有的socket连接都丢进去给它,其中一个有活动时它就把那个连接抛出来给我们用。表达能力有限,大概就是这么个意思。。。

socket_accept

此函数接受唯一参数,即前面socket_create创建的socket文件(句柄)。返回一个新的资源,或者FALSE。本函数将会通知socket_listen(),将会传入一个连接的socket资源。一旦成功建立socket连接,将会返回一个新的socket资源,用于通信。如果有多个socket在队列中,那么将会先处理第一个。关键就是这里:如果没有socket连接,那么本函数将会等待,直到有新socket进来。

如果前面不用socket_select在没有socket的时候阻塞住程序,那么就卡在这里永远无法结束了。

后面的流程就很清晰了,当有一个新的客户端请求到达,用socket_accept创建一个资源,并加入到$this->accept连接池里面。并将它的标示isHand设为false,那么下次循环(因为$this->cycle[] = $this->socket;$this->cycle有变化,所以socket_select会返回)的时候就会执行upgrade握手。然后等待它的新消息即可。

程序经调试可以成功运行,php5.3+websocket13。

有兴趣的同学可以下载:文件地址

时间: 2024-08-25 16:57:52

写了websocket个聊天室,然后终于弄懂了php的socket的相关文章

基于django channel 实现websocket的聊天室

websocket ? 网易聊天室? ? web微信? ? 直播? 假如你工作以后,你的老板让你来开发一个内部的微信程序,你需要怎么办?我们先来分析一下里面的技术难点 消息的实时性? 实现群聊 现在有这样一个需求,老板给到你了,关乎你是否能转正?你要怎么做? 我们先说消息的实时性,按照我们目前的想法是我需要用http协议来做,那么http协议怎么来做那? 是不是要一直去访问我们的服务器,问服务器有没有人给我发消息,有没有人给我发消息?那么大家认为我多长时间去访问一次服务比较合适那? 1分钟1次?

nodejs+websocket制作聊天室视频教程

本套教程主要讲解了node平台的安装,node初级知识.node 服务器端程序响应http请求,通过npm安装第三方包,websocket即时通讯.聊天页面界面制作.拖动原理.拖动效果.遮罩效果.定位和浮动.滚动条滚动高度设置.用户进入与离开聊天室提示.当前在线人数的即时统计和显示.以及群聊和私聊两大聊天功能.本套教程js代码稍微有点多,对0基础的初学者可能会有一定的难度,希望通过本套教程的学习,让大家认识nodejs,感受用js写服务器端程序的乐趣. 本教程是高清完整版视频教程. 技术咨询和交

Spring Boot2 系列教程 (十七) | 整合 WebSocket 实现聊天室

微信公众号:一个优秀的废人.如有问题,请后台留言,反正我也不会听. 前言 昨天那篇介绍了 WebSocket 实现广播,也即服务器端有消息时,将消息发送给所有连接了当前 endpoint 的浏览器.但这无法解决消息由谁发送,又由谁接收的问题.所以,今天写一篇实现一对一的聊天室. 今天这一篇建立在昨天那一篇的基础之上,为便于更好理解今天这一篇,推荐先阅读:「SpringBoot 整合WebSocket 实现广播消息 」 准备工作 Spring Boot 2.1.3 RELEASE Spring S

php+websocket 实现聊天室

一.配置  开启socket组建,否则会报 Fatal error: Call to undefined function socket_create() 错误 1.打开php.ini配置文件,搜索 extension=php_sockets.dll,把前面的‘:’分号删掉.修改之后重启服务. 注意:如果php版本多,一定要注意使用的哪个版本就要取修改哪个版本的php.ini文件,wamp开启socket需要apache和php下面的php.ini一起修改,而phpstudy只需要修改一个php

WebSocket 网页聊天室的实现(服务器端:.net + windows服务,前端:Html5)

websocket是HTML5中的比较有特色一块,它使得以往在客户端软件中常用的socket在web程序中也能轻松的使用,较大的提高了效率.废话不多说,直接进入题. 网页聊天室包括2个部分,后端服务器+前端页面. 1.后端服务部分:.net4.0 + windows服务.相比寄宿在iis中,寄宿在进程中的windows服务更加的稳定可靠(文章中的例子用windows控制台程序演示,后面给出完整的windows服务的代码). 2.前端部分:html5 + jQuery + bootstrap.基本

php+html5基于websocket实现聊天室的方法

<?php error_reporting(E_ALL); ob_implicit_flush(); $sk=new Sock('127.0.0.1',8000); $sk->run(); class Sock{ public $sockets; public $users; public $master; public function __construct($address, $port){ $this->master=$this->WebSocket($address, $

搭建Websocket简易聊天室

本文,我们通过Egret和Node.js实现一个在线聊天室的demo.主要包括:聊天,改用户名,查看其他用户在线状态的功能.大致流程为,用户访问网页,即进入聊天状态,成为新游客,通过底部的输入框,可以输入自己想说的话,点击发布,信息呈现给所有在聊天的人的页面.用户可以实时修改自己的昵称,用户离线上线都会实时广播给其他用户. 体验链接 http://7hds.com:8888/ 下图为最终制作完成的聊天面板 WebSocket服务器可以用其他语言编写,本文采用的方法建立在Node.js上 . 在N

基于Tornado的websocket实现聊天室

self.render-string() 渲染成字符串 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .container{ border: 2px solid #dddddd; height: 400px; overflow: auto; } &

nodejs+websocket实现聊天室功能

最近一个朋友在项目中需要实现实时聊天等一些功能,帮忙弄了个粗略的,上代码. 服务器端 安装 express 跟 socket.io npm install --save express npm install --save socket.io var app = require('express')(); var http = require('http').Server(app); var io = require('socket.io')(http); app.get('/', functi