WinSock - send and receive whole vectors.

Apr 7, 2012 at 5:07pm
closed account (2NywAqkS)
I want to make a multi-player game using WinSock and I need a method of sending all the data to the server and to multiple clients. My current method is, the client contains a player class containing the coordinates, there is an instance called 'thisPlayer' and a vector of them called 'players' (which should contain all the clients player data). Operations, such as moving, are done on 'thisPlayer' and then all of the players in 'players' are drawn. The data is sent and received like this:
1
2
3
4
5
6
case FD_READ:
{
   unsigned recvData;
   players = recv(Socket, (char *)&recvData, sizeof(recvData), 0);
   send(Socket, (char *)&thisPlayer, sizeof(thisPlayer), 0);
}


In the server there's a vector of <unsigned int> called 'data'. This data vector is gathered and distributed like so:
1
2
3
4
5
6
7
8
case FD_READ:
{
   for (int i = 0; i < data.size(); i++)
      {
         recv(Socket[i], (char *)&data[i], sizeof(data[i]), 0);
         send(Socket[i], (char *)&data, sizeof(data), 0);
      }
}


This doesn't work. In the client none of the players are drawn. It isn't a connection issue because the server detects the client and all that. I would imagine the problem is it can't get a whole vector with the recv function but I can't think of a better way of doing it.

Is this the right way of doing it? If so what am I doing wrong? and if not what is the right way of doing it? I hope I have made my problem clear, feel free to ask any questions.

Thanks,
Rowan.
Last edited on Apr 8, 2012 at 8:17am
Apr 8, 2012 at 11:05am
I'll just write out the protocol in text to begin with.
1. Each client sends the position off its local player.
2. The server receives updates from each client and echoes the full set of positions to all clients.
3. Each client receives position updates for all players.
4. Each client renders the updated position for each player.

Now that I've written out the protocol as you've described it, can you see that you don't implement the protocol?

Points to note are:
1. Each client sents the position of one player (the local player).
2. The server sends ALL the player positions each time.
3. Each player must receive ALL player positions.

You also need:
1. A client (or player) id to identify each player in data being sent around. This will allow you to ignore updates for your self (as you already know that) and map player data to player objects.
2. You need to send the size of the vector when you send the vector or else the clients can't determine how large the vector is.
Last edited on Apr 8, 2012 at 11:06am
Apr 8, 2012 at 11:59am
closed account (2NywAqkS)
Thanks for the reply.

2. You need to send the size of the vector when you send the vector or else the clients can't determine how large the vector is.


How would I do that ?
Apr 8, 2012 at 12:25pm
You can write the size then the elements.
1
2
3
uint32_t sz = players.size();
send(s, &sz, sizeof(sz), 0);
send(s, &players.front(), sz*sizeof(players.front()), 0);
Apr 9, 2012 at 9:00am
closed account (2NywAqkS)
would I recieve it like this? because I don't think this works.
1
2
3
4
int sz;
recv(Socket, (char *)&sz, sizeof(sz), 0);
players.resize(sz);
recv(Socket, (char *)&players.front(), sizeof(&players.front()), 0);
Apr 9, 2012 at 9:43am
You probably need to receive updates into a temp buffer ... but we can get to that later.

You forgot the size multiplier in the receive:
1
2
3
4
int sz;
recv(Socket, (char *)&sz, sizeof(sz), 0);
players.resize(sz);
recv(Socket, (char *)&players.front(), sz*sizeof(&players.front()), 0);
Apr 9, 2012 at 10:59am
closed account (2NywAqkS)
Ok, I have done that and now the player is showing up on the screen but I keep getting this Unhanded exception error and it opens up the vector header and points to a line saying
1
2
if (max_size() < _Count)
_Xlen();	// result too long 


is this to do with a temp buffer you were talking about?
Apr 9, 2012 at 11:43am
This players vector that you're using. Can you show be the declaration a Player? It really should be just a set plain old datatypes (ints and so on) and not contain another object.
Apr 9, 2012 at 11:51am
closed account (2NywAqkS)
the Player declaration is
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Player
{
public:
	double x, y;
	Player();
}thisPlayer;

std::vector <Player> players;

Player::Player()
{
	srand(current_time);
	x = rand() % 502;
	srand(current_time * x);
	y = rand() % 502;
}
Apr 9, 2012 at 12:05pm
The structure should be ok. Passing floats in this way is ok for the same kind of machines.

Can you see the call stack when the app blows up?

If you're working on a LAN, recv() returns all that was sent. But when going out over the Internet, the packet may be broken up by routers and reassembled on the client. In which case, you need to check how many bytes were actually received and keep calling it until you get the whole thing. But don't worry about that yet.
Apr 9, 2012 at 4:11pm
closed account (2NywAqkS)
well, I've been messing around with it all day and I can't seem to get any satisfactory results so I've uploaded the source code. It would be greatly appreciated if you could go through and see what I've done wrong, because it's probably a mixure of faults throughout the code

http://www.mediafire.com/?voa17338my6777q
Apr 9, 2012 at 6:35pm
Why don't you transform everything to a string (say base64 encoded) and send that over the socket ? You either prefix the size, or just add an invalid base64 character to signal end of transmission.
It is just a suggestion.
Apr 9, 2012 at 7:18pm
Get familiar with the concept of serialization. Each class that needs to be transmitted should be able to serialize itself into a stream (in your case, a memory stream) and later on it should be able to deserialize the data in the stream and produce the object.

Once you have done this, you serialize the entire array be serializing each element in the array. You then transmit the serialized data, which is then relayed to all clients. Whenever the client receives the data, it is deserialized to produce the usable object.
Apr 9, 2012 at 10:30pm
I ran the app, pretty cool.

You're using non-blocking sockets and you occasionally get errors coming back from them. So you need to check the error codes from send and recv.

The error you're always getting back from recv is:
1
2
3
4
5
6
7
8
//
// MessageId: WSAEWOULDBLOCK
//
// MessageText:
//
//  A non-blocking socket operation could not be completed immediately.
//
#define WSAEWOULDBLOCK                   10035L 


I modified the receive code in the client to:
1
2
3
4
5
6
7
8
9
10
11
12
13
case FD_READ:
{
    int sz = 0, ret = 0, err = 0;
    ret = recv(Socket, (char *)&sz, sizeof(sz), 0);
    if (ret < 0)
    {
        err = WSAGetLastError();  // I just wanted to see what the error was
        break;
    }
    players.resize(sz);
    recv(Socket, (char *)&players.front(), sz*sizeof(&players.front()), 0);
}
break;

The idea is, if you don't read the size correctly, you can't resize the array.

The app ran ok until I started a second instance. I don't have the time to spend on it now, I'll take another look tomorrow or Wednesday.
Apr 12, 2012 at 8:43pm
closed account (2NywAqkS)
I think I have made some improvements in that now I put everything in one string when it is sent, like this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
case FD_READ:
{
	for (int i = 0; i < data.size(); i++)
	{
		unsigned tempBuffer;
		data[i] = recv(Socket[i], (char *)&tempBuffer, sizeof(tempBuffer), 0);
		char sz[1] = {data.size()};
		char id[1] = {i};
		char * stream = sz;
		strncat(stream, id, 1);
		strncat(stream, (char *)&data.front(), sz[0]*sizeof(data.front()));
		send(Socket[i], stream, 4+(sz[0]*sizeof(data.front())), 0);
		}
}


but I'm not quite sure how to properly receive it on the other end. I've managed to receive the vector size properly but not the rest of the vector and the ID

The client code:
1
2
3
4
5
6
7
8
9
10
11
case FD_READ:
{
	char buffer[1024];
	int clientSize;
        recv(Socket, buffer, sizeof(buffer), 0);
	clientSize = buffer[0];
	clientID = buffer[1];
	players.resize(clientSize);
	strcpy((char *)&players.front(), buffer + 2);
}
break;
Last edited on Apr 13, 2012 at 11:07am
Apr 13, 2012 at 11:28am
I've spent some time looking at this. I've never used Winsock's non-blocking WSA functions, and as it's never used in a portable apps, I can't spend a lot of time learning it. So I can't help you with all those WSAWOULDBLOCK things. I did spend some time on it, but was finding it incredibly complicated, far more than BSD Socket's select() stuff. You may find this useful.
http://tangentsoft.net/wskfaq/articles/debugging-tcp.html

The source of your problem is comms. That Calculate() function sends coordinate updates. And you have the main loop sending those Player updates. That's a real confusion, the server doesn't know what it's receiving, coordinate updates or player updates.

Further more, there's no way to indicate which player is which in the players vector. The client takes vector index zero to the local player. All the clients do that, so when there's more than one player, it all goes bad.

A player needs to be uniquely identified. So on the same machine, that might be process id. On a network, you could use hostname (or address)/pid as the identifier.

What you really need is a single message passed to the server from the clients. And a single message send back. This way, the protocol is implied and you don't need code to reconcile send/recv of different kinds of information. So this is what I recomend.

Client Protocol
1. When the client starts up, instantiates a player to represent itself. This player has a number, the pid. See GetCurrentProcessId().
2. On startup. It sends it's player info and the coordates.
3. Periodically (like when the position changes), it sends it's player info and the coordinates.
4. It listens for updates from the server. This message is the number of other players then the player/coords for each of the other players. It can then knows about all the other players and their current positions. If a player is missing from the update, you can assume it's dropped out of the game.

Server Protocol
1. The server starts up and listens for new connections (from accept) and updates (from recv).
2. The server maintains a map of SOCKET to PlayerRec (PlayerId and Coords) so it knows which client is on which socket. It also maintains a collection of these, which represent all the socket/playerid/coords sets.
3. On accept, create a new map entry and a new entry in the collection.
4. On recv, update the PlayerRec (PlayerId and Coords) for that SOCKET. The set a dirty flag.
5. On timer, if the dirty flag is set, send the number of "other" players then the details to all connected players then clear the dirty flag.
Last edited on Apr 13, 2012 at 11:33am
Apr 15, 2012 at 9:38pm
Just another thought on send() and recv():

Even in blocking mode, a send() or recv() might not send or receive all of the data in one call: I have found it necessary to code these routines in a loop -- preferably in a separate function. That way, your sends and receives don't return from the new function until it's finished sending or receiving or there is an error detected:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

/*    Assumes socket is connected    */

int SendRoutine( Socket s, char* Data, int Length, int flags )
    {
    BOOL bWriting = TRUE;

    const char* p = NULL;

    int
        SentThisMany = 0,
        ThisMany = 0,
        TotalWritten = 0,
        rc = -1;

    p = Data;

    ThisMany = Length;

    Writing = ( NULL != p );

    while( Writing )
        {
         SentThisMany = send( s, p, ThisMany, flags );

         switch( SentThisMany )
             {
             case 0:
                 Writing = false;    /*    Sending done    */

                 break;

             case SOCKET_ERROR:
                 rc = WSAGetLastError();

                 if( rc != WSAEWOULDBLOCK )
                     Writing = false;

                 break;


                 default:
                     if( SentThisMany > 0 )
                        {
                        ThisMany -= SentThisMany;

                        p += SentThisMany;

                        TotalWritten += SentThisMany;

                    }    /*    if( SentThisMany > 0 )	    */

                break;

            }    /*     switch( SentThisMany )    */

        if( ThisMany <= 0 )
            Writing = false;

        }    /*    while( Writing )    */

    if( TotalWritten > 0 )
        rc = 0;

    return rc;

    }
        /*    SendRoutine()    */

Topic archived. No new replies allowed.