Archived

This topic is now archived and is closed to further replies.

Brandisco

network parsing

Recommended Posts

Is it possible using the winsock to have clients send structs to the server and vice versa. I saw an example where someone sent the memory address of the struct and then in the server (or client) it read the buffer into the memory address of its struct (both structs are identical -- with the same members). If this method works then no parsing would be required but lack some power. In visual basic I used the functions InStr(which would return the location of a string (int) inside a string given 2 strings) and Mid (which would return the part of the string -- you tell it the string and the start and end locations). With these 2 functions I was able to parse all network data in a multiplayer. Data that looked like this: user_join_game:username:password ( i could simply parse the data between colons) I am wondering if the first option is possible and if there are any standard C functions like the 2 I mention in paragraph 2. Thanks, Brandon

Share this post


Link to post
Share on other sites
There are standard C functions to twiddle strings, though I haven''t used any of them in years except strcpy.

It''s fairly straight forward to create a packet out of a C struct, it''s a process called ''serialization''. Instead of sending text and parsing it, you send binary data and parse that. It''s usually easier to code, and orders of magnitude smaller & faster.


DWORD* pdwSetPacketID = (DWORD*)&packetbuffer[bufferbytes];
pdwSetSize = CMyPacket::s_dwPacketID;
bufferbytes+=sizeof(DWORD);
memcpy(&packetbuffer[bufferbytes], &MyPacket, sizeof(CMyPacket));
bufferbytes+=sizeof(CMyPacket);


If the packet has pointers it gets more complicated.

Magmai Kai Holmlor
- Not For Rent

Share this post


Link to post
Share on other sites
You can''t just send the memory address of something over a network link and expect it to link, because the other end of the link is in a different process (and thus address) space, let alone a different computer, and the pointer your sending will prolly point to invalid memory at the other side. You''d cause heap / stack corruption or segfaults (Access Violations for you Windows people )

Now, Magmai already answered the more important part. However, I''d like to add something. For example, imagine a server<->client relationship where the same strings are sent back and forth very often. Instead of sending the entire string every time, you could have the server send a string table down to the client, and after that you''d just send an offset into the string table. You could even add messages to extend the string table. Beware of collisions if both the server and the client may add to the table. In that case, you may want to have two string tables, one which is maintained (owned) by the server, and one for the client.


BTW Magmai: You''ve _never_ used standard C functions in years? WTF have you been using? CString?
Or maybe you just never had to write a config parser...

cu,
Prefect

Resist Windows XP''s Invasive Production Activation Technology!
One line of sourcecode says more than a thousand words.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
I think I know exactly what you are talking about. Just do all your network byte reordering, cast the structure into a byte pointer and on one side. On the other side, make sure you have the equivalent structure and just receive it into that structure. The variables should correspond to each other if you declare the variables in the same order within the structure. Do your network byte reordering again and presto it works. Also, make sure you align your bytes.

Share this post


Link to post
Share on other sites
Exactly.

For the byte packing thing, do a
#pragma pack(1)
before all the critical structure declarations, and a
#pragma pack()
afterwards to restore to normal packing.
Of course you could also use pack(4) or whatever, but that would potentially waste space, which is still critical over the inet. You wouldn''t need it either if you only used one compiler. But as soon as you''re using different compilers, things might break (even different versions might break it).
Note that the same applies when you declare structures for your file types.

Note that you only have to do the network byte ordering stuff if you''re going to port your game to a platform with different endianness (I always forgot which endian type is which). As long as you stay on an i386 you can save your time and not do it. (Even if you intend to port to a different platform later, you can still use Intel byte ordering anyway.. you just have to do the byte reordering on that other platform).

cu,
Prefect

One line of sourcecode says more than a thousand words.

Share this post


Link to post
Share on other sites
No need to reorder the bytes unless you''re trying to send data from a x86 to a Mac or something...

You need to add a packet/message identitifer as the first item in the packet so you know what kind it is. Then you need a big ''ol switch statement, or a fancy message-map to ''crack'' and handle the packets.

If I need to twiddle a config file I use the pre-canned Window''s .ini file reader/writers. Very easy, very flexible, already done; all around .ini files are verra niace. If it''s not .ini then it''s in the registry... so no I haven''t written a text parser since my freshman year in college... and I usually use sprintf to put together strings otherwise.



Magmai Kai Holmlor
- Not For Rent

Share this post


Link to post
Share on other sites
If you want to get really fancy, you should try using factories for the message types. I''ve seen an article on factories used for network message parsing on gamedev some months ago...

Basically, it''s a "technology" which is better known from "business" programming, and involves one C++ class derived from a pure base class for each message type, and a template class which is instantiated globally once per message type class. This template then adds "its" C++ class to a global linked list of message types, or similar.

cu,
Prefect

One line of sourcecode says more than a thousand words.

Share this post


Link to post
Share on other sites
I read that same article on pluggable class factories. I thought the idea sounded good on paper, but implementation was ugly and it didn't really gain you anything.

As far as this goes, I have a system that does pretty much exactly what Brandisco is asking. The only difference is that I'm using DirectPlay. As for how it's done, just make sure you don't use pointers in your message structures. If you want to use a string, declare it as a character array, not a character pointer. If you declare it as a pointer, it won't show up in a contiguous region of memory with the structure. Also, put your largest strings that may vary in length at the end of the structure, then compute the size of the structure to send based on the length of that string.

Here's some example code. The first structure is the base message which all other messages are derived from. The second is a structure I use to send an exported CryptoAPI key blob to set up my encryption:

    
typedef struct DP_MSG{
USHORT nType;
USHORT nSize;
DP_MSG(USHORT type){
nType = type;
nSize = sizeof(DP_MSG);
}
}*PDP_MSG;

typedef struct DP_MSG_KEY : DP_MSG{
BYTE byData[DP_MAX_KEY_LEN];
DP_MSG_KEY(USHORT type, PBLOB_INFO pBlob) : DP_MSG(type){
ZeroMemory(byData, DP_MAX_KEY_LEN);
if(pBlob->dwSize <= DP_MAX_KEY_LEN)
memcpy(byData, pBlob->pbData, pBlob->dwSize);
nSize += (USHORT)pBlob->dwSize;
}
}*PDP_MSG_KEY;


When I want to send the structure using DPlay (and it would be the same with Winsock) I just pass the address of the message structure, and the length of the data to send. On the receiving end, I receive the message, cast to PDP_MSG and switch based on the type of message.

Hope that helps.

Edited by - JonStelly on August 14, 2001 10:35:15 AM

Share this post


Link to post
Share on other sites
Can anyone tell me whats wrong with the code below?

buffer = "xy:23:23:23:23"

Where the 23's could be any integer. This is part of the code for my game of networked pong.

I read the incoming packet from the server into the buffer string and then call parse code to parse it for me. I try to parse it down so I know where to draw the enemy's paddle.

Thanks

  
volatile int ex1 = 450, ex2 = 400, ey1 = 100, ey2 = 103;
void parse_code() {
char *funct;
memcpy(funct, buffer + 1, strstr(buffer, ":") - 1);
memcpy(buffer, strstr(buffer, ":") + 1, strlen(buffer));

if (strcmp(funct,"xy") == 0) {
memcpy(ex1, buffer + 1, strstr(buffer, ":") - 1);
memcpy(buffer, strstr(buffer, ":") + 1, strlen(buffer));
memcpy(ex2, buffer + 1, strstr(buffer, ":") - 1);
memcpy(buffer, strstr(buffer, ":") + 1, strlen(buffer));
memcpy(ey1, buffer + 1, strstr(buffer, ":") - 1);
memcpy(buffer, strstr(buffer, ":") + 1, strlen(buffer));
ey2 = buffer;
}
}


Edited by - Brandisco on August 16, 2001 9:46:35 AM

Share this post


Link to post
Share on other sites
Ouch. It looks like you've got much to learn...

First of all, what on earth do you need volatile for? I've never ever seen it used it any real program (well, in DOS times when you used to trap IRQs...).

Second, why do you send the messages as text? It'd be much easier to send them in binary form using something like this:

  
// Somewhere in a header far, far away

typedef struct {
int type;
} generic_packet_t;

typedef struct {
int type;
int ex1;
int ex2;
int ey1;
int ey2;
} position_packet_t;
#define PACKET_TYPE_POSITION 42

// The send code:

position_packet_t pkt;
pkt.type = PACKET_TYPE_POSITION;

// ADD: fill in pkt.ex1 .ex2 .ey1 and .ey2


if (send(your_socket, &pkt, sizeof(pkt), 0) == -1) {
// ADD: Error handling

}

// The receiver code:

char buf[MAX_PACKET_SIZE];


if (recv(your_socket, &buf, sizeof(buf), 0) == -1) {
// ADD: Error handling

}

switch(((generic_packet_t *)&buf)->type) {
case PACKET_TYPE_POSITION:
position_packet_t *pkt = (position_packet_t *)&buf; // alias

// Read out .ex1, .ex2, etc.. here

break;
// ADD: Other packet types

default:
// ADD: Error: Unknown packet

}


Of course this is a bit simplified, typed out of my head with potential errors and everything.
Oh, one _VERY_ important thing: Simply using recv() like is showed in this example is a very Bad Thing(tm). You should really save the buffer in between several recv()s, as a packet could've been split up - if you use TCP that is.
If you were using UDP, you'd have to use sendto() instead of send(), and recvfrom() instead of recv(). Packets can't be split in UDP, but they might get dropped, so UDP has its own difficulties.

Now if you do want to parse text (you will have to some time anyway), I'd give you a hint: Use some kind of tokenizer. Say you've read your packet into a string. Now write a function which scans this string for ':'s (just cycle through the string using pointers), replace those ':'s by 0s (not '0', an integer 0 which terminates a string) but save the starting pointer of each part (token) of the string. You'll end up with something similar to argc/argv in main(), which is quite easy to process.

To convert strings into integer, use atoi() or strtol(). Just look them up in the manual or online help or whatever you've got near you.

cu,
Prefect

One line of sourcecode says more than a thousand words.

Edited by - Prefect on August 16, 2001 1:03:59 PM

Share this post


Link to post
Share on other sites