PHP: game loop (threads or the sort)
I am writing PHP code to be a game client. It uses socket; socket_create followed by socket_connect and then socket_read. It works fine, but the issue is that the server can send a packet at any time which means socket_read needs to be happening constantly in a "game loop". So something like this:
<?php
$reply = "";
do {
$recv = "";
$recv = socket_read($socket, '1400');
if($recv != "") {
$reply .= $recv;
}
} while($recv != "");
echo($reply);
?>
Doesn't work because it's stuck in the loop (server doesn't terminate connection until game is quit by client) and the PHP code needs to 开发者_JS百科handle the packet stuff as it comes in.
So PHP doesn't really have threading. What's the best way of handling this?
Basically any software platform is going to butt up against this problem. Most, as you've figured out, solve it with threading. While threading IS possible in PHP. It requires MAJORHAXXX. Such as launching a commandline php thread from within php.
It really doesn't end up being ideal.
However, there are other ways to get around this.
But you need to check ALL the marks on this list first:
[] - My game doesn't need to constantly keep checking the server, such as for player locations or complex movements. Anything beyond a chat-room level of data transfer and update rates should leave this box un-checked.
[] - My game doesn't need to be told BY THE SERVER anything. It is perfectly acceptable for the client to ask for anything it needs, perhaps once a second or better off once a minute.
[] - My game doesn't need to keep a constant simulation of a complex world running on the server for longer than it takes to complete a request. Tracking chat is one thing, doing physics and graphics modifications is another.
If you checked all of these boxes, then PHP is STILL IN THE GAME! Otherwise. Don't bother.
Basically, what I am saying here is that PHP is great for games that aren't really multiplayer, and that are turn-based or at least not very interactive. But once you have to keep things going without the player, PHP falls on its face.
VOODOO LEVEL
But if you simply MUST do this. There ARE ways to get around it.
A - Create a PHP Daemon that runs your world, pipe all other traffic to either a getter or setter request file that interacts with the database. So, you might request a getting of the game world state, or set a value that the player performed. All other game-world related things can be handled by the daemon and the game itself takes place in the database.
B - Use cron, not a Daemon. (dangerous, but we already established you as a risk taker, right?)
C - TRY only a Daemon and listening to sockets, then sending out threads (via exec()) to respond. Kind of like AndreKR's idea above, only you don't need to sleep. Problem here is you will almost always end up missing stuff or otherwise getting cut off. And the whole thing might explode if the Daemon get's run twice somehow..
If you really want to do this, you have to sleep for some time, check the socket, sleep again, check the socket...
To check the socket without blocking you need to use non-blocking I/O which you can achieve with the socket_set_nonblock()
or socket_recv()
which has a DONTWAIT flag.
Can be done, but I agree with @Andrey and @DampeS8N, not the best choice. If you are dead set on doing this, check out this book: You want to do WHAT with PHP?
TCP implementations tend to fragment and join messages; there's no telling how much data or how many message fragments a socket receive will return. You need to know where a message ends and a new one begins (which may happen multiple times in data returned by a single read). Some simple solutions:
- Use some kind of delimiter. End each message by '\0'.
- Send the message size along with the message. Start each message with "Content-length: 42\n" or two size bytes (0x00 0x42).
- Use XML.
<message>
starts and</message>
ends a message.
PHP's XML parser doesn't like incomplete XMLs, though so the third option is out unless you want to match the start and end tags manually. Use the first option if the protocol is based on ASCII, second if it's binary, third if it's already XML.
Now, remember you can get any number of messages per packet. In the most complex case, you might have the end of an earlier message followed by a number of full messages and the beginning of yet another message in a single packet.
A full solution would be along these lines:
while (connected) {
while (messages in buffer < 1) {
read from socket;
add to buffer;
}
while (messages in buffer > 0) {
extract message from buffer;
process message;
}
}
...though this is an asynchronous message loop. I'll leave the "if there's a message available, return it; else, wait for one" synchronous implementation as an exercise. (Hint: You'll need a class to build and buffer messages.)
PHP has no multithreading, so you should really consider to use a more suitable language (like Andrey mentioned in its comment).
All you have to do is to use socket_select()
function:
http://php.net/manual/en/function.socket-select.php
It will put your script to sleep and wake it up when there is data on the socket to be read. It's waaay more efficient that periodical sleep/read, cron scripts and all other proposed solutions.
@aib made a valid point. The server might sent a complete "game message" divided into several packets. Dont expect to get all your data in a singe exceution of code block after socket_select()
returns.
Instead of writing this smelly blocking polling loop, check out some event system based around the reactor pattern like Python Twisted or Ruby EventMachine.
I believe the PHP flavor is call PHP-MIO: http://thethoughtlab.blogspot.com/2007/04/non-blocking-io-with-php-mio.html
精彩评论