-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Fixed crash in context if the engine was shutdown while a context w…
…as still suspended - Implemented a script socket add-on - asrun can now use sockets to setup a simple server, or connect to remote sockets git-svn-id: http://svn.code.sf.net/p/angelscript/code/trunk@2951 404ce1b2-830e-0410-a2e2-b09542c77caf
- Loading branch information
angelcode
committed
Sep 2, 2024
1 parent
bc5eb66
commit 9fcfbc1
Showing
13 changed files
with
351 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
#include "scriptsocket.h" | ||
#include <assert.h> | ||
|
||
BEGIN_AS_NAMESPACE | ||
|
||
#ifdef _WIN32 | ||
|
||
// Link with ws2_32.lib | ||
#pragma comment(lib, "Ws2_32.lib") | ||
|
||
// Manage the required calls to WSAStartup and WSACleanup | ||
class WindowsSocketLib | ||
{ | ||
public: | ||
WindowsSocketLib() : m_status(0) | ||
{ | ||
WORD wVersionRequested = 0x0202; | ||
WSADATA wsadata; | ||
// WSAStartup can be called multiple times, so it is not a problem if another piece of code also called it | ||
m_status = WSAStartup(wVersionRequested, &wsadata); | ||
if (m_status != 0) | ||
{ | ||
// No usable WINSOCK.DLL found | ||
} | ||
else if (wsadata.wVersion != wVersionRequested) | ||
{ | ||
// WINSOCK.DLL does not support version 2.2 | ||
WSACleanup(); | ||
m_status = -1; | ||
} | ||
} | ||
|
||
~WindowsSocketLib() | ||
{ | ||
// WSACleanup must be called for each successful call to WSAStartup | ||
if( m_status == 0 ) | ||
WSACleanup(); | ||
} | ||
|
||
int m_status; | ||
} g_windowsSocketLib; | ||
#endif | ||
|
||
CScriptSocket::CScriptSocket() : m_refCount(1), m_socket(-1), m_isListening(false) | ||
{ | ||
// TODO: On Windows check if the Windows Socket was properly loaded, else raise a script exception | ||
} | ||
|
||
void CScriptSocket::AddRef() const | ||
{ | ||
asAtomicInc(m_refCount);; | ||
} | ||
|
||
void CScriptSocket::Release() const | ||
{ | ||
if (asAtomicDec(m_refCount) == 0) | ||
delete this; | ||
} | ||
|
||
CScriptSocket::~CScriptSocket() | ||
{ | ||
// Disconnect the socket if it is open | ||
Close(); | ||
} | ||
|
||
int CScriptSocket::Listen(asWORD port) | ||
{ | ||
// If another socket is already used it must first be closed, and the working thread must be shutdown | ||
if (m_socket != -1) | ||
return -1; | ||
|
||
// Set up a listener socket | ||
// TODO: Allow script to define the protocol | ||
m_socket = (int)socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | ||
if (m_socket == -1) | ||
return -1; | ||
|
||
sockaddr_in serverAddress = { 0 }; | ||
serverAddress.sin_family = AF_INET; | ||
serverAddress.sin_port = htons(port); | ||
serverAddress.sin_addr.s_addr = INADDR_ANY; | ||
|
||
// TODO: Need to be able to tell if the port is already occupied | ||
int r = bind(m_socket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)); | ||
if (r == SOCKET_ERROR) | ||
{ | ||
closesocket(m_socket); | ||
m_socket = -1; | ||
return -1; | ||
} | ||
m_isListening = true; | ||
|
||
// TODO: Allow script to define the max queue for incoming connections | ||
listen(m_socket, 5); | ||
|
||
return 0; | ||
} | ||
|
||
CScriptSocket* CScriptSocket::Accept() | ||
{ | ||
// Cannot accept a client on an ordinary socket or if the socket is not active | ||
if (!m_isListening || m_socket == -1) | ||
return 0; | ||
|
||
// TODO: Should optionally allow setting a timeout for how long to wait | ||
// First determine if there is anything to receive so that doesn't block | ||
fd_set read = { 1, (SOCKET)m_socket }; | ||
TIMEVAL timeout = { 0,0 }; // Don't wait | ||
int r = select(0, &read, 0, 0, &timeout); | ||
if (r == 0) | ||
return 0; | ||
|
||
// TODO: need to be able to check to what ip address and port the socket is connected it | ||
// Each incoming client connection a new CScriptSocket is created and put in the array so the script can retrieve them | ||
int clientSocket = (int)accept(m_socket, 0, 0); | ||
if (clientSocket != -1) | ||
{ | ||
CScriptSocket* client = new CScriptSocket(); | ||
client->m_socket = clientSocket; | ||
return client; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
int CScriptSocket::Close() | ||
{ | ||
// If the socket is open | ||
if (m_socket != -1) | ||
return -1; | ||
|
||
// Close the listener socket | ||
closesocket(m_socket); | ||
m_socket = -1; | ||
|
||
return 0; | ||
} | ||
|
||
int CScriptSocket::Connect(asUINT ipv4Address, asWORD port) | ||
{ | ||
// If another socket is already used it must first be closed, and the working thread must be shutdown | ||
if (m_socket != -1) | ||
return -1; | ||
|
||
// Set up a client socket | ||
// TODO: Allow script to define the protocol | ||
m_socket = (int)socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | ||
|
||
sockaddr_in serverAddress = { 0 }; | ||
serverAddress.sin_family = AF_INET; | ||
serverAddress.sin_port = htons(port); | ||
serverAddress.sin_addr.s_addr = htonl(ipv4Address); | ||
|
||
// TODO: Allow script to define a timeout | ||
int result = connect(m_socket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)); | ||
if (result == SOCKET_ERROR) | ||
{ | ||
closesocket(m_socket); | ||
m_socket = -1; | ||
return -1; | ||
} | ||
|
||
// TODO: Should the client socket already start receiving data? Or should it wait for the script to initiate that? | ||
|
||
return 0; | ||
} | ||
|
||
int CScriptSocket::Send(const std::string& data) | ||
{ | ||
// Cannot send on a listener socket or if the socket is not connected | ||
if (m_isListening || m_socket == -1) | ||
return -1; | ||
|
||
// Send a buffer of data over the socket | ||
// TODO: The sending of data should be done in a background thread so it can be done over a time rather than block the caller | ||
// TODO: How to determine the maximum size of data that can be sent in a single call? | ||
int r = send(m_socket, data.c_str(), (int)data.length(), 0); | ||
if (r == SOCKET_ERROR) | ||
return -1; | ||
|
||
// Return the number of bytes sent | ||
return r; | ||
} | ||
|
||
std::string CScriptSocket::Receive() | ||
{ | ||
// Cannot receive on a listener socket or if the socket is not connected | ||
if (m_isListening || m_socket == -1) | ||
return ""; | ||
|
||
// TODO: Need to be able to set the size of the internal buffer | ||
|
||
// TODO: Should optionally allow setting a timeout for how long to wait | ||
// First determine if there is anything to receive so that doesn't block | ||
char buf[1024] = {}; | ||
fd_set read = { 1, (SOCKET)m_socket }; | ||
TIMEVAL timeout = { 0,0 }; // Don't wait | ||
int r = select(0, &read, 0, 0, &timeout); | ||
if (r == 0) | ||
return ""; | ||
|
||
std::string msg; | ||
for (;;) | ||
{ | ||
// Read the buffer | ||
r = recv(m_socket, buf, sizeof(buf), 0); | ||
if (r >= 0) | ||
{ | ||
msg.append(buf, r); | ||
break; | ||
} | ||
else if (r == SOCKET_ERROR) | ||
{ | ||
r = WSAGetLastError(); | ||
if (r == WSAEMSGSIZE) | ||
{ | ||
// Append what could be read, then read the rest | ||
msg.append(buf, sizeof(buf)); | ||
} | ||
else | ||
break; | ||
} | ||
} | ||
|
||
return msg; | ||
} | ||
|
||
static CScriptSocket* CScriptSocket_Factory() | ||
{ | ||
return new CScriptSocket(); | ||
} | ||
|
||
int RegisterScriptSocket(asIScriptEngine* engine) | ||
{ | ||
int r; | ||
|
||
// Check that the string type has been registered already | ||
r = engine->GetTypeIdByDecl("string"); assert(r >= 0); | ||
|
||
// Register the socket class with the script engine | ||
engine->RegisterObjectType("socket", 0, asOBJ_REF); | ||
r = engine->RegisterObjectBehaviour("socket", asBEHAVE_FACTORY, "socket @f()", asFUNCTION(CScriptSocket_Factory), asCALL_CDECL); assert(r >= 0); | ||
r = engine->RegisterObjectBehaviour("socket", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptSocket, AddRef), asCALL_THISCALL); assert(r >= 0); | ||
r = engine->RegisterObjectBehaviour("socket", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptSocket, Release), asCALL_THISCALL); assert(r >= 0); | ||
|
||
r = engine->RegisterObjectMethod("socket", "int listen(uint16 port)", asMETHOD(CScriptSocket, Listen), asCALL_THISCALL); assert(r >= 0); | ||
r = engine->RegisterObjectMethod("socket", "int close()", asMETHOD(CScriptSocket, Close), asCALL_THISCALL); assert(r >= 0); | ||
r = engine->RegisterObjectMethod("socket", "socket @accept()", asMETHOD(CScriptSocket, Accept), asCALL_THISCALL); assert(r >= 0); | ||
r = engine->RegisterObjectMethod("socket", "int connect(uint ipv4address, uint16 port)", asMETHOD(CScriptSocket, Connect), asCALL_THISCALL); assert(r >= 0); | ||
r = engine->RegisterObjectMethod("socket", "int send(const string &in data)", asMETHOD(CScriptSocket, Send), asCALL_THISCALL); assert(r >= 0); | ||
r = engine->RegisterObjectMethod("socket", "string receive()", asMETHOD(CScriptSocket, Receive), asCALL_THISCALL); assert(r >= 0); | ||
|
||
return 0; | ||
} | ||
|
||
END_AS_NAMESPACE | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// | ||
// CScriptSocket | ||
// | ||
// This class represents a socket. It can be used to set up listeners for | ||
// connecting incoming clients, and also to connect to a server. The socket | ||
// class will create threads in the background to handle the communication | ||
// and update a send and a receive buffer so the script will not have to deal | ||
// with that. | ||
// | ||
|
||
#ifndef SCRIPTSOCKET_H | ||
#define SCRIPTSOCKET_H | ||
|
||
#include <string> | ||
#include <list> | ||
#ifdef _WIN32 | ||
#include <winsock2.h> | ||
#endif | ||
|
||
#ifndef ANGELSCRIPT_H | ||
// Avoid having to inform include path if header is already include before | ||
#include <angelscript.h> | ||
#endif | ||
|
||
BEGIN_AS_NAMESPACE | ||
|
||
class CScriptSocket | ||
{ | ||
public: | ||
CScriptSocket(); | ||
|
||
void AddRef() const; | ||
void Release() const; | ||
|
||
int Listen(asWORD port); | ||
int Close(); | ||
CScriptSocket* Accept(); | ||
int Connect(asUINT ipv4Address, asWORD port); | ||
int Send(const std::string& data); | ||
std::string Receive(); | ||
|
||
protected: | ||
~CScriptSocket(); | ||
mutable int m_refCount; | ||
|
||
int m_socket; | ||
bool m_isListening; | ||
}; | ||
|
||
int RegisterScriptSocket(asIScriptEngine* engine); | ||
|
||
END_AS_NAMESPACE | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.