mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-12 15:14:35 +00:00
Engine directory for ticket #1
This commit is contained in:
parent
352279af7a
commit
7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions
455
Engine/source/app/net/httpObject.cpp
Normal file
455
Engine/source/app/net/httpObject.cpp
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "app/net/httpObject.h"
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "platform/event.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "console/simBase.h"
|
||||
#include "console/consoleInternal.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
IMPLEMENT_CONOBJECT(HTTPObject);
|
||||
|
||||
ConsoleDocClass( HTTPObject,
|
||||
"@brief Allows communications between the game and a server using HTTP.\n\n"
|
||||
|
||||
"HTTPObject is derrived from TCPObject and makes use of the same callbacks for dealing with "
|
||||
"connections and received data. However, the way in which you use HTTPObject to connect "
|
||||
"with a server is different than TCPObject. Rather than opening a connection, sending data, "
|
||||
"waiting to receive data, and then closing the connection, you issue a get() or post() and "
|
||||
"handle the response. The connection is automatically created and destroyed for you.\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// In this example we'll retrieve the weather in Las Vegas using\n"
|
||||
"// Google's API. The response is in XML which could be processed\n"
|
||||
"// and used by the game using SimXMLDocument, but we'll just output\n"
|
||||
"// the results to the console in this example.\n\n"
|
||||
|
||||
"// Define callbacks for our specific HTTPObject using our instance's\n"
|
||||
"// name (WeatherFeed) as the namespace.\n\n"
|
||||
|
||||
"// Handle an issue with resolving the server's name\n"
|
||||
"function WeatherFeed::onDNSFailed(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"DNSFailed\";\n\n"
|
||||
|
||||
" // Handle DNS failure\n"
|
||||
"}\n\n"
|
||||
|
||||
"function WeatherFeed::onConnectFailed(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"ConnectFailed\";\n\n"
|
||||
" // Handle connection failure\n"
|
||||
"}\n\n"
|
||||
|
||||
"function WeatherFeed::onDNSResolved(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"DNSResolved\";\n\n"
|
||||
"}\n\n"
|
||||
|
||||
"function WeatherFeed::onConnected(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"Connected\";\n\n"
|
||||
|
||||
" // Clear our buffer\n"
|
||||
" %this.buffer = \"\";\n"
|
||||
"}\n\n"
|
||||
|
||||
"function WeatherFeed::onDisconnect(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"Disconnected\";\n\n"
|
||||
|
||||
" // Output the buffer to the console\n"
|
||||
" echo(\"Google Weather Results:\");\n"
|
||||
" echo(%this.buffer);\n"
|
||||
"}\n\n"
|
||||
|
||||
"// Handle a line from the server\n"
|
||||
"function WeatherFeed::onLine(%this, %line)\n"
|
||||
"{\n"
|
||||
" // Store this line in out buffer\n"
|
||||
" %this.buffer = %this.buffer @ %line;\n"
|
||||
"}\n\n"
|
||||
|
||||
"// Create the HTTPObject\n"
|
||||
"%feed = new HTTPObject(WeatherFeed);\n\n"
|
||||
|
||||
"// Define a dynamic field to store the last connection state\n"
|
||||
"%feed.lastState = \"None\";\n\n"
|
||||
|
||||
"// Send the GET command\n"
|
||||
"%feed.get(\"www.google.com:80\", \"/ig/api\", \"weather=Las-Vegas,US\");\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see TCPObject\n"
|
||||
|
||||
"@ingroup Networking\n"
|
||||
);
|
||||
|
||||
//--------------------------------------
|
||||
|
||||
HTTPObject::HTTPObject()
|
||||
{
|
||||
mHostName = 0;
|
||||
mPath = 0;
|
||||
mQuery = 0;
|
||||
mPost = 0;
|
||||
mBufferSave = 0;
|
||||
}
|
||||
|
||||
HTTPObject::~HTTPObject()
|
||||
{
|
||||
dFree(mHostName);
|
||||
dFree(mPath);
|
||||
dFree(mQuery);
|
||||
dFree(mPost);
|
||||
|
||||
mHostName = 0;
|
||||
mPath = 0;
|
||||
mQuery = 0;
|
||||
mPost = 0;
|
||||
dFree(mBufferSave);
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
//--------------------------------------
|
||||
void HTTPObject::get(const char *host, const char *path, const char *query)
|
||||
{
|
||||
if(mHostName)
|
||||
dFree(mHostName);
|
||||
if(mPath)
|
||||
dFree(mPath);
|
||||
if(mQuery)
|
||||
dFree(mQuery);
|
||||
if(mPost)
|
||||
dFree(mPost);
|
||||
if(mBufferSave)
|
||||
dFree(mBufferSave);
|
||||
|
||||
mBufferSave = 0;
|
||||
mHostName = dStrdup(host);
|
||||
mPath = dStrdup(path);
|
||||
if(query)
|
||||
mQuery = dStrdup(query);
|
||||
else
|
||||
mQuery = NULL;
|
||||
mPost = NULL;
|
||||
|
||||
connect(host);
|
||||
}
|
||||
|
||||
void HTTPObject::post(const char *host, const char *path, const char *query, const char *post)
|
||||
{
|
||||
if(mHostName)
|
||||
dFree(mHostName);
|
||||
if(mPath)
|
||||
dFree(mPath);
|
||||
if(mQuery)
|
||||
dFree(mQuery);
|
||||
if(mPost)
|
||||
dFree(mPost);
|
||||
if(mBufferSave)
|
||||
dFree(mBufferSave);
|
||||
|
||||
mBufferSave = 0;
|
||||
mHostName = dStrdup(host);
|
||||
mPath = dStrdup(path);
|
||||
if(query && query[0])
|
||||
mQuery = dStrdup(query);
|
||||
else
|
||||
mQuery = NULL;
|
||||
mPost = dStrdup(post);
|
||||
connect(host);
|
||||
}
|
||||
|
||||
static char getHex(char c)
|
||||
{
|
||||
if(c <= 9)
|
||||
return c + '0';
|
||||
return c - 10 + 'A';
|
||||
}
|
||||
|
||||
static S32 getHexVal(char c)
|
||||
{
|
||||
if(c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
else if(c >= 'A' && c <= 'Z')
|
||||
return c - 'A' + 10;
|
||||
else if(c >= 'a' && c <= 'z')
|
||||
return c - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void HTTPObject::expandPath(char *dest, const char *path, U32 destSize)
|
||||
{
|
||||
static bool asciiEscapeTableBuilt = false;
|
||||
static bool asciiEscapeTable[256];
|
||||
if(!asciiEscapeTableBuilt)
|
||||
{
|
||||
asciiEscapeTableBuilt = true;
|
||||
U32 i;
|
||||
for(i = 0; i <= ' '; i++)
|
||||
asciiEscapeTable[i] = true;
|
||||
for(;i <= 0x7F; i++)
|
||||
asciiEscapeTable[i] = false;
|
||||
for(;i <= 0xFF; i++)
|
||||
asciiEscapeTable[i] = true;
|
||||
asciiEscapeTable[static_cast<U32>('\"')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('_')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('\'')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('#')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('$')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('%')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('&')] = false;
|
||||
asciiEscapeTable[static_cast<U32>('+')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('-')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('~')] = true;
|
||||
}
|
||||
|
||||
U32 destIndex = 0;
|
||||
U32 srcIndex = 0;
|
||||
while(path[srcIndex] && destIndex < destSize - 3)
|
||||
{
|
||||
char c = path[srcIndex++];
|
||||
if(asciiEscapeTable[static_cast<U32>(c)])
|
||||
{
|
||||
dest[destIndex++] = '%';
|
||||
dest[destIndex++] = getHex((c >> 4) & 0xF);
|
||||
dest[destIndex++] = getHex(c & 0xF);
|
||||
}
|
||||
else
|
||||
dest[destIndex++] = c;
|
||||
}
|
||||
dest[destIndex] = 0;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
void HTTPObject::onConnected()
|
||||
{
|
||||
Parent::onConnected();
|
||||
char expPath[8192];
|
||||
char buffer[8192];
|
||||
|
||||
if(mQuery)
|
||||
{
|
||||
dSprintf(buffer, sizeof(buffer), "%s?%s", mPath, mQuery);
|
||||
expandPath(expPath, buffer, sizeof(expPath));
|
||||
}
|
||||
else
|
||||
expandPath(expPath, mPath, sizeof(expPath));
|
||||
|
||||
char *pt = dStrchr(mHostName, ':');
|
||||
if(pt)
|
||||
*pt = 0;
|
||||
dSprintf(buffer, sizeof(buffer), "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", expPath, mHostName);
|
||||
if(pt)
|
||||
*pt = ':';
|
||||
|
||||
send((U8*)buffer, dStrlen(buffer));
|
||||
mParseState = ParsingStatusLine;
|
||||
mChunkedEncoding = false;
|
||||
}
|
||||
|
||||
void HTTPObject::onConnectFailed()
|
||||
{
|
||||
dFree(mHostName);
|
||||
dFree(mPath);
|
||||
dFree(mQuery);
|
||||
mHostName = 0;
|
||||
mPath = 0;
|
||||
mQuery = 0;
|
||||
Parent::onConnectFailed();
|
||||
}
|
||||
|
||||
|
||||
void HTTPObject::onDisconnect()
|
||||
{
|
||||
dFree(mHostName);
|
||||
dFree(mPath);
|
||||
dFree(mQuery);
|
||||
mHostName = 0;
|
||||
mPath = 0;
|
||||
mQuery = 0;
|
||||
Parent::onDisconnect();
|
||||
}
|
||||
|
||||
bool HTTPObject::processLine(U8 *line)
|
||||
{
|
||||
if(mParseState == ParsingStatusLine)
|
||||
{
|
||||
mParseState = ParsingHeader;
|
||||
}
|
||||
else if(mParseState == ParsingHeader)
|
||||
{
|
||||
if(!dStricmp((char *) line, "transfer-encoding: chunked"))
|
||||
mChunkedEncoding = true;
|
||||
if(line[0] == 0)
|
||||
{
|
||||
if(mChunkedEncoding)
|
||||
mParseState = ParsingChunkHeader;
|
||||
else
|
||||
mParseState = ProcessingBody;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(mParseState == ParsingChunkHeader)
|
||||
{
|
||||
if(line[0]) // strip off the crlf if necessary
|
||||
{
|
||||
mChunkSize = 0;
|
||||
S32 hexVal;
|
||||
while((hexVal = getHexVal(*line++)) != -1)
|
||||
{
|
||||
mChunkSize *= 16;
|
||||
mChunkSize += hexVal;
|
||||
}
|
||||
if(mBufferSave)
|
||||
{
|
||||
mBuffer = mBufferSave;
|
||||
mBufferSize = mBufferSaveSize;
|
||||
mBufferSave = 0;
|
||||
}
|
||||
if(mChunkSize)
|
||||
mParseState = ProcessingBody;
|
||||
else
|
||||
{
|
||||
mParseState = ProcessingDone;
|
||||
finishLastLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Parent::processLine((UTF8*)line);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
U32 HTTPObject::onDataReceive(U8 *buffer, U32 bufferLen)
|
||||
{
|
||||
U32 start = 0;
|
||||
parseLine(buffer, &start, bufferLen);
|
||||
return start;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
U32 HTTPObject::onReceive(U8 *buffer, U32 bufferLen)
|
||||
{
|
||||
if(mParseState == ProcessingBody)
|
||||
{
|
||||
if(mChunkedEncoding && bufferLen >= mChunkSize)
|
||||
{
|
||||
U32 ret = onDataReceive(buffer, mChunkSize);
|
||||
mChunkSize -= ret;
|
||||
if(mChunkSize == 0)
|
||||
{
|
||||
if(mBuffer)
|
||||
{
|
||||
mBufferSaveSize = mBufferSize;
|
||||
mBufferSave = mBuffer;
|
||||
mBuffer = 0;
|
||||
mBufferSize = 0;
|
||||
}
|
||||
mParseState = ParsingChunkHeader;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
U32 ret = onDataReceive(buffer, bufferLen);
|
||||
mChunkSize -= ret;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
else if(mParseState != ProcessingDone)
|
||||
{
|
||||
U32 start = 0;
|
||||
parseLine(buffer, &start, bufferLen);
|
||||
return start;
|
||||
}
|
||||
return bufferLen;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
DefineEngineMethod( HTTPObject, get, void, ( const char* Address, const char* requirstURI, const char* query ), ( "" ),
|
||||
"@brief Send a GET command to a server to send or retrieve data.\n\n"
|
||||
|
||||
"@param Address HTTP web address to send this get call to. Be sure to include the port at the end (IE: \"www.garagegames.com:80\").\n"
|
||||
"@param requirstURI Specific location on the server to access (IE: \"index.php\".)\n"
|
||||
"@param query Optional. Actual data to transmit to the server. Can be anything required providing it sticks with limitations of the HTTP protocol. "
|
||||
"If you were building the URL manually, this is the text that follows the question mark. For example: http://www.google.com/ig/api?<b>weather=Las-Vegas,US</b>\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Create an HTTP object for communications\n"
|
||||
"%httpObj = new HTTPObject();\n\n"
|
||||
"// Specify a URL to transmit to\n"
|
||||
"%url = \"www.garagegames.com:80\";\n\n"
|
||||
"// Specify a URI to communicate with\n"
|
||||
"%URI = \"/index.php\";\n\n"
|
||||
"// Specify a query to send.\n"
|
||||
"%query = \"\";\n\n"
|
||||
"// Send the GET command to the server\n"
|
||||
"%httpObj.get(%url,%URI,%query);\n"
|
||||
"@endtsexample\n\n"
|
||||
)
|
||||
{
|
||||
if( !query || !query[ 0 ] )
|
||||
object->get(Address, requirstURI, NULL);
|
||||
else
|
||||
object->get(Address, requirstURI, query);
|
||||
}
|
||||
|
||||
DefineEngineMethod( HTTPObject, post, void, ( const char* Address, const char* requirstURI, const char* query, const char* post ),,
|
||||
"@brief Send POST command to a server to send or retrieve data.\n\n"
|
||||
|
||||
"@param Address HTTP web address to send this get call to. Be sure to include the port at the end (IE: \"www.garagegames.com:80\").\n"
|
||||
"@param requirstURI Specific location on the server to access (IE: \"index.php\".)\n"
|
||||
"@param query Actual data to transmit to the server. Can be anything required providing it sticks with limitations of the HTTP protocol. \n"
|
||||
"@param post Submission data to be processed.\n"
|
||||
|
||||
"@note The post() method is currently non-functional.\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Create an HTTP object for communications\n"
|
||||
"%httpObj = new HTTPObject();\n\n"
|
||||
"// Specify a URL to transmit to\n"
|
||||
"%url = \"www.garagegames.com:80\";\n\n"
|
||||
"// Specify a URI to communicate with\n"
|
||||
"%URI = \"/index.php\";\n\n"
|
||||
"// Specify a query to send.\n"
|
||||
"%query = \"\";\n\n"
|
||||
"// Specify the submission data.\n"
|
||||
"%post = \"\";\n\n"
|
||||
"// Send the POST command to the server\n"
|
||||
"%httpObj.POST(%url,%URI,%query,%post);\n"
|
||||
"@endtsexample\n\n"
|
||||
)
|
||||
{
|
||||
object->post(Address, requirstURI, query, post);
|
||||
}
|
||||
81
Engine/source/app/net/httpObject.h
Normal file
81
Engine/source/app/net/httpObject.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _HTTPOBJECT_H_
|
||||
#define _HTTPOBJECT_H_
|
||||
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/util/tVector.h"
|
||||
#endif
|
||||
#ifndef _TCPOBJECT_H_
|
||||
#include "app/net/tcpObject.h"
|
||||
#endif
|
||||
|
||||
class HTTPObject : public TCPObject
|
||||
{
|
||||
private:
|
||||
typedef TCPObject Parent;
|
||||
protected:
|
||||
enum ParseState {
|
||||
ParsingStatusLine,
|
||||
ParsingHeader,
|
||||
ParsingChunkHeader,
|
||||
ProcessingBody,
|
||||
ProcessingDone,
|
||||
};
|
||||
ParseState mParseState;
|
||||
U32 mTotalBytes;
|
||||
U32 mBytesRemaining;
|
||||
public:
|
||||
U32 mStatus;
|
||||
F32 mVersion;
|
||||
U32 mContentLength;
|
||||
bool mChunkedEncoding;
|
||||
U32 mChunkSize;
|
||||
const char *mContentType;
|
||||
char *mHostName;
|
||||
char *mPath;
|
||||
char *mQuery;
|
||||
char *mPost;
|
||||
U8 *mBufferSave;
|
||||
U32 mBufferSaveSize;
|
||||
public:
|
||||
static void expandPath(char *dest, const char *path, U32 destSize);
|
||||
void get(const char *hostName, const char *urlName, const char *query);
|
||||
void post(const char *host, const char *path, const char *query, const char *post);
|
||||
HTTPObject();
|
||||
~HTTPObject();
|
||||
|
||||
//static HTTPObject *find(U32 tag);
|
||||
|
||||
virtual U32 onDataReceive(U8 *buffer, U32 bufferLen);
|
||||
virtual U32 onReceive(U8 *buffer, U32 bufferLen); // process a buffer of raw packet data
|
||||
virtual void onConnected();
|
||||
virtual void onConnectFailed();
|
||||
virtual void onDisconnect();
|
||||
bool processLine(U8 *line);
|
||||
|
||||
DECLARE_CONOBJECT(HTTPObject);
|
||||
};
|
||||
|
||||
|
||||
#endif // _H_HTTPOBJECT_
|
||||
417
Engine/source/app/net/net.cpp
Normal file
417
Engine/source/app/net/net.cpp
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "core/dnet.h"
|
||||
#include "core/idGenerator.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "console/simBase.h"
|
||||
#include "console/console.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "sim/netObject.h"
|
||||
#include "app/net/serverQuery.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// remote procedure call console functions
|
||||
//----------------------------------------------------------------
|
||||
|
||||
class RemoteCommandEvent : public NetEvent
|
||||
{
|
||||
public:
|
||||
typedef NetEvent Parent;
|
||||
enum {
|
||||
MaxRemoteCommandArgs = 20,
|
||||
CommandArgsBits = 5
|
||||
};
|
||||
|
||||
private:
|
||||
S32 mArgc;
|
||||
char *mArgv[MaxRemoteCommandArgs + 1];
|
||||
NetStringHandle mTagv[MaxRemoteCommandArgs + 1];
|
||||
static char mBuf[1024];
|
||||
public:
|
||||
RemoteCommandEvent(S32 argc=0, const char **argv=NULL, NetConnection *conn = NULL)
|
||||
{
|
||||
mArgc = argc;
|
||||
for(S32 i = 0; i < argc; i++)
|
||||
{
|
||||
if(argv[i][0] == StringTagPrefixByte)
|
||||
{
|
||||
char buffer[256];
|
||||
mTagv[i+1] = NetStringHandle(dAtoi(argv[i]+1));
|
||||
if(conn)
|
||||
{
|
||||
dSprintf(buffer + 1, sizeof(buffer) - 1, "%d", conn->getNetSendId(mTagv[i+1]));
|
||||
buffer[0] = StringTagPrefixByte;
|
||||
mArgv[i+1] = dStrdup(buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
mArgv[i+1] = dStrdup(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
const char *getDebugName()
|
||||
{
|
||||
static char buffer[256];
|
||||
dSprintf(buffer, sizeof(buffer), "%s [%s]", getClassName(), mTagv[1].isValidString() ? mTagv[1].getString() : "--unknown--" );
|
||||
return buffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
~RemoteCommandEvent()
|
||||
{
|
||||
for(S32 i = 0; i < mArgc; i++)
|
||||
dFree(mArgv[i+1]);
|
||||
}
|
||||
|
||||
virtual void pack(NetConnection* conn, BitStream *bstream)
|
||||
{
|
||||
bstream->writeInt(mArgc, CommandArgsBits);
|
||||
// write it out reversed... why?
|
||||
// automatic string substitution with later arguments -
|
||||
// handled automatically by the system.
|
||||
|
||||
for(S32 i = 0; i < mArgc; i++)
|
||||
conn->packString(bstream, mArgv[i+1]);
|
||||
}
|
||||
|
||||
virtual void write(NetConnection* conn, BitStream *bstream)
|
||||
{
|
||||
pack(conn, bstream);
|
||||
}
|
||||
|
||||
virtual void unpack(NetConnection* conn, BitStream *bstream)
|
||||
{
|
||||
|
||||
mArgc = bstream->readInt(CommandArgsBits);
|
||||
// read it out backwards
|
||||
for(S32 i = 0; i < mArgc; i++)
|
||||
{
|
||||
conn->unpackString(bstream, mBuf);
|
||||
mArgv[i+1] = dStrdup(mBuf);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void process(NetConnection *conn)
|
||||
{
|
||||
static char idBuf[10];
|
||||
|
||||
// de-tag the command name
|
||||
|
||||
for(S32 i = mArgc - 1; i >= 0; i--)
|
||||
{
|
||||
char *arg = mArgv[i+1];
|
||||
if(*arg == StringTagPrefixByte)
|
||||
{
|
||||
// it's a tag:
|
||||
U32 localTag = dAtoi(arg + 1);
|
||||
NetStringHandle tag = conn->translateRemoteStringId(localTag);
|
||||
NetStringTable::expandString( tag,
|
||||
mBuf,
|
||||
sizeof(mBuf),
|
||||
(mArgc - 1) - i,
|
||||
(const char**)(mArgv + i + 2) );
|
||||
dFree(mArgv[i+1]);
|
||||
mArgv[i+1] = dStrdup(mBuf);
|
||||
}
|
||||
}
|
||||
const char *rmtCommandName = dStrchr(mArgv[1], ' ') + 1;
|
||||
if(conn->isConnectionToServer())
|
||||
{
|
||||
dStrcpy(mBuf, "clientCmd");
|
||||
dStrcat(mBuf, rmtCommandName);
|
||||
|
||||
char *temp = mArgv[1];
|
||||
mArgv[1] = mBuf;
|
||||
|
||||
Con::execute(mArgc, (const char **) mArgv+1);
|
||||
mArgv[1] = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
dStrcpy(mBuf, "serverCmd");
|
||||
dStrcat(mBuf, rmtCommandName);
|
||||
char *temp = mArgv[1];
|
||||
|
||||
dSprintf(idBuf, sizeof(idBuf), "%d", conn->getId());
|
||||
mArgv[0] = mBuf;
|
||||
mArgv[1] = idBuf;
|
||||
|
||||
Con::execute(mArgc+1, (const char **) mArgv);
|
||||
mArgv[1] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
DECLARE_CONOBJECT(RemoteCommandEvent);
|
||||
};
|
||||
char RemoteCommandEvent::mBuf[1024];
|
||||
|
||||
IMPLEMENT_CO_NETEVENT_V1(RemoteCommandEvent);
|
||||
|
||||
ConsoleDocClass( RemoteCommandEvent,
|
||||
"@brief Object used for remote procedure calls.\n\n"
|
||||
"Not intended for game development, for exposing ConsoleFunctions (such as commandToClient) only.\n\n"
|
||||
"@internal");
|
||||
|
||||
static void sendRemoteCommand(NetConnection *conn, S32 argc, const char **argv)
|
||||
{
|
||||
if(U8(argv[0][0]) != StringTagPrefixByte)
|
||||
{
|
||||
Con::errorf(ConsoleLogEntry::Script, "Remote Command Error - command must be a tag.");
|
||||
return;
|
||||
}
|
||||
S32 i;
|
||||
for(i = argc - 1; i >= 0; i--)
|
||||
{
|
||||
if(argv[i][0] != 0)
|
||||
break;
|
||||
argc = i;
|
||||
}
|
||||
for(i = 0; i < argc; i++)
|
||||
conn->validateSendString(argv[i]);
|
||||
RemoteCommandEvent *cevt = new RemoteCommandEvent(argc, argv, conn);
|
||||
conn->postNetEvent(cevt);
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupBegin( Net, "Functions for use with the network; tagged strings and remote commands.");
|
||||
|
||||
ConsoleFunction( commandToServer, void, 2, RemoteCommandEvent::MaxRemoteCommandArgs + 1, "(string func, ...)"
|
||||
"@brief Send a command to the server.\n\n"
|
||||
|
||||
"@param func Name of the server command being called\n"
|
||||
"@param ... Various parameters being passed to server command\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Create a standard function. Needs to be executed on the client, such \n"
|
||||
"// as within scripts/client/default.bind.cs\n"
|
||||
"function toggleCamera(%val)\n"
|
||||
"{\n"
|
||||
" // If key was down, call a server command named 'ToggleCamera'\n"
|
||||
" if (%val)\n"
|
||||
" commandToServer('ToggleCamera');\n"
|
||||
"}\n\n"
|
||||
"// Server command being called from above. Needs to be executed on the \n"
|
||||
"// server, such as within scripts/server/commands.cs\n"
|
||||
"function serverCmdToggleCamera(%client)\n"
|
||||
"{\n"
|
||||
" if (%client.getControlObject() == %client.player)\n"
|
||||
" {\n"
|
||||
" %client.camera.setVelocity(\"0 0 0\");\n"
|
||||
" %control = %client.camera;\n"
|
||||
" }\n"
|
||||
" else\n"
|
||||
" {\n"
|
||||
" %client.player.setVelocity(\"0 0 0\");\n"
|
||||
" %control = %client.player;\n"
|
||||
" }\n"
|
||||
" %client.setControlObject(%control);\n"
|
||||
" clientCmdSyncEditorGui();\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@ingroup Networking")
|
||||
{
|
||||
NetConnection *conn = NetConnection::getConnectionToServer();
|
||||
if(!conn)
|
||||
return;
|
||||
sendRemoteCommand(conn, argc - 1, argv + 1);
|
||||
}
|
||||
|
||||
ConsoleFunction( commandToClient, void, 3, RemoteCommandEvent::MaxRemoteCommandArgs + 2, "(NetConnection client, string func, ...)"
|
||||
"@brief Send a command from the server to the client\n\n"
|
||||
|
||||
"@param client The numeric ID of a client GameConnection\n"
|
||||
"@param func Name of the client function being called\n"
|
||||
"@param ... Various parameters being passed to client command\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Set up the client command. Needs to be executed on the client, such as\n"
|
||||
"// within scripts/client/client.cs\n"
|
||||
"// Update the Ammo Counter with current ammo, if not any then hide the counter.\n"
|
||||
"function clientCmdSetAmmoAmountHud(%amount)\n"
|
||||
"{\n"
|
||||
" if (!%amount)\n"
|
||||
" AmmoAmount.setVisible(false);\n"
|
||||
" else\n"
|
||||
" {\n"
|
||||
" AmmoAmount.setVisible(true);\n"
|
||||
" AmmoAmount.setText(\"Ammo: \"@%amount);\n"
|
||||
" }\n"
|
||||
"}\n\n"
|
||||
"// Call it from a server function. Needs to be executed on the server, \n"
|
||||
"//such as within scripts/server/game.cs\n"
|
||||
"function GameConnection::setAmmoAmountHud(%client, %amount)\n"
|
||||
"{\n"
|
||||
" commandToClient(%client, 'SetAmmoAmountHud', %amount);\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@ingroup Networking\n")
|
||||
{
|
||||
NetConnection *conn;
|
||||
if(!Sim::findObject(argv[1], conn))
|
||||
return;
|
||||
sendRemoteCommand(conn, argc - 2, argv + 2);
|
||||
}
|
||||
|
||||
|
||||
ConsoleFunction(removeTaggedString, void, 2, 2, "(int tag)"
|
||||
"@brief Remove a tagged string from the Net String Table\n\n"
|
||||
|
||||
"@param tag The tag associated with the string\n\n"
|
||||
|
||||
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
|
||||
"@see addTaggedString()\n"
|
||||
"@see getTaggedString()\n"
|
||||
"@ingroup Networking\n")
|
||||
{
|
||||
gNetStringTable->removeString(dAtoi(argv[1]+1), true);
|
||||
}
|
||||
|
||||
ConsoleFunction( addTaggedString, const char*, 2, 2, "(string str)"
|
||||
"@brief Use the addTaggedString function to tag a new string and add it to the NetStringTable\n\n"
|
||||
|
||||
"@param str The string to be tagged and placed in the NetStringTable. Tagging ignores case, "
|
||||
"so tagging the same string (excluding case differences) will be ignored as a duplicated tag.\n\n"
|
||||
|
||||
"@return Returns a string( containing a numeric value) equivalent to the string ID for the newly tagged string"
|
||||
|
||||
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
|
||||
"@see removeTaggedString()\n"
|
||||
"@see getTaggedString()\n"
|
||||
"@ingroup Networking\n")
|
||||
{
|
||||
NetStringHandle s(argv[1]);
|
||||
gNetStringTable->incStringRefScript(s.getIndex());
|
||||
|
||||
char *ret = Con::getReturnBuffer(10);
|
||||
ret[0] = StringTagPrefixByte;
|
||||
dSprintf(ret + 1, 9, "%d", s.getIndex());
|
||||
return ret;
|
||||
}
|
||||
|
||||
ConsoleFunction( getTaggedString, const char*, 2, 2, "(int tag)"
|
||||
"@brief Use the getTaggedString function to convert a tag to a string.\n\n"
|
||||
|
||||
"This is not the same as detag() which can only be used within the context "
|
||||
"of a function that receives a tag. This function can be used any time and "
|
||||
"anywhere to convert a tag to a string.\n\n"
|
||||
|
||||
"@param tag A numeric tag ID.\n"
|
||||
|
||||
"@returns The string as found in the Net String table.\n"
|
||||
|
||||
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
|
||||
"@see addTaggedString()\n"
|
||||
"@see removeTaggedString()\n"
|
||||
"@ingroup Networking\n")
|
||||
{
|
||||
const char *indexPtr = argv[1];
|
||||
if (*indexPtr == StringTagPrefixByte)
|
||||
indexPtr++;
|
||||
return gNetStringTable->lookupString(dAtoi(indexPtr));
|
||||
}
|
||||
|
||||
ConsoleFunction( buildTaggedString, const char*, 2, 11, "(string format, ...)"
|
||||
"@brief Build a string using the specified tagged string format.\n\n"
|
||||
|
||||
"This function takes an already tagged string (passed in as a tagged string ID) and one "
|
||||
"or more additional strings. If the tagged string contains argument tags that range from "
|
||||
"%%1 through %%9, then each additional string will be substituted into the tagged string. "
|
||||
"The final (non-tagged) combined string will be returned. The maximum length of the tagged "
|
||||
"string plus any inserted additional strings is 511 characters.\n\n"
|
||||
|
||||
"@param format A tagged string ID that contains zero or more argument tags, in the form of "
|
||||
"%%1 through %%9.\n"
|
||||
"@param ... A variable number of arguments that are insterted into the tagged string "
|
||||
"based on the argument tags within the format string.\n"
|
||||
|
||||
"@returns An ordinary string that is a combination of the original tagged string with any additional "
|
||||
"strings passed in inserted in place of each argument tag.\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Create a tagged string with argument tags\n"
|
||||
"%taggedStringID = addTaggedString(\"Welcome %1 to the game!\");\n\n"
|
||||
|
||||
"// Some point later, combine the tagged string with some other string\n"
|
||||
"%string = buildTaggedString(%taggedStringID, %playerName);\n"
|
||||
"echo(%string);\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
|
||||
"@see addTaggedString()\n"
|
||||
"@see getTaggedString()\n"
|
||||
"@ingroup Networking\n")
|
||||
{
|
||||
const char *indexPtr = argv[1];
|
||||
if (*indexPtr == StringTagPrefixByte)
|
||||
indexPtr++;
|
||||
const char *fmtString = gNetStringTable->lookupString(dAtoi(indexPtr));
|
||||
char *strBuffer = Con::getReturnBuffer(512);
|
||||
const char *fmtStrPtr = fmtString;
|
||||
char *strBufPtr = strBuffer;
|
||||
S32 strMaxLength = 511;
|
||||
if (!fmtString)
|
||||
goto done;
|
||||
|
||||
//build the string
|
||||
while (*fmtStrPtr)
|
||||
{
|
||||
//look for an argument tag
|
||||
if (*fmtStrPtr == '%')
|
||||
{
|
||||
if (fmtStrPtr[1] >= '1' && fmtStrPtr[1] <= '9')
|
||||
{
|
||||
S32 argIndex = S32(fmtStrPtr[1] - '0') + 1;
|
||||
if (argIndex >= argc)
|
||||
goto done;
|
||||
const char *argStr = argv[argIndex];
|
||||
if (!argStr)
|
||||
goto done;
|
||||
S32 strLength = dStrlen(argStr);
|
||||
if (strLength > strMaxLength)
|
||||
goto done;
|
||||
dStrcpy(strBufPtr, argStr);
|
||||
strBufPtr += strLength;
|
||||
strMaxLength -= strLength;
|
||||
fmtStrPtr += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//if we don't continue, just copy the character
|
||||
if (strMaxLength <= 0)
|
||||
goto done;
|
||||
*strBufPtr++ = *fmtStrPtr++;
|
||||
strMaxLength--;
|
||||
}
|
||||
|
||||
done:
|
||||
*strBufPtr = '\0';
|
||||
return strBuffer;
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupEnd( Net );
|
||||
192
Engine/source/app/net/netTest.cpp
Normal file
192
Engine/source/app/net/netTest.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/simBase.h"
|
||||
#include "platform/event.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "sim/netObject.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
class SimpleMessageEvent : public NetEvent
|
||||
{
|
||||
char *msg;
|
||||
public:
|
||||
typedef NetEvent Parent;
|
||||
SimpleMessageEvent(const char *message = NULL)
|
||||
{
|
||||
if(message)
|
||||
msg = dStrdup(message);
|
||||
else
|
||||
msg = NULL;
|
||||
}
|
||||
~SimpleMessageEvent()
|
||||
{ dFree(msg); }
|
||||
|
||||
virtual void pack(NetConnection* /*ps*/, BitStream *bstream)
|
||||
{ bstream->writeString(msg); }
|
||||
virtual void write(NetConnection*, BitStream *bstream)
|
||||
{ bstream->writeString(msg); }
|
||||
virtual void unpack(NetConnection* /*ps*/, BitStream *bstream)
|
||||
{ char buf[256]; bstream->readString(buf); msg = dStrdup(buf); }
|
||||
virtual void process(NetConnection *)
|
||||
{ Con::printf("RMSG %d %s", mSourceId, msg); }
|
||||
|
||||
DECLARE_CONOBJECT(SimpleMessageEvent);
|
||||
};
|
||||
|
||||
IMPLEMENT_CO_NETEVENT_V1(SimpleMessageEvent);
|
||||
|
||||
ConsoleDocClass( SimpleMessageEvent,
|
||||
"@brief A very simple example of a network event.\n\n"
|
||||
|
||||
"This object exists purely for instructional purposes. It is primarily "
|
||||
"geared toward developers that wish to understand the inner-working of "
|
||||
"Torque 3D's networking system. This is not intended for actual game "
|
||||
"development.\n\n "
|
||||
|
||||
"@see NetEvent for the inner workings of network events\n\n"
|
||||
|
||||
"@ingroup Networking\n");
|
||||
|
||||
DefineEngineStaticMethod( SimpleMessageEvent, msg, void, (NetConnection* con, const char* message),,
|
||||
"@brief Send a SimpleMessageEvent message to the specified connection.\n\n"
|
||||
|
||||
"The far end that receives the message will print the message out to the console.\n"
|
||||
|
||||
"@param con The unique ID of the connection to transmit to\n"
|
||||
"@param message The string containing the message to transmit\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Send a message to the other end of the given NetConnection\n"
|
||||
"SimpleMessageEvent::msg( %conn, \"A message from me!\");\n\n"
|
||||
|
||||
"// The far end will see the following in the console\n"
|
||||
"// (Note: The number may be something other than 1796 as it is the SimObjectID\n"
|
||||
"// of the received event)\n"
|
||||
"// \n"
|
||||
"// RMSG 1796 A message from me!\n"
|
||||
"@endtsexample\n\n"
|
||||
)
|
||||
{
|
||||
//NetConnection *con = (NetConnection *) Sim::findObject(argv[1]);
|
||||
|
||||
if(con)
|
||||
con->postNetEvent(new SimpleMessageEvent(message));
|
||||
}
|
||||
|
||||
//ConsoleFunction( msg, void, 3, 3, "(NetConnection id, string message)"
|
||||
// "Send a SimpleNetObject message to the specified connection.")
|
||||
//{
|
||||
// NetConnection *con = (NetConnection *) Sim::findObject(argv[1]);
|
||||
// if(con)
|
||||
// con->postNetEvent(new SimpleMessageEvent(argv[2]));
|
||||
//}
|
||||
|
||||
class SimpleNetObject : public NetObject
|
||||
{
|
||||
typedef NetObject Parent;
|
||||
public:
|
||||
char message[256];
|
||||
SimpleNetObject()
|
||||
{
|
||||
mNetFlags.set(ScopeAlways | Ghostable);
|
||||
dStrcpy(message, "Hello World!");
|
||||
}
|
||||
U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
|
||||
{
|
||||
stream->writeString(message);
|
||||
return 0;
|
||||
}
|
||||
void unpackUpdate(NetConnection *conn, BitStream *stream)
|
||||
{
|
||||
stream->readString(message);
|
||||
Con::printf("Got message: %s", message);
|
||||
}
|
||||
void setMessage(const char *msg)
|
||||
{
|
||||
setMaskBits(1);
|
||||
dStrcpy(message, msg);
|
||||
}
|
||||
|
||||
DECLARE_CONOBJECT(SimpleNetObject);
|
||||
};
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject);
|
||||
|
||||
ConsoleDocClass( SimpleNetObject,
|
||||
"@brief A very simple example of a class derived from NetObject.\n\n"
|
||||
|
||||
"This object exists purely for instructional purposes. It is primarily "
|
||||
"geared toward developers that wish to understand the inner-working of "
|
||||
"Torque 3D's networking system. This is not intended for actual game "
|
||||
"development.\n\n "
|
||||
|
||||
"@tsexample\n"
|
||||
"// On the server, create a new SimpleNetObject. This is a ghost always\n"
|
||||
"// object so it will be immediately ghosted to all connected clients.\n"
|
||||
"$s = new SimpleNetObject();\n\n"
|
||||
|
||||
"// All connected clients will see the following in their console:\n"
|
||||
"// \n"
|
||||
"// Got message: Hello World!\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see NetObject for a full breakdown of this example object\n"
|
||||
|
||||
"@ingroup Networking\n");
|
||||
|
||||
DefineEngineMethod( SimpleNetObject, setMessage, void, (const char* msg),,
|
||||
"@brief Sets the internal message variable.\n\n"
|
||||
|
||||
"SimpleNetObject is set up to automatically transmit this new message to "
|
||||
"all connected clients. It will appear in the clients' console.\n"
|
||||
|
||||
"@param msg The new message to send\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// On the server, create a new SimpleNetObject. This is a ghost always\n"
|
||||
"// object so it will be immediately ghosted to all connected clients.\n"
|
||||
"$s = new SimpleNetObject();\n\n"
|
||||
|
||||
"// All connected clients will see the following in their console:\n"
|
||||
"// \n"
|
||||
"// Got message: Hello World!\n\n"
|
||||
|
||||
"// Now again on the server, change the message. This will cause it to\n"
|
||||
"// be sent to all connected clients.\n"
|
||||
"$s.setMessage(\"A new message from me!\");\n\n"
|
||||
|
||||
"// All connected clients will now see in their console:\n"
|
||||
"// \n"
|
||||
"// Go message: A new message from me!\n"
|
||||
"@endtsexample\n\n"
|
||||
)
|
||||
{
|
||||
object->setMessage(msg);
|
||||
}
|
||||
|
||||
//ConsoleMethod( SimpleNetObject, setMessage, void, 3, 3, "(string msg)")
|
||||
//{
|
||||
// object->setMessage(argv[2]);
|
||||
//}
|
||||
2139
Engine/source/app/net/serverQuery.cpp
Normal file
2139
Engine/source/app/net/serverQuery.cpp
Normal file
File diff suppressed because it is too large
Load diff
128
Engine/source/app/net/serverQuery.h
Normal file
128
Engine/source/app/net/serverQuery.h
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _SERVERQUERY_H_
|
||||
#define _SERVERQUERY_H_
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _EVENT_H_
|
||||
#include "platform/event.h"
|
||||
#endif
|
||||
#ifndef _BITSET_H_
|
||||
#include "core/bitSet.h"
|
||||
#endif
|
||||
|
||||
#include "platform/platformNet.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Game Server Information
|
||||
|
||||
struct ServerInfo
|
||||
{
|
||||
enum StatusFlags
|
||||
{
|
||||
// Info flags (0-7):
|
||||
Status_Dedicated = BIT(0),
|
||||
Status_Passworded = BIT(1),
|
||||
Status_Linux = BIT(2),
|
||||
Status_Xenon = BIT(6),
|
||||
|
||||
// Status flags:
|
||||
Status_New = 0,
|
||||
Status_Querying = BIT(28),
|
||||
Status_Updating = BIT(29),
|
||||
Status_Responded = BIT(30),
|
||||
Status_TimedOut = BIT(31),
|
||||
};
|
||||
|
||||
U8 numPlayers;
|
||||
U8 maxPlayers;
|
||||
U8 numBots;
|
||||
char* name;
|
||||
char* gameType;
|
||||
char* missionName;
|
||||
char* missionType;
|
||||
char* statusString;
|
||||
char* infoString;
|
||||
NetAddress address;
|
||||
U32 version;
|
||||
U32 ping;
|
||||
U32 cpuSpeed;
|
||||
bool isFavorite;
|
||||
BitSet32 status;
|
||||
|
||||
ServerInfo()
|
||||
{
|
||||
numPlayers = 0;
|
||||
maxPlayers = 0;
|
||||
numBots = 0;
|
||||
name = NULL;
|
||||
gameType = NULL;
|
||||
missionType = NULL;
|
||||
missionName = NULL;
|
||||
statusString = NULL;
|
||||
infoString = NULL;
|
||||
version = 0;
|
||||
ping = 0;
|
||||
cpuSpeed = 0;
|
||||
isFavorite = false;
|
||||
status = Status_New;
|
||||
}
|
||||
~ServerInfo();
|
||||
|
||||
bool isNew() { return( status == Status_New ); }
|
||||
bool isQuerying() { return( status.test( Status_Querying ) ); }
|
||||
bool isUpdating() { return( status.test( Status_Updating ) ); }
|
||||
bool hasResponded() { return( status.test( Status_Responded ) ); }
|
||||
bool isTimedOut() { return( status.test( Status_TimedOut ) ); }
|
||||
|
||||
bool isDedicated() { return( status.test( Status_Dedicated ) ); }
|
||||
bool isPassworded() { return( status.test( Status_Passworded ) ); }
|
||||
bool isLinux() { return( status.test( Status_Linux ) ); }
|
||||
bool isXenon() { return( status.test( Status_Xenon ) ); }
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
extern Vector<ServerInfo> gServerList;
|
||||
extern bool gServerBrowserDirty;
|
||||
extern void clearServerList();
|
||||
extern void queryLanServers(U32 port, U8 flags, const char* gameType, const char* missionType,
|
||||
U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, U16 minCPU,
|
||||
U8 filterFlags);
|
||||
extern void queryMasterGameTypes();
|
||||
extern void queryMasterServer(U8 flags, const char* gameType, const char* missionType,
|
||||
U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, U16 minCPU,
|
||||
U8 filterFlags, U8 buddyCount, U32* buddyList );
|
||||
extern void queryFavoriteServers( U8 flags );
|
||||
extern void querySingleServer(const NetAddress* addr, U8 flags);
|
||||
extern void startHeartbeat();
|
||||
extern void sendHeartbeat( U8 flags );
|
||||
|
||||
#ifdef TORQUE_DEBUG
|
||||
extern void addFakeServers( S32 howMany );
|
||||
#endif // DEBUG
|
||||
|
||||
#endif
|
||||
521
Engine/source/app/net/tcpObject.cpp
Normal file
521
Engine/source/app/net/tcpObject.cpp
Normal file
|
|
@ -0,0 +1,521 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "app/net/tcpObject.h"
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "platform/event.h"
|
||||
#include "console/simBase.h"
|
||||
#include "console/consoleInternal.h"
|
||||
#include "core/strings/stringUnit.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
TCPObject *TCPObject::table[TCPObject::TableSize] = {0, };
|
||||
|
||||
IMPLEMENT_CONOBJECT(TCPObject);
|
||||
|
||||
ConsoleDocClass( TCPObject,
|
||||
"@brief Allows communications between the game and a server using TCP/IP protocols.\n\n"
|
||||
|
||||
"To use TCPObject you set up a connection to a server, send data to the server, and handle "
|
||||
"each line of the server's response using a callback. Once you are done communicating with "
|
||||
"the server, you disconnect.\n\n"
|
||||
|
||||
"TCPObject is intended to be used with text based protocols which means you'll need to "
|
||||
"delineate the server's response with an end-of-line character. i.e. the newline "
|
||||
"character @\\n. You may optionally include the carriage return character @\\r prior to the newline "
|
||||
"and TCPObject will strip it out before sending the line to the callback. If a newline "
|
||||
"character is not included in the server's output, the received data will not be "
|
||||
"processed until you disconnect from the server (which flushes the internal buffer).\n\n"
|
||||
|
||||
"TCPObject may also be set up to listen to a specific port, making Torque into a TCP server. "
|
||||
"When used in this manner, a callback is received when a client connection is made. Following "
|
||||
"the outside connection, text may be sent and lines are processed in the usual manner.\n\n"
|
||||
|
||||
"If you want to work with HTTP you may wish to use HTTPObject instead as it handles all of the "
|
||||
"HTTP header setup and parsing.\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// In this example we'll retrieve the new forum threads RSS\n"
|
||||
"// feed from garagegames.com. As we're using TCPObject, the\n"
|
||||
"// raw text response will be received from the server, including\n"
|
||||
"// the HTTP header.\n\n"
|
||||
|
||||
"// Define callbacks for our specific TCPObject using our instance's\n"
|
||||
"// name (RSSFeed) as the namespace.\n\n"
|
||||
|
||||
"// Handle an issue with resolving the server's name\n"
|
||||
"function RSSFeed::onDNSFailed(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"DNSFailed\";\n\n"
|
||||
|
||||
" // Handle DNS failure\n"
|
||||
"}\n\n"
|
||||
|
||||
"function RSSFeed::onConnectFailed(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"ConnectFailed\";\n\n"
|
||||
" // Handle connection failure\n"
|
||||
"}\n\n"
|
||||
|
||||
"function RSSFeed::onDNSResolved(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"DNSResolved\";\n\n"
|
||||
"}\n\n"
|
||||
|
||||
"function RSSFeed::onConnected(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"Connected\";\n\n"
|
||||
"}\n\n"
|
||||
|
||||
"function RSSFeed::onDisconnect(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"Disconnected\";\n\n"
|
||||
"}\n\n"
|
||||
|
||||
"// Handle a line from the server\n"
|
||||
"function RSSFeed::onLine(%this, %line)\n"
|
||||
"{\n"
|
||||
" // Print the line to the console\n"
|
||||
" echo( %line );\n"
|
||||
"}\n\n"
|
||||
|
||||
"// Create the TCPObject\n"
|
||||
"%rss = new TCPObject(RSSFeed);\n\n"
|
||||
|
||||
"// Define a dynamic field to store the last connection state\n"
|
||||
"%rss.lastState = \"None\";\n\n"
|
||||
|
||||
"// Connect to the server\n"
|
||||
"%rss.connect(\"www.garagegames.com:80\");\n\n"
|
||||
|
||||
"// Send the RSS feed request to the server. Response will be\n"
|
||||
"// handled in onLine() callback above\n"
|
||||
"%rss.send(\"GET /feeds/rss/threads HTTP/1.1\\r\\nHost: www.garagegames.com\\r\\n\\r\\n\");\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see HTTPObject\n"
|
||||
|
||||
"@ingroup Networking\n"
|
||||
);
|
||||
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onConnectionRequest, void, (const char* address, const char* ID), (address, ID),
|
||||
"@brief Called whenever a connection request is made.\n\n"
|
||||
"This callback is used when the TCPObject is listening to a port and a client is attempting to connect.\n"
|
||||
"@param address Server address connecting from.\n"
|
||||
"@param ID Connection ID\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onLine, void, (const char* line), (line),
|
||||
"@brief Called whenever a line of data is sent to this TCPObject.\n\n"
|
||||
"This callback is called when the received data contains a newline @\\n character, or "
|
||||
"the connection has been disconnected and the TCPObject's buffer is flushed.\n"
|
||||
"@param line Data sent from the server.\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onDNSResolved, void, (),(),
|
||||
"Called whenever the DNS has been resolved.\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onDNSFailed, void, (),(),
|
||||
"Called whenever the DNS has failed to resolve.\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onConnected, void, (),(),
|
||||
"Called whenever a connection is established with a server.\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onConnectFailed, void, (),(),
|
||||
"Called whenever a connection has failed to be established with a server.\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onDisconnect, void, (),(),
|
||||
"Called whenever the TCPObject disconnects from whatever it is currently connected to.\n"
|
||||
);
|
||||
|
||||
TCPObject *TCPObject::find(NetSocket tag)
|
||||
{
|
||||
for(TCPObject *walk = table[U32(tag) & TableMask]; walk; walk = walk->mNext)
|
||||
if(walk->mTag == tag)
|
||||
return walk;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void TCPObject::addToTable(NetSocket newTag)
|
||||
{
|
||||
removeFromTable();
|
||||
mTag = newTag;
|
||||
mNext = table[U32(mTag) & TableMask];
|
||||
table[U32(mTag) & TableMask] = this;
|
||||
}
|
||||
|
||||
void TCPObject::removeFromTable()
|
||||
{
|
||||
for(TCPObject **walk = &table[U32(mTag) & TableMask]; *walk; walk = &((*walk)->mNext))
|
||||
{
|
||||
if(*walk == this)
|
||||
{
|
||||
*walk = mNext;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processConnectedReceiveEvent(NetSocket sock, RawData incomingData);
|
||||
void processConnectedAcceptEvent(NetSocket listeningPort, NetSocket newConnection, NetAddress originatingAddress);
|
||||
void processConnectedNotifyEvent( NetSocket sock, U32 state );
|
||||
|
||||
S32 gTCPCount = 0;
|
||||
|
||||
TCPObject::TCPObject()
|
||||
{
|
||||
mBuffer = NULL;
|
||||
mBufferSize = 0;
|
||||
mPort = 0;
|
||||
mTag = InvalidSocket;
|
||||
mNext = NULL;
|
||||
mState = Disconnected;
|
||||
|
||||
gTCPCount++;
|
||||
|
||||
if(gTCPCount == 1)
|
||||
{
|
||||
Net::smConnectionAccept.notify(processConnectedAcceptEvent);
|
||||
Net::smConnectionReceive.notify(processConnectedReceiveEvent);
|
||||
Net::smConnectionNotify.notify(processConnectedNotifyEvent);
|
||||
}
|
||||
}
|
||||
|
||||
TCPObject::~TCPObject()
|
||||
{
|
||||
disconnect();
|
||||
dFree(mBuffer);
|
||||
|
||||
gTCPCount--;
|
||||
|
||||
if(gTCPCount == 0)
|
||||
{
|
||||
Net::smConnectionAccept.remove(processConnectedAcceptEvent);
|
||||
Net::smConnectionReceive.remove(processConnectedReceiveEvent);
|
||||
Net::smConnectionNotify.remove(processConnectedNotifyEvent);
|
||||
}
|
||||
}
|
||||
|
||||
bool TCPObject::processArguments(S32 argc, const char **argv)
|
||||
{
|
||||
if(argc == 0)
|
||||
return true;
|
||||
else if(argc == 1)
|
||||
{
|
||||
addToTable(U32(dAtoi(argv[0])));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TCPObject::onAdd()
|
||||
{
|
||||
if(!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
const char *name = getName();
|
||||
|
||||
if(name && name[0] && getClassRep())
|
||||
{
|
||||
Namespace *parent = getClassRep()->getNameSpace();
|
||||
Con::linkNamespaces(parent->mName, name);
|
||||
mNameSpace = Con::lookupNamespace(name);
|
||||
|
||||
}
|
||||
|
||||
Sim::getTCPGroup()->addObject(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
U32 TCPObject::onReceive(U8 *buffer, U32 bufferLen)
|
||||
{
|
||||
// we got a raw buffer event
|
||||
// default action is to split the buffer into lines of text
|
||||
// and call processLine on each
|
||||
// for any incomplete lines we have mBuffer
|
||||
U32 start = 0;
|
||||
parseLine(buffer, &start, bufferLen);
|
||||
return start;
|
||||
}
|
||||
|
||||
void TCPObject::parseLine(U8 *buffer, U32 *start, U32 bufferLen)
|
||||
{
|
||||
// find the first \n in buffer
|
||||
U32 i;
|
||||
U8 *line = buffer + *start;
|
||||
|
||||
for(i = *start; i < bufferLen; i++)
|
||||
if(buffer[i] == '\n' || buffer[i] == 0)
|
||||
break;
|
||||
U32 len = i - *start;
|
||||
|
||||
if(i == bufferLen || mBuffer)
|
||||
{
|
||||
// we've hit the end with no newline
|
||||
mBuffer = (U8 *) dRealloc(mBuffer, mBufferSize + len + 1);
|
||||
dMemcpy(mBuffer + mBufferSize, line, len);
|
||||
mBufferSize += len;
|
||||
*start = i;
|
||||
|
||||
// process the line
|
||||
if(i != bufferLen)
|
||||
{
|
||||
mBuffer[mBufferSize] = 0;
|
||||
if(mBufferSize && mBuffer[mBufferSize-1] == '\r')
|
||||
mBuffer[mBufferSize - 1] = 0;
|
||||
U8 *temp = mBuffer;
|
||||
mBuffer = 0;
|
||||
mBufferSize = 0;
|
||||
|
||||
processLine((UTF8*)temp);
|
||||
dFree(temp);
|
||||
}
|
||||
}
|
||||
else if(i != bufferLen)
|
||||
{
|
||||
line[len] = 0;
|
||||
if(len && line[len-1] == '\r')
|
||||
line[len-1] = 0;
|
||||
processLine((UTF8*)line);
|
||||
}
|
||||
if(i != bufferLen)
|
||||
*start = i + 1;
|
||||
}
|
||||
|
||||
void TCPObject::onConnectionRequest(const NetAddress *addr, U32 connectId)
|
||||
{
|
||||
char idBuf[16];
|
||||
char addrBuf[256];
|
||||
Net::addressToString(addr, addrBuf);
|
||||
dSprintf(idBuf, sizeof(idBuf), "%d", connectId);
|
||||
onConnectionRequest_callback(addrBuf,idBuf);
|
||||
}
|
||||
|
||||
bool TCPObject::processLine(UTF8 *line)
|
||||
{
|
||||
onLine_callback(line);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TCPObject::onDNSResolved()
|
||||
{
|
||||
mState = DNSResolved;
|
||||
onDNSResolved_callback();
|
||||
}
|
||||
|
||||
void TCPObject::onDNSFailed()
|
||||
{
|
||||
mState = Disconnected;
|
||||
onDNSFailed_callback();
|
||||
}
|
||||
|
||||
|
||||
void TCPObject::onConnected()
|
||||
{
|
||||
mState = Connected;
|
||||
onConnected_callback();
|
||||
}
|
||||
|
||||
void TCPObject::onConnectFailed()
|
||||
{
|
||||
mState = Disconnected;
|
||||
onConnectFailed_callback();
|
||||
}
|
||||
|
||||
void TCPObject::finishLastLine()
|
||||
{
|
||||
if(mBufferSize)
|
||||
{
|
||||
mBuffer[mBufferSize] = 0;
|
||||
processLine((UTF8*)mBuffer);
|
||||
dFree(mBuffer);
|
||||
mBuffer = 0;
|
||||
mBufferSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TCPObject::onDisconnect()
|
||||
{
|
||||
finishLastLine();
|
||||
mState = Disconnected;
|
||||
onDisconnect_callback();
|
||||
}
|
||||
|
||||
void TCPObject::listen(U16 port)
|
||||
{
|
||||
mState = Listening;
|
||||
U32 newTag = Net::openListenPort(port);
|
||||
addToTable(newTag);
|
||||
}
|
||||
|
||||
void TCPObject::connect(const char *address)
|
||||
{
|
||||
NetSocket newTag = Net::openConnectTo(address);
|
||||
addToTable(newTag);
|
||||
}
|
||||
|
||||
void TCPObject::disconnect()
|
||||
{
|
||||
if( mTag != InvalidSocket ) {
|
||||
Net::closeConnectTo(mTag);
|
||||
}
|
||||
removeFromTable();
|
||||
}
|
||||
|
||||
void TCPObject::send(const U8 *buffer, U32 len)
|
||||
{
|
||||
Net::sendtoSocket(mTag, buffer, S32(len));
|
||||
}
|
||||
|
||||
DefineEngineMethod(TCPObject, send, void, (const char *data),,
|
||||
"@brief Transmits the data string to the connected computer.\n\n"
|
||||
|
||||
"This method is used to send text data to the connected computer regardless if we initiated the "
|
||||
"connection using connect(), or listening to a port using listen().\n"
|
||||
|
||||
"@param data The data string to send.\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Set the command data\n"
|
||||
"%data = \"GET \" @ $RSSFeed::serverURL @ \" HTTP/1.0\\r\\n\";\n"
|
||||
"%data = %data @ \"Host: \" @ $RSSFeed::serverName @ \"\\r\\n\";\n"
|
||||
"%data = %data @ \"User-Agent: \" @ $RSSFeed::userAgent @ \"\\r\\n\\r\\n\"\n\n"
|
||||
|
||||
"// Send the command to the connected server.\n"
|
||||
"%thisTCPObj.send(%data);\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
object->send( (const U8*)data, dStrlen(data) );
|
||||
}
|
||||
|
||||
DefineEngineMethod(TCPObject, listen, void, (int port),,
|
||||
"@brief Start listening on the specified port for connections.\n\n"
|
||||
|
||||
"@param port Port for this TCPObject to start listening for connections on.\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Set the port number list\n"
|
||||
"%portNumber = 80;\n\n"
|
||||
|
||||
"// Inform this TCPObject to start listening at the specified port.\n"
|
||||
"%thisTCPObj.send(%portNumber);\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
object->listen(U32(port));
|
||||
}
|
||||
|
||||
DefineEngineMethod(TCPObject, connect, void, (const char* address),,
|
||||
"@brief Connect to the given address.\n\n"
|
||||
|
||||
"@param address Server address (including port) to connect to.\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Set the address.\n"
|
||||
"%address = \"www.garagegames.com:80\";\n\n"
|
||||
|
||||
"// Inform this TCPObject to connect to the specified address.\n"
|
||||
"%thisTCPObj.connect(%address);\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
object->connect(address);
|
||||
}
|
||||
|
||||
DefineEngineMethod(TCPObject, disconnect, void, (),,
|
||||
"@brief Disconnect from whatever this TCPObject is currently connected to, if anything.\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Inform this TCPObject to disconnect from anything it is currently connected to.\n"
|
||||
"%thisTCPObj.disconnect();\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
object->disconnect();
|
||||
}
|
||||
|
||||
void processConnectedReceiveEvent(NetSocket sock, RawData incomingData)
|
||||
{
|
||||
TCPObject *tcpo = TCPObject::find(sock);
|
||||
if(!tcpo)
|
||||
{
|
||||
Con::printf("Got bad connected receive event.");
|
||||
return;
|
||||
}
|
||||
|
||||
U32 size = incomingData.size;
|
||||
U8 *buffer = (U8*)incomingData.data;
|
||||
|
||||
while(size)
|
||||
{
|
||||
U32 ret = tcpo->onReceive(buffer, size);
|
||||
AssertFatal(ret <= size, "Invalid return size");
|
||||
size -= ret;
|
||||
buffer += ret;
|
||||
}
|
||||
}
|
||||
|
||||
void processConnectedAcceptEvent(NetSocket listeningPort, NetSocket newConnection, NetAddress originatingAddress)
|
||||
{
|
||||
TCPObject *tcpo = TCPObject::find(listeningPort);
|
||||
if(!tcpo)
|
||||
return;
|
||||
|
||||
tcpo->onConnectionRequest(&originatingAddress, newConnection);
|
||||
}
|
||||
|
||||
void processConnectedNotifyEvent( NetSocket sock, U32 state )
|
||||
{
|
||||
TCPObject *tcpo = TCPObject::find(sock);
|
||||
if(!tcpo)
|
||||
return;
|
||||
|
||||
switch(state)
|
||||
{
|
||||
case Net::DNSResolved:
|
||||
tcpo->onDNSResolved();
|
||||
break;
|
||||
case Net::DNSFailed:
|
||||
tcpo->onDNSFailed();
|
||||
break;
|
||||
case Net::Connected:
|
||||
tcpo->onConnected();
|
||||
break;
|
||||
case Net::ConnectFailed:
|
||||
tcpo->onConnectFailed();
|
||||
break;
|
||||
case Net::Disconnected:
|
||||
tcpo->onDisconnect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
96
Engine/source/app/net/tcpObject.h
Normal file
96
Engine/source/app/net/tcpObject.h
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _TCPOBJECT_H_
|
||||
#define _TCPOBJECT_H_
|
||||
|
||||
#ifndef _SIMBASE_H_
|
||||
#include "console/simBase.h"
|
||||
#endif
|
||||
|
||||
#include "platform/platformNet.h"
|
||||
|
||||
class TCPObject : public SimObject
|
||||
{
|
||||
public:
|
||||
enum State {Disconnected, DNSResolved, Connected, Listening };
|
||||
|
||||
DECLARE_CALLBACK(void, onConnectionRequest, (const char* address, const char* ID));
|
||||
DECLARE_CALLBACK(void, onLine, (const char* line));
|
||||
DECLARE_CALLBACK(void, onDNSResolved,());
|
||||
DECLARE_CALLBACK(void, onDNSFailed, ());
|
||||
DECLARE_CALLBACK(void, onConnected, ());
|
||||
DECLARE_CALLBACK(void, onConnectFailed, ());
|
||||
DECLARE_CALLBACK(void, onDisconnect, ());
|
||||
|
||||
private:
|
||||
NetSocket mTag;
|
||||
TCPObject *mNext;
|
||||
enum { TableSize = 256, TableMask = 0xFF };
|
||||
static TCPObject *table[TableSize];
|
||||
State mState;
|
||||
|
||||
protected:
|
||||
typedef SimObject Parent;
|
||||
U8 *mBuffer;
|
||||
U32 mBufferSize;
|
||||
U16 mPort;
|
||||
|
||||
public:
|
||||
TCPObject();
|
||||
virtual ~TCPObject();
|
||||
|
||||
void parseLine(U8 *buffer, U32 *start, U32 bufferLen);
|
||||
void finishLastLine();
|
||||
|
||||
static TCPObject *find(NetSocket tag);
|
||||
|
||||
// onReceive gets called continuously until all bytes are processed
|
||||
// return # of bytes processed each time.
|
||||
virtual U32 onReceive(U8 *buffer, U32 bufferLen); // process a buffer of raw packet data
|
||||
virtual bool processLine(UTF8 *line); // process a complete line of text... default action is to call into script
|
||||
virtual void onDNSResolved();
|
||||
virtual void onDNSFailed();
|
||||
virtual void onConnected();
|
||||
virtual void onConnectFailed();
|
||||
virtual void onConnectionRequest(const NetAddress *addr, U32 connectId);
|
||||
virtual void onDisconnect();
|
||||
void connect(const char *address);
|
||||
void listen(U16 port);
|
||||
void disconnect();
|
||||
State getState() { return mState; }
|
||||
|
||||
bool processArguments(S32 argc, const char **argv);
|
||||
void send(const U8 *buffer, U32 bufferLen);
|
||||
void addToTable(NetSocket newTag);
|
||||
void removeFromTable();
|
||||
|
||||
void setPort(U16 port) { mPort = port; }
|
||||
|
||||
bool onAdd();
|
||||
|
||||
DECLARE_CONOBJECT(TCPObject);
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // _H_TCPOBJECT_
|
||||
Loading…
Add table
Add a link
Reference in a new issue