所以,起初我在同一台机器上运行多人游戏服务器和网络服务器。将lua-tables解析为php以使其进入SQL。我想,既然DCS-Lua引擎也支持TCP,最好使用TCP连接传输数据。
前言的结尾,这是我希望找到帮助的地方!
我的TCP连接工作正常,所有数据似乎转移得很好,但 php TCPServer会每12-24小时冻结。我从几年前就有很少的PHP经验,只有挖掘插座才能将网络服务器与游戏服务器放在同一台机器上。代码基本上工作正常,并做它应该的,但在一个随机点的PHP服务器将停止响应。
我一直试图找到原因几周并且有点放弃,直到我认为这可能是我找到经验丰富的人可以帮助我确定错误的最后手段。它特别难以调试,因为它每天只发生一次或两次。我希望那些对tcp套接字有更深入了解的人可能会在一瞥中看到它并说:当然它会冻结并解释原因! ○:)
所以这是PHP-Server的代码:
<?php
error_reporting(E_ALL);
include('source\Helper.php');
class koTCPServer {
private $address = '0.0.0.0'; // 0.0.0.0 means all available interfaces
//private $address = '127.0.0.1';
private $port = 52525; // the TCP port that should be used
private $maxClients = 10;
private $helper;
private $clients;
private $socket;
private $receivedScoreIDs;
private $dataBuffer = "";
private $numPacketsReceived = 0;
public function __construct() {
// Set time limit to indefinite execution
$this->log("constructing Socket");
set_time_limit(0);
error_reporting(E_ALL ^ E_NOTICE);
$this->helper = new TAW_Source_Helper();
$this->receivedScoreIDs = array();
}
public function start() {
$this->log("Starting Socket ...");
// Create a TCP Stream socket
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
// Bind the socket to an address/port
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($this->socket, $this->address, $this->port);
// Start listening for connections
socket_listen($this->socket, $this->maxClients);
$this->clients = array('0' => array('socket' => $this->socket));
$this->log("socket started, looking for connection ...");
while (true) {
// Setup clients listen socket for reading
$read = array();
$read[0] = $this->socket;
for($i=1; $i < $this->maxClients+1; $i++) {
if($this->clients[$i] != NULL) {
$read[$i+1] = $this->clients[$i]['socket'];
}
}
$write = NULL;
//$write = $read;
$except = NULL;
// Set up a blocking call to socket_select()
$ready = socket_select($read, $write, $except, $tv_sec = NULL);
if (false === §ready) {
echo "socket_select() failed, reason: " . socket_strerror(socket_last_error()) . "\n";
}
/* if a new connection is being made add it to the client array */
if(in_array($this->socket, $read)) {
$this->message("new connection");
$newSocket = socket_accept($this->socket);
socket_getpeername($newSocket, $ip);
for($i=1; $i < $this->maxClients+1; $i++) {
if(!isset($this->clients[$i])) {
$this->clients[$i]['socket'] = $newSocket;
$this->clients[$i]['ipaddress'] = $ip;
$this->clients[$i]['txbuf'] = '';
$this->clients[$i]['rxbuf'] = '';
$this->log("New client #$i connected, ip: " . $this->clients[$i]['ipaddress'] . " maxclients = " . $this->maxClients);
break;
} elseif($this->clients[$i]['ipaddress'] == $ip) {
$this->clients[$i]['socket'] = $newSocket;
$this->log("client #".$i.", ip: ".$ip." reconnected");
$oldRXBuf = $this->clients[$i]['rxbuf'];
$this->log("- old rxbuf: '$oldRXBuf'");
break;
} elseif($i == $this->maxClients) {
$this->message('Too many Clients connected!');
}
if($ready < 1) {
continue;
}
}
}
// If a client is trying to write - handle it now
for($i=1; $i < $this->maxClients+1; $i++) {
// if theres something to read from the client
if(in_array($this->clients[$i]['socket'], $read)) {
$data = @socket_read($this->clients[$i]['socket'], 1024000, PHP_NORMAL_READ);
if($data == null) {
$this->log('Client #'.$i.', '.$this->clients[$i]['ipaddress'].' disconnected!');
//update server status
if($this->clients[$i]['status'] != "restarting")
$this->helper->setServerStatus($this->clients[$i]['serverID'], "disconnected");
unset($this->clients[$i]);
continue;
}
// Data received!
if(!empty($data)) {
$this->numPacketsReceived++;
$this->message($this->numPacketsReceived.": We have data!: '".strval($data)."'");
// check if data is complete
$dataBuffer =& $this->clients[$i]['rxbuf'];
$dataBuffer .= $data;
$endIdx = strpos($dataBuffer, "\n");
while($endIdx !== FALSE) {
//$this->message(" - complete data received - ");
// handling data here
// .
// .
// .
// remove json string from $dataBuffer
$dataBuffer = substr($dataBuffer, $endIdx+1, strlen($dataBuffer));
$endIdx = strpos($dataBuffer, PHP_EOL);
}
}
} // end if client read
// now send stuff
if(isset($this->clients[$i])) {
// if theres something so send to the client, send it
if(strlen($this->clients[$i]['txbuf']) > 0) {
//$this->message("sending to '".$this->clients[$i]['ipaddress']."' ...");
$length = strlen($this->clients[$i]['txbuf']);
$result = socket_write($this->clients[$i]['socket'], $this->clients[$i]['txbuf'], $length);
if($result === false) {
//$this->message("send failed");
} else {
$this->clients[$i]['txbuf'] = substr($this->clients[$i]['txbuf'], $result);
}
}
}
} // end for clients
} // end while
}
}
$TCPServer = new koTCPServer();
$TCPServer->start();
?>
这是在lua中建立连接的方式:
local require = require
local loadfile = loadfile
package.path = package.path..";.\\LuaSocket\\?.lua"
package.cpath = package.cpath..";.\\LuaSocket\\?.dll"
local JSON = loadfile("Scripts\\JSON.lua")()
local socket = require("socket")
koTCPSocket = {}
--koTCPSocket.host = "localhost"
koTCPSocket.host = "91.133.95.88"
koTCPSocket.port = 52525
koTCPSocket.JSON = JSON
koTCPSocket.serverName = "unknown"
koTCPSocket.txbufRaw = '{"type":"intro","serverName":"notstartedyet"}\n'
koTCPSocket.txbuf = ''
koTCPSocket.rxbuf = ''
koTCPSocket.txTableBuf = {} -- holds all runtime type of tables (savegame, radiolist, playerlist) which are NOT unique
koTCPSocket.txScoreBuf = {} -- holds all scores which are unique and need to be transmitted securely. Once transmitted, server will return the scoreID to be removed from the table, if server does not return the id, send it again!
koTCPSocket.txScoreDelayBuf = {} -- delay scores by 5 seconds before they are sent again!
koTCPSocket.bufferFileName = lfs.writedir() .. "Missions\\The Kaukasus Offensive\\ko_TCPBuffer.lua"
function koTCPSocket.startConnection()
koEngine.debugText("koTCPSocket.startconnection()")
-- start connection
koTCPSocket.connection = socket.tcp()
koTCPSocket.connection:settimeout(.0001)
koTCPSocket.connection:connect(koTCPSocket.host, koTCPSocket.port)
-- looping functions
mist.scheduleFunction(koTCPSocket.transmit, nil, timer.getTime(), 0.01)
mist.scheduleFunction(koTCPSocket.receive, nil, timer.getTime()+0.5, 0.01)
end
koTCPSocket.inTransit = false -- 1 if we need to delete the entry from the buffer after it was sent, 0 if there is no object in the buffer to delete
function koTCPSocket.transmit()
-- if scorebuffer is empty, check if we have sent scores that need to be sent again
if #koTCPSocket.txScoreBuf == 0 then
for i, msg in ipairs(koTCPSocket.txScoreDelayBuf) do
if (timer.getTime() - msg.sendTime) > 60 then -- if message is older than 60 seconds, and still hasn't been confirmed by webserver, send it again!
env.info("koTCPSocket.transmit(): found score in delay buffer that is older than 60 seconds ... sending again!")
msg.sendTime = nil
table.insert(koTCPSocket.txScoreBuf, msg)
table.remove(koTCPSocket.txScoreDelayBuf, i)
end
end
end
-- refresh score buffers
-- we have an object cued (#txTableBuf > 0) and we did not just finish a transmission (not inTransit)
if koTCPSocket.txbuf:len() == 0 and #koTCPSocket.txScoreBuf > 0 and not koTCPSocket.inTransit then
koTCPSocket.txbuf = koTCPSocket.txbuf..koTCPSocket.JSON:encode(koTCPSocket.txScoreBuf[1]).."\n" -- cue the next transmission
-- now move the score back in the buffer, its going to be deleted
local tmp = koTCPSocket.txScoreBuf[1]
tmp.sendTime = timer.getTime()
table.remove(koTCPSocket.txScoreBuf,1)
table.insert(koTCPSocket.txScoreDelayBuf, tmp)
elseif koTCPSocket.txbuf:len() == 0 and #koTCPSocket.txTableBuf > 0 and not koTCPSocket.inTransit then
koTCPSocket.txbuf = koTCPSocket.txbuf..koTCPSocket.JSON:encode(koTCPSocket.txTableBuf[1]).."\n" -- cue the next transmission
koTCPSocket.inTransit = true -- we started a new transmission
-- we have just finished a transmission (inTransit = true) and there is one more object in the txTableBuf (>1)
elseif koTCPSocket.txbuf:len() == 0 and #koTCPSocket.txTableBuf > 1 and koTCPSocket.inTransit then
table.remove(koTCPSocket.txTableBuf,1) -- remove the just transmitted object and cue the next transmission
koTCPSocket.txbuf = koTCPSocket.txbuf..koTCPSocket.JSON:encode(koTCPSocket.txTableBuf[1]).."\n"
-- we have just finished a transmission (inTransit = true) and there is no more object in the txTableBuf (==0)
elseif koTCPSocket.txbuf:len() == 0 and #koTCPSocket.txTableBuf == 1 and koTCPSocket.inTransit then
table.remove(koTCPSocket.txTableBuf,1) -- remove the just transmitted object
koTCPSocket.inTransit = false -- no more transmissions
end
-- handle actual transmission
if koTCPSocket.txbuf:len() > 0 then
--koEngine.debugText("koTCPSocket.transmit() - buffer available, sending ...")
local bytes_sent = nil
local ret1, ret2, ret3 = koTCPSocket.connection:send(koTCPSocket.txbuf)
if ret1 then
--koEngine.debugText(" - Transmission complete!")
bytes_sent = ret1
else
koEngine.debugText("could not send koTCPSocket: "..ret2)
if ret3 == 0 then
if ret2 == "closed" then
if MissionData then
koTCPSocket.txbuf = koTCPSocket.txbuf..koTCPSocket.txbufRaw..'\n'
else
koTCPSocket.txbuf = koTCPSocket.txbuf..'{"type":"dummy"}\n'
end
koTCPSocket.rxbuf = ""
koTCPSocket.connection = socket.tcp()
koTCPSocket.connection:settimeout(.0001)
koEngine.debugText("koTCPSocket: socket was closed")
end
--koEngine.debugText("reconnecting to "..tostring(koTCPSocket.host)..":"..tostring(koTCPSocket.port))
koTCPSocket.connection:connect(koTCPSocket.host, koTCPSocket.port)
return
end
bytes_sent = ret3
koEngine.debugText("bytes sent: "..tostring(bytes_sent))
koEngine.debugText(" - sent string: '"..koTCPSocket.txbuf:sub(1, bytes_sent).."'")
end
koTCPSocket.txbuf = koTCPSocket.txbuf:sub(bytes_sent + 1)
end
end
function koTCPSocket.receive()
--env.info("koTCPSocket.receive()")
local line, err, partRes = koTCPSocket.connection:receive('*l')
if partRes and partRes:len() > 0 then
--env.info("koTCPSocket.receive(), partRes = '"..tostring(partRes).."'")
koTCPSocket.rxbuf = koTCPSocket.rxbuf .. partRes
env.info("koTCPSocket.receive() - partRes = '"..partRes.."'")
local line = koTCPSocket.rxbuf:sub(1, koTCPSocket.rxbuf:find("\\n")-1)
koTCPSocket.rxbuf = koTCPSocket.rxbuf:sub(koTCPSocket.rxbuf:find("\\n")+2, -1)
while line:len() > 0 do
local msg = JSON:decode(line)
--env.info("koTCPSocket.receive(): msg = "..koEngine.TableSerialization(msg))
if msg.type == "alive" then
env.info("koTCPSocket.receive() - Alive packet received and returned")
koTCPSocket.txbuf = koTCPSocket.txbuf .. '{"type":"alive","serverName":"'..koTCPSocket.serverName..'"}\n'
elseif msg.type == "scoreReceived" then
env.info("koTCPSocket.receive() - scoreID '"..msg.scoreID.."' received, checking buffer!")
--env.info("txScoreBuf = "..koEngine.TableSerialization(txScoreBuf))
for i, scoreTable in ipairs(koTCPSocket.txScoreBuf) do
for ucid, score in pairs(scoreTable.data) do
--env.info("score = "..koEngine.TableSerialization(score))
--env.info("comparing "..score.scoreID.." with "..msg.scoreID)
if tonumber(score.scoreID) == tonumber(msg.scoreID) then
table.remove(koTCPSocket.txScoreBuf, i)
env.info("- found score in table, removed index "..i)
end
end
end
for i, scoreTable in ipairs(koTCPSocket.txScoreDelayBuf) do
for ucid, score in pairs(scoreTable.data) do
if tonumber(score.scoreID) == tonumber(msg.scoreID) then
table.remove(koTCPSocket.txScoreDelayBuf, i)
env.info("- found score in delay-table, removed index "..i)
end
end
end
end
if koTCPSocket.rxbuf:len() > 0 and koTCPSocket.rxbuf:find("\\n") then
line = koTCPSocket.rxbuf:sub(1, koTCPSocket.rxbuf:find("\\n")-1)
koTCPSocket.rxbuf = koTCPSocket.rxbuf:sub(koTCPSocket.rxbuf:find("\\n")+2, -1)
env.info("koTCPSocket.receive() - rxbuf in loop = '"..koTCPSocket.rxbuf.."'")
else
line = ""
end
end
end
end
function koTCPSocket.close(reason)
koTCPSocket.send({reason = "reason"},"shutdown")
koTCPSocket.connection:close()
end
env.info("koTCPSocket loaded")