首页 >后端开发 >php教程 >socket多用户连接求助

socket多用户连接求助

WBOY
WBOY原创
2016-06-23 14:23:231333浏览

小弟正在做一个flash为客户端,php为服务端的socket小程序

现在出现一个问题flash成功连接服务端后!一切都能正常通信

但是不能多用户连接服务端(比如我打开两个flash,第二就会连接不上)

求助php socket是否能解决多用户连接的问题


望高手相助


回复讨论(解决方案)

没有人开发过类似的东西吗?

你这个 PHP 程序,是普通的处理响应 HTTP 请求吗?还是真的是个 socket server 呢?

如果是后者的话,你的 PHP 程序是如何被启动的呢?

你要先把这些说清楚,下面才好讨论。

是要真的socket响应
flash一直跟php保持着连接状态

php
可以用
set_time_limit(0);
ignore_user_abort();
进行永远执行

但是还是用java写的好
可我不会java

你只回答了我两个问题中的第一个。第二个问题,你的 PHP 是如何启动的呢?

如果是手工预先发一个 HTTP 请求来启动的,那么就需要在 PHP 里用一个固定端口接收并处理多个 socket 连接请求,这个需要一定的技巧。

如果是由 FLASH 发送这个 HTTP 请求来启动的,那就可以在 PHP 里使用不同的端口来接收 socket 连接。

无论如何,这种方式运行的 PHP 程序,不适合做 socket server。

好建议

用flash来启动socket服务端

但是我不好掌握启动后运行时间(如果永久运行,服务器的开销会很大,直至最后down掉)
还有当有新用户进入的时候,我需要主动发送信息给其他之前登录的用户,却不太好实现了

回答你之前的问题
php启动是在进入游戏之前先用php连接一下服务端如果不能连接就请求一下(我之前是这样想的)

的确,每个 FLASH 自己请求启动一个 PHP 来做 socket server,就只能为这个 FLASH 单独服务,难以实现多个会话的互通。

不了解你的实际应用背景,所以不清楚你为什么一定要用 socket,如果你的通信方式能回归到普通的 HTTP 的话,应该是最简单的。

一定要用 socket 的话,建议你还是考虑换一种方式吧。不是说用 java 就好,即使用 java,如果是 web 方式部署的话,问题是一样的。PHP 也可以用 CLI 的方式来做。

通过HTTP连接, 就是通过IIS,APACHE, 他门有很好的处理多用户的本事,

所以在可能的情况下都是用WEBSERVICE,也即是用用IIS,APACHE,

所以网上的教材说到FLASH都是说用WEBSERVICE,

否则自己搞个SERVER处理得不好就会有倒塞, 

主要就是做一个flash游戏,基于web,能够几个人在一个房间内玩

要能够即时通知各个用户其他用户的情况,所以必须用socket才能达到即时通信的目的

CLI还没有用过,能给我一个例子吗?
我想知道他是如何工作的,能干什么

你这个应用场景,关键就是要“即时通知”,是吧?

用 socket 当然能达成目的,但要实现起来并不容易,至少在 web 容器里会比较费劲。

其实用 HTTP 长连接也能达到同样的效果,比如你访问开心网,总会有一个未完成的 HTTP 请求,最多 30 秒它就会结束,但马上会再发起一个请求,这就可以实现“即时通知”。相关资料可以搜索“comet 长连接”。

(请注意,上面说的 30 秒,不是说每个通知要等 30 秒。如果没有任何通知,则超过 30 秒会改用一个新的连接;但如果有通知的话,会立刻返回)

CLI 只是 PHP 程序的另外一种启动运行方式,相当于在服务器上直接运行一个 exe,只不过这个 exe 是用 PHP 编写的。因为没有 web 容器的限制,对于多线程、网络通信等功能使用起来比较自由。

php
可以用
set_time_limit(0);
ignore_user_abort();
进行永远执行

但是还是用java写的好
可我不会java
怎么还有ignore_user_abort(),php socket server要在命令行下执行,也就是上面说的cli,而不是设为为一个http响应端.
http://devzone.zend.com/article/1086

谢谢maquan

php编写.exe后缀的可执行文件吗?

还是说文件也是.php只是启动发式变了而已?

http://devzone.zend.com/article/1086上面的写法和手册的socket_select差不多

我再尝试下

谢谢大家的帮助!

小弟学到了很多以前不懂的知识和一些解决方案!

再次感谢

CLI 只是 PHP 程序的另外一种启动运行方式,相当于在服务器上直接运行一个 exe,只不过这个 exe 是用 PHP 编写的。因为没有 web 容器的限制,对于多线程、网络通信等功能使用起来比较自由。


我用cli方式运行就不会有线程限制了?
是不是等于我以前写的代码直接运行就可以了?

非常感谢大家,已经可以达到多人在线了

感觉即时性也非常的好

谢谢maquan和BooJS

结贴后还能继续聊天吗?

还想跟你们聊聊呢?

结贴不着急,只要是跟这个话题有关的,尽管继续聊,hehe

不过如果是其它问题,最好另外开贴,这样也方便大家阅读。CSDN 是公共场所嘛 :)

昨天上传到空间发现用cli运行有限制30秒便超时了!

那么我们就必须要写一个守护他的程序当发现超时的时候重新用cli方式运行一次

那么用try catch是否能监控到超时的时候运行catch里面的东西呢?
try{
    我的socket代码
}
catch(){
    再次重新请求
}

catch括号里面我该如何写呢?

对空间不了解,不知道他们有什么管理限制。PHP 自己的运行时间限制可以通过 set_time_limit() 来设定,缺省就是 30s。

用守护程序重新加载的方式,未必能很好地解决你的问题,它会让你的服务端表现很不稳定,客户端开发难度加大。

如果你是租用空间的话,超常规的程序一定会有诸多限制,强烈建议你仔细了解一下长连接。

好的谢谢

空间确实是设置了30秒超时
我先了解一下长连接!

这个对环境的要求会小很多

通过一天的了解大概了解了什么是comet长连接了!

主要就在php端写上
flush();
刷新缓冲区,让前台的ajax接收到信息

同时php端并没有停止工作

当30秒超时的时候,客户端再重新连接一次

不知道我的理解是否正确?

你理解得没错,大体上就是这个意思。

不过你可能太关注实现的细节了。概念上讲,所谓长连接,就是服务器端收到 HTTP 请求后,并不立刻完成它,而是保持一段时间(比如 30 秒),这期间如果有需要即时送达的通知,则立刻输出响应内容并关闭连接。如果平安无事,到时间也要返回(最好不要等到被容器杀掉),这样可以给客户端一个完整的响应,便于客户端继续下面的工作。

实现长连接的一个难点在于等待过程中如何获知“需要即时送达的通知”,尤其是在租用空间的 web 容器中,实现起来会很受限制,简单的轮询和延时肯定效率非常差,最好是某种阻塞方式的 IO。


????????????????????????????????
基于CSDN论坛提供的插件扩展功能,自己做了个签名档工具, 分享给大家,欢迎 技术交流 :)

确实在等待过程中,如何知道有新内容需要返回是一个比较麻烦的事情
做的不好会影响即时性

我是这样想的,如果有内容需要返回的时候是读取由另一个页面创建的xml或者php数组之类的(即读取写文件),当连接发起时,记录一下文件当前的修改时间,然后当修改时间发生变化的时候就返回文件里的内容。
不知道这样会不会出现一些问题,比如修改和连接同时进行(我连接的前一秒,文件被修改了,这样就只有等下次文件修改的时候才会返回信息,出现这样情况的几率不知道有多大)

你指的IO操作我不太明白



我写过一个socket的小例子。在我的博客里面,你可以去看看。
http://blog.csdn.net/a82168506/article/details/6307461

谢谢楼上这位兄弟,socket的我也写了代码和你的差不多

问题就是空间强制设定了30秒超时(set_time_limit(0)没有用),所以没有办法才想看看长连接能不能解决!

学习了一下IO
发现我的这种长连接貌似是阻塞IO吧!

如果想做到是非阻塞IO,技巧在哪呢?

经过测试发现ajax发起的长连接是非阻塞IO

只是浏览器对长连接有限制,当相同浏览起发送两个长连接的时候,有一个是无法连接的!

如果实现有新消息立即通知客户

我想了如下方法
还是用读文件的方法
首先页面打开的时候,由js发起连接同时传送所要读的文件当前的修改时间

到达php页面后当发现修改时间和传送过来的时间不一致的时候通知客户端有新消息
php页面运行到25秒的时候直接返回一个文件的最后修改时间给js,此次长连接结束

js接收到php传回的文件修改时间后重新发起连接并传送php传回的文件修改时间

我相信这样就不会漏掉需要通知客户端的信息了吧

以上是我个人的一个想法

请maquan和大家再帮帮我,分享一下你们的处理方法

谢谢

……
到达php页面后当发现修改时间和传送过来的时间不一致的时候通知客户端有新消息
php页面运行到25秒的时候直接返回一个文件的最后修改时间给js,此次长连接结束
……
在红、蓝两个动作之间的时间里,程序在干什么呢?如果在第 10 秒的时候“事件发生了”,能及时通知吗?

两个动作之间php页面sleep(1)(返回新消息后连接并没有结束),然后接着检查文件修改时间,直至运行时间大于25秒才结束连接,再由js发起新连接

这样应该没有问题吧

如果是你,你怎么做呢?如何判断有新的消息需要返回?

等偶有时间了,也来弄下flash

首先,“返回新消息后连接并没有结束”这个恐怕是有问题的,只有结束掉连接,客户端才会认为“已经完整接受到了一个 HTTP Response”,才会进一步处理。如果不结束连接的话,即使你做了 flush 等等,最多也只是把数据流传到客户端,但客户端并没有完成接收、开始处理,也就是说你这个通知还是没有“即时”。

至于“检查是否有通知”的方法,你所用的应该属于“轮询+延迟”的方法,一般来说可以用,但“即时性”和“运行效率”稍差一点点。我总觉得“阻塞 IO”的方式也许会好一点,你可以参考一下 socket_recvfrom(),缺点是资源占用多了一点。

租用的空间嘛,没办法,受制于 web 容器。如果是自己的独立服务器,搞个 deamon,用多路异步 IO,想咋玩就咋玩,hehe


????????????????????????????????
基于CSDN论坛提供的插件扩展功能,自己做了个签名档工具, 分享给大家,欢迎 技术交流 :)

http://www.zeitoun.net/articles/comet_and_php/start
我查找到这个例子

他使用了一个js库就可以实现了长连接且内容能正常返回,连接不终止!

大家看看

这个库我不太熟悉,有熟悉的朋友吗?

这就是真正意义上的comet吗?

多方查找资料
发现能参考的很少

我现有的水平可能只能这样写了

js发起长连接

php页面如果有新信息立即返回给js并且结束该连接,JS收到新信息后(进行处理),马上发起新的连接(数据处理之前)

如果超过25秒都没有新信息,php结束该连接,js同样再次发起新连接!

用socket_recvfrom()我又必须用cli去运行一个服务端了吧,这样的就是阻塞IO了?
貌似JS也是可以当作socket客户端连接服务端的,如果多个客户端同时去连接,出现了阻塞,不知道客户端的发起的连接会超时吗?会不会在socket服务端断开前一连接时自动连接上呢?

理论上讲如果客户端多的情况下,实时性恐怕也无法保证了

上面我好像理解错误了,找到了一个最通俗的解释

比如进程A要从网络收一个包,如果没有包就等待,这就是阻塞。
不等待,去做别的事情,等包来了再处理就是不阻塞。

要是按这样来看

js发起长连接
php页面如果有新信息立即返回给js并且结束该连接,JS收到新信息后(进行处理),马上发起新的连接(数据处理之前)
如果超过25秒都没有新信息,php结束该连接,js同样再次发起新连接!
其实是一个阻塞IO,当没有新信息的时候js是一直处理等待状态,直到有新的信息为止

在一个通信的两段,读操作和写操作,它们分别使用的是“阻塞”还是“非阻塞”,是各说各的,互不影响。

比如你的服务端代码:等待通知如果采用 socket_recvfrom(),那就是“阻塞”的,而输出 HTTP Response 一般来说都是“阻塞”的。而在 js 这边接收 HTTP Response,既然你用了 AJAX,那就一定是“非阻塞”的,这正是 AJAX 的价值所在,否则页面就卡死变白屏了。
用socket_recvfrom()我又必须用cli去运行一个服务端了吧,这样的就是阻塞IO了?
不是这样的。负责长连接的那个 PHP 用 socket_recvfrom() 等待通知数据,处于“阻塞状态”;而产生通知数据的一定是你另外一个常规的 PHP 程序,比如“某用户登录”,由它负责 socket_sendto()。

理论上讲如果客户端多的情况下,实时性恐怕也无法保证了
需要担心的不是“实时性”,前面描述的通信模型如果能成功的话,实时性总是能保证的(当然还取决于网络速度)。问题在于当客户端数量多的情况下,资源占用会比较大,包括:

·每个客户端会持续占用一个 PHP 长连接进程资源;
·每个长连接进程会占用一个 socket 资源;

总的说来,局限在 web 容器里,这种模式不适合大规模应用。比如空间服务商限定了你网站的 HTTP 并发连接数,那长连接有可能将你的整个网站拖垮。那还不如回归到“客户端周期性轮询”的模式呢,损失一点“实时性”,换来的是“在线用户数”。

我运用了你的方法尝试,发现一个问题

每个端口只能创建一个套接字
如果我使用随机端口的话socket_sendto就不会知道目前有哪些端口需要发送了

我是否需要维护一个当前在线的端口表?是保存到数据库比较好,还是存为文件模式呢?

用 UDP 广播,具体用法你查一下资料吧。

好的太感谢你的耐心解答了
我先查查资料

maquan
广播我已经测试成功

但是广播还是需要设置端口

我贴上我的代码

js请求的php

header("Cache-Control: no-cache, must-revalidate");header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");include('config.php');//这里是$add(IP)和$port$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);socket_bind($socket, $add, $port);//我尝试将$port写为0但是如果这样创建会失败$port = 0;socket_recvfrom($socket, $buf, 10240, 0, $from, $port);echo "Received $buf from remote address $from and remote port $port" . PHP_EOL;socket_close($socket);


广播的php
include('config.php');$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);socket_set_option($sock, SOL_SOCKET, SO_BROADCAST, 1); $msg = rand(1000,9999);$len = strlen($msg);socket_sendto($sock, $msg, $len, 0,'255.255.255.255', $port);//这里的$port要和上面那文件的$port相同上面的文件才能收到广播,但是不同客户连接后端口必须不一样才能成功创建socket连接,所以要怎么写才能通知该IP下所有端口的用户呢?这种是通知了端口为$port的所有用户吧socket_close($sock);


我该如何写才能是通知所有端口呢?

把socket.accept写在while(1)中就行

accept是监听有没有用户连接吧

我这个是不需要用户连接用sendto根据ip和端口发送给在线的用户

再试试 socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1); 

还是不行
socket_sendto函数的端口参数不能为空的话接收端就收不到信息,也就是说不能一次性send信息给所有线上的端口

socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1); 应该是写在接收端吧,但是写了也不行

你能写个例子给我吗?

昨天晚上心血来潮,写了一段 UDP 广播通信的程序,正好可以供你参考 :)

recvfrom.php

<?phpheader("Cache-Control: no-cache, must-revalidate");header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");$socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );socket_set_option( $socket, SOL_SOCKET, SO_BROADCAST, 1 );socket_set_option( $socket, SOL_SOCKET, SO_REUSEADDR, 1 );$result = socket_bind( $socket, '0.0.0.0', 9999 );$addr = '';$port = 0;$result = socket_recvfrom( $socket, $buf, 10240, 0, $addr, $port );socket_close($socket);echo "recvfrom: [({$result}){$buf}] from [{$addr}:{$port}]";


sendto.php
<?php$socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );socket_set_option( $socket, SOL_SOCKET, SO_BROADCAST, 1 );$msg = date('Y-n-j H:i:s');$len = strlen( $msg );$result = socket_sendto( $socket, $msg, $len, 0, '255.255.255.255', 9999 );socket_close( $socket );echo "sendto: [({$result}){$msg}]";


以上两个 PHP 程序,recvfrom.php 可以用多个浏览器窗口分别发送请求,都会处在等待状态,然后再用一个浏览器窗口发送 sendto.php 的请求,前面的几个浏览器窗口就同时得到结果了。


????????????????????????????????
基于CSDN论坛提供的插件扩展功能,自己做了个签名档工具, 分享给大家,欢迎 技术交流 :)

非常感谢
已经测试成功
现在返回去看SO_REUSEADDR的解释Reports whether local addresses can be reused. 
才恍然大悟,“是否可重复使用本地地址”
设置了这个option过后就不会再出现bind时提醒一个IP一个端口只能被建立一个连接了

刚测试发现
recvfrom.phpl里面不用加这句也成功
socket_set_option( $socket, SOL_SOCKET, SO_BROADCAST, 1 );

我认为可以删除,因为广播的页面是sendto.php,recvfrom.php只需要可重复使用本地地址和端口就行了

是这样吗?

这个我也不是很清楚。

UDP 广播包跟普通的 UDP 包一个很明显的区别就是“目标 IP 地址不同”。作为“监听 UDP 包”的 socket,从物理上讲,它肯定能“感知”到广播和非广播的 UDP 包,但它会不会“接收”广播包呢?如果你已经把这个 socket 设成“广播”型了,那肯定就没问题了,但如果不设置呢?它会不会拒收呢?

我觉得这个不好说,可能跟具体的系统实现有关吧。虽然实际使用时不写也可以,但我总觉得写上更安全。

有道理
计算一下外网的广播地址测试一下

广播地址要有子网掩码才能算啊

还得问问服务商,只有IP貌似算不了

不知道我理解得对不对,广播(broadcast)仅限于局域网内,即一个网段。要出外网的话,就是多播(multicast)了,需要路由器配合。

我做的这个其实就是对一个ip的不同端口进行广播呀

应该是可以办到的吧

如果无法办到就真的只能维护一个用户使用的端口表了

尝试了一下不行
帮我看看广播地址是不是算错了


IP 地址: 123.183.215.45
子网掩码: 255.255.255.128
默认网关: 123.183.215.1

我算出来是
123.183.215.127

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn