HomeNewsFeaturesLicensingDownloadsScreenshotsFAQRoadmap Contact Us
Search:
4 Online

Community

Discussion Topics Recent Postings User Contributions General Articles Example Documentation Credits

Licensed Developer

Programmer's Manual Artists Manual Tutorials and Articles

Programming: Game Code

Game Code Overview Server Side Game Code Client Side Game Code

Programming: System

Alphabetical Function List Renderer File System Collision & Ray Casting Low Level Audio Game Audio The Console Console Variable List Multiplayer Localisation Maths Library Memory Manager Model File Formats Texture Formats

Art: Overviews

Specifications Shaders Particle Systems Lens Flares Cipher console Cipher file types Tutorials Reference

Art: Tools

Shader Designer Particle Designer 3dsmax tools Model Conversion Font Generator

Cipher Engine
Game Development Search Engine
GameDev.net
You are not signed in - [sign in] [register]

Retrieving a URL

Overview

Author: RichardGreen
Date Submitted: 2003-12-21 11:15:00

Source Code

In article.


Description

Design

As part of the game i'm "working on" i needed to be able to store and retrieve race times from the server and display a "News" page or a scrolling ticker in the UI. I decided that the best way to do this was via php and HTTP GET.

After looking at the cipher networking code and finding it was all UDP and not TCP (as was to be expected looking back at it now) i realised i would have to modify a number of functions, these were:
<ul>
<li>[Code]winsock_OpenSocket[/Code]
<li>[Code]winsock_Write[/Code]
<li>[Code]winsock_Read[/Code]
</ul>

My first attempt included passing a bool as an extra param to these functions to speficy if they where TCP or UDP, how ever after looking at winsock_OpenSocket i decided i would rewrite the functions as needed, following the cipher naming convention the functions are named:
<ul>
<li>[Code]winsock_TcpOpenSocket[/Code]
<li>[Code]winsock_TcpWrite[/Code]
<li>[Code]winsock_TcpRead[/Code]
</ul>

As anyone who knows network programming knows UDP is state-less and TCP is stated, this meant i had to create a totally new function to connect the socket our end to the server, this function is named winsock_TcpConnect.

The source code for these 4 functions is presented below and is placed in net_winsock.h:


/*
==========================
winsock_OpenTcpSocket

Opens a socket
==========================
*/
int winsock_OpenTcpSocket(int port)
{
int newsocket;
struct sockaddr_in address;

// create a new socket
newsocket = socket(PF_INET, SOCK_STREAM, 0);
if (newsocket==INVALID_SOCKET)
{
con_Print(_T("winsock: Failed to open socket\n"));
return -1;
}

// prepare the sockets name
com_memset(&address, 0, sizeof(address));
address.sin_family = PF_INET;
address.sin_addr.s_addr = wsd.local_IPaddress;
address.sin_port = htons((u_short)port);

// bind to it
if (bind(newsocket, (struct sockaddr*)&address, sizeof(address))==SOCKET_ERROR)
{
// failed to bind to the socket
con_Print(_T("winsock: Failed to bind to address\n"));
closesocket(newsocket);
return -1;
}

// worked
return newsocket;
}


/*
==========================
winsock_TcpConnect

connects to a remote machine via TCP
==========================
*/

int winsock_TcpConnect(int socket, netaddress_t* address)
{
struct sockaddr_in sockaddress;

// convert the address and send the data
winsock_NetAddressToSockAddress(address, &sockaddress);
//SOCKET_ERROR
return connect(socket,(SOCKADDR*)&sockaddress,sizeof(sockaddress));
}


/*
==========================
winsock_TcpWrite

Sends 'len' bytes of data to the machine at 'address'
==========================
*/
int winsock_TcpWrite(int socket, byte* buf, int len, netaddress_t* address)
{
return send(socket, buf, len, 0);
}

/*
==========================
winsock_TcpRead

Reads bytes off the network.
returns the number of bytes we got. <0 is an error
If it successfully reads data from the network, address will
hold the address of the machine that sent the data
==========================
*/
int winsock_TcpRead(int socket, byte* buf, int len, netaddress_t* address)
{
int bytesread, totalbytesread;

totalbytesread = 0;

do{
bytesread = recv(socket, buf, len, 0);
totalbytesread += bytesread;
}while((bytesread != SOCKET_ERROR) && bytesread!=0 && bytesread != WSAECONNRESET);

// convert the returned address
return bytesread;
}
[/Code]

The next bit was to actually write the URL retreval, I decided to stick to HTTP 1.0 for the first version as its very slightly easier to implement than 1.1 (altough the difference is tiny and it would take about 2 minutes to make it 1.1).

After looking at the cipher calls that can be made from cgame,game and UI the function was prototyped like:
[code]void net_LoadURL(const tchar_t *URL, tchar_t* data, int len);


The parameters are fairly self explanetory, URL is the URL of the file, data is where you want to store the string returned from the server and len is the length of the data array.


/*
==========================
net_LoadURL

Reads the data from a URL using HTTP/1.0
==========================
*/

void net_LoadURL(const tchar_t *URL, tchar_t* data, int len)
{
int socket;
int conn_state;
int amount_sent;
int URLLen;
int i,t;
int ServerLen;
int Done;

netaddress_t addr;
byte *buffer;

tchar_t Request[MAX_STRING_SIZE];
tchar_t Server[MAX_STRING_SIZE];
tchar_t *URLCopy;

buffer = (byte*)com_Malloc(len);
com_zeromem(buffer, len);
com_zeromem(Server, MAX_STRING_SIZE);

URLLen = com_strlen(URL);
URLCopy = com_strdup(URL);

ServerLen = 0;
Done = 0;

i=0;
while(Done == 0 && i < URLLen)
{
if(i < 7)
{
*URLCopy++;
}else{
char *temp = sys_AsMultiByte(URLCopy);

if(temp[i-7] == '/')
{
ServerLen++;
Done = 1;
}else{
ServerLen++;
}
}
i++;
}

com_strncpy(Server,URLCopy,ServerLen);

// Reset URLCopy pointer to start so we don't get heap corruption
for(t=0 ;t<7;t++)
*URLCopy--;

if(com_strchr(Server,':') == NULL)
{
// We don't have a port so assume port 80
com_Sprintf(Server,MAX_STRING_SIZE,_T("%ls:80"),Server);
}

socket = winsock_OpenTcpSocket(0);
if(socket == -1)
{
con_Printf(_T("Couldn't open socket to load url from\n"));
return;
}
winsock_NameToAddress(Server, &addr);

com_Sprintf(Request,MAX_STRING_SIZE,_T("GET %ls HTTP/1.0\r\n\r\n\r\n"),URL);
conn_state = winsock_TcpConnect(socket,&addr);
// con_Printf(_T("State: %d\n"),conn_state);
amount_sent = winsock_TcpWrite(socket, sys_AsMultiByte(Request), com_strlen(Request), &addr);
// con_Printf(_T("Sent: %d\n"),amount_sent);

winsock_TcpRead(socket,buffer,len-1,&addr);
winsock_CloseSocket(socket);

// All done

sys_MultiByteToWide((const char*)buffer,data,len-1);

com_Free(buffer);
com_Free(URLCopy);
}


After that was written i had to hook up the call so it could be used in the server/ui code, instead of doing it like that i decided upon putting it in the allgame.c file so it can be called from anywhere, i added a value to the common entry points enum after the A_CVAR_STRING value and named it A_NET_LOADURL, i then had to change cg_public.h, g_public.h and ui_public.h to match the new enum in allgame.c.

After this was done i had to add an extra case statment in vm_CommonEntry (also in allgame.c) to acutally call the net_LoadURL funtion.

That was it for the server side, on the clients the changes are a lot more trivial, all thats needed to be done is to add a line in each of the syscalls files that are like:

// RG - LoadURL Support
void cipher_LoadURL(const tchar_t* URL, tchar_t *dst, int len)
{
cipher(UI_LOADURL,URL,dst,len);
}


Where UI_LOADURL is the new enum value placed after UI_CVAR_STRING.


Usage

The code is very easy to use from the ui/game/cgame components, its can be done in 3 lines like:

tchar_t dst[1024];
cipher_LoadURL(_T("http://www.codeproject.com/"),dst,1024);
cipher_con_Print(dst);


The url must begin with http:// and if the file you want to download is the index file on a site it must end with a /. Post data to a site is easy aswell, the url must be in the same format as above but can include a ?var1=data&var2=data2 cgi parameters section on the end of it.

The data returned from cipher_LoadURL also contains the full HTTP headers before the body of the text, they are easily seperated via a single blank line that denotes where the headers finish and the body starts.

Richard Green

[Recent Contributions] [Recent Source Code]

User Contributed Comments

philk 22nd June, 2004 17:01
Please find a bug-fixed TcpRead function below.

int winsock_TcpRead(int socket, byte* buf, int len, netaddress_t* address, winsock_Progress proc)
{
int bytesread, totalbytesread;

totalbytesread = 0;

do{
bytesread = recv(socket, buf, len, 0);
if (bytesread > 0)
{
totalbytesread += bytesread;
buf += bytesread;
len -= bytesread;
}
}while((bytesread != SOCKET_ERROR) && bytesread!=0 && bytesread != WSAECONNRESET && len > 0);

// convert the returned address
return totalbytesread;
}




Register and Sign In to discuss this article