Advertisement Jump to content
  • Advertisement

hplus0603

Moderator
  • Content Count

    12274
  • Joined

  • Last visited

  • Days Won

    1

hplus0603 last won the day on April 15 2018

hplus0603 had the most liked content!

Community Reputation

11533 Excellent

About hplus0603

  • Rank
    Moderator - Multiplayer and Network Programming

Personal Information

Social

  • Twitter
    jwatte
  • Github
    jwatte
  • Twitch
    MyGunShootsBitcoins

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. hplus0603

    Issues with RakNet and public IP

    It resolves the problem, if the server that does the punch-through is publicly available, and is responding on the appropriate port, and is not filtered by any firewall. Where do you run the server? On some public cloud, like amazon, google, linode, azure, or somesuch? It needs to have a publicly reachable IP address. What is the address of the server? What is the address you tell the client to send to? If you use Wireshark on the client, or tcpdump on the server, what packets, ports, and addresses do you see?
  2. How CCUs are calculated depends entirely on the service provider in question. For a small party game, try using Steamworks (for PC/Mac/Linux,) Google Play Services (for Android) or iTunes Game Center (for iOS.)
  3. There is no industry standard for "CCU of a game." For the matchmaking server, a measurement of "matchmaking CCU" would only count players waiting for matching. For the game overall, for market sizing purposes, "game CCU" would presumably count every currently active player.
  4. Yes, that seems like a fine way of doing it.
  5. UDP and TCP don't know about MTU; that's an underlying IP thing. And fragmentation isn't bad these days; the network stack will fragment for you and reassemble at the other end, both for IPv4 and IPv6. (for v6, the network implementation in the kernel is responsible for detecting and fragmenting; in v4, any router along the path can do this.) The maximum UDP packet size is 65535 bytes, so don't send a datagram bigger than that! Most games send packets anywhere at the even-multiples-of-60 rate, with multiple "steps" bunched into a single packet. I e, common send frequencies are 10, 12, 15, 20, 30, and 60 Hz.
  6. Yes, it's effectively another sequence number. However, you can have many separate sequence series -- for example, "projectiles spawned by player X" don't need to be in-sequence with "projectiles spawned by player Y" (depending on gameplay and simulation specifics.) Also, for sequence numbers, you can often get away with only sending the lowest byte, or the lowest two bytes, of the sequence number, because you'll know when it's supposed to "wrap over" -- if you drop a client if it hasn't sent you 30 packets in a row, and you can't create more than 4 entities per packet, then you can't create more than 120 entities in a packet outage time, and thus a single byte is enough to unambiguously keep a sequence number in sync. (If this doesn't make sense, go look at the bit representation of unsigned integers keeping serial numbers, and what happens as they increment -- very important feature!)
  7. A "new object created" isn't that different from an important event like "player X shot a bullet." Typically, for important messages, you will stuff the message into each UDP packet you send, until you get an acknowledgement from the other side for a UDP packet that you know contained that message. Then the receiving side will de-duplicate multiple copies of the same message (not just same UDP datagram!) to make that work out. Typically, you will have serial numbers for messages, and when you first send a message, tag it with a serial number, and re-use that for each copy; that way the receiving end can know whether a particular message is handled or not.
  8. The most-mentioned C# networking library for games is Lidgren; have you checked that out? A second option is RakNet with C# bindings, which has also been mentioned on these forums before. (I don't know if the bindings are P/Invoke or what, though.)
  9. I played around with this in the past, and there are pluses and minuses. Yes, you can work around head-of-line blocking for a single packet lost when multiplexing over two connections. Yes, it's not a lot of overhead to send "the last X inputs" in each packet, if you use RLE encoding of the inputs. No, you're not guaranteed that the browser will create multiple connections; HTTP2 and QUIC allow browsers to multiplex as much as they want across as many connections as they want. I have no idea what actual browsers and servers end up doing in practice these days. No, this won't solve all problems, because packet loss often comes in bursts, in which case all of your connections end up being delayed at more or less the same time. It's super simple to set up once you have the basic system going, though (assuming your code is well structured,) so it's certainly worthwhile testing out for your particular use case.
  10. No, for online play you need a client/server architecture. Go with an existing game engine that has networking built-in would be the easiest. In general, gameplay implementation needs to take networking into account from the ground up, it's not usually possible to "bolt on" networking afterwards, which is why using an engine with networking from the start is a good idea.
  11. There are several kinds of game coding, really. There's engine coding, which builds "networking" and "graphics" and "sound" and "art tool plugins" and so forth, typically in C/C++. There's "scripting" coding, which builds GUIs and game logic and so forth, typically in some scripting language like Python/Lua/Blueprint or something like it. There's "shader" coding, which builds the specific 3D light/shadow/material solutions, typically in shader code like GLSL or HLSL or in shader graphs for specific engines. Even within each of these kinds, people often end up specializing as budgets go up -- there are people whose only job is to make fire and smoke shaders for FPS games! So, "which kind of coding" should you learn? It depends on what you want to do, and which game engine you're using. By the way, the simplest possible networking model for a party game that's only played on a local network, is to have each player send their actions 20 times a second using a UDP broadcast packet to a known port. Make each game client listen on the same port, and assume that any packet received is from another player. This would make it trivial to cheat, but party games aren't particularly susceptible to this. And the cool part of this is that there's no match-making needed, no server needed, no set-up needed. Just start the game, and you end up joining in with everyone else!
  12. You still have a bug in that you don't slice the incoming byte array to the size of the number of bytes you've received. Thus, the memorystream may deserialize out from uninitialized or zero data in the byte array -- it thinks you always got 256 bytes. This is the kind of bug that exploiters love to find in networked games; undefined behavior that lets them craft packets that makes the computer do things it wasn't intended to do.
  13. Did you fix the improper call to Connect that I pointed out in the first answer? Also, what is the UDPClient class, as opposed to the UDPSocket class? Both client and server should be able to use bare UDP sockets. And if you use a UDPClient, that should be on the client side, not the server side. It seems to me that you haven't yet understood how Ethernet, IP, and UDP networking works on the wires, in the network cards, in the kernel, and in the API. Thus, you're trying to just mash buttons in the dark. That will never work. What your client needs to do: 1) Find the server's IP address. This could be an argument to the program. You can resolve it using IPEndPoint. 2) Create a UDP socket. It shouldn't be bound or connected to any particular address/port. 3) Keep a receive request outstanding on the socket; when the receive request comes in, parse the packet, and issue a new receive request. Remember the IP address and port that the packet came in from, so you can respond to it! If you haven't received anything from the server for five seconds, assume that the server disconnected and stop the session. 4) On a timer of some sort, send an outgoing packet to the server, on your games well-defined port number, and on the server's IP address, depending on the state of the client (if not yet connected, send connection request; if in a game, send game input state; and so forth.) What the server needs to do: 1) Create a UDP socket 2) Bind (not Connect) to the INADDR_ANY address (0.0.0.0) on the game's well-defined port number. 3) Always keep a read request active. When it completes, remember the IP address/port of the packet, and look up in a dictionary of clients. If client doesn't exist, create a new client and put in the dictionary. Handle each packet as it comes in, based on your known game state and the per-client state. Keep note of the last time you received data from the client. 4) On a timer or some sort, iterate through all the clients in your dictionary of clients, and send some kind of packet/update of game state to those clients. If some client hasn't sent anything to you for the last 5 seconds, assume the client disconnected and kick it. You will note that some of those patterns are similar and can be re-used between client/server, but you don't have to do so right away -- it's probably easier to write them as separate things first, to make sure you keep the concerns of the two sides separated. Remove anything from your code that doesn't look like what I suggested above. Or, better yet, start from an empty project, and build those bits of code from scratch, using MSDN documentation. When you need to import things like serialization or game code, move those files over into the new project. By building from scratch, you know what all the bits are doing, which will help you build a robust program overall.
  14. UDP sockets are generally not connected. If you connect a UDP socket, you say "every packet I send on this socket should go to this particular address," which is usually not what you want. Your server is using Connect() on both TCP and UDP sockets. (Generally, you also don't Connect() on the client, but that's probably not the cause of your current problem.) When a datagram comes in on a UDP socket, you will also receive the address/port it was sent from. To send a reply, send to the address/port you received the datagram on. The same UDP socket will receive messages from many remote clients, and will thus want to send responses to many remote clients. Btw: Instead of parsing an IP address and binding the TCP socket, you will typically bind on the "IPADDR_ANY" address (0.0.0.0) so that the user doesn't have to know the IP address of their own machine to open a listening socket. When it comes to asking for help online, your post is missing some bits, though: - you say that you get an error, but you don't say whether that error is on the client or the server - your call to Send is on something called a "listener" which you don't show what it is (you bind a udpListener) - you don't show the values of the various addresses -- which address values you use matter! I like it that you're trying to isolate to a small amount of code (posting all your code is even worse!) so take this as a suggestion for how to improve your question-asking skills. (Which end up also improving your debugging skills if you use them on yourself, which in turn will lead to less questions ... virtuous cycle :-D)
  15. You have this same problem in a non-IOCP server, too! The main thing that changes with IOCP is how you structure your threads (one worker thread per CPU,) and how you structure your application to know when to do what (basically, build a state machine.) There are many structures that can make sense. The main trick is going to be building something that is thread-safe enough for multiple completion threads to run simultaneously, yet doesn't serialize all logic on a single lock. One way to structure a server like this would be: 1. There's a global hash table from "socket handle" to "client object." Inserts/removes/look-ups in this table need to be thread safe, but once you have the client object, the hash table doesn't need to stay locked. 2. The client object has an "incoming buffer" array of bytes, with a "head," "received," and "parsed" pointers (for where it's attempting to receive to, what has been received already, as well as how far it's managed to parse.) The buffer is treated as a cyclic buffer. The client object also has an OVERLAPPED structure to use for receiving into this buffer. As long as there is space in the in buffer, there should always be an outstanding receive call trying to receive into the empty space of this buffer, so each time a receive is completed, you should start another one (if there is enough empty space left,) then start parsing whatever you have. 3. The client object has another OVERLAPPED structure, plus an "outgoing buffer" array of bytes, with a "should write," "currently attempting to write" and a "written completed" pointer. As long as there is any data to write, you should have an outstanding write request, so when one completes, if there's anything in the buffer, start a new one. If something is added to the outgoing buffer, check whether a request is outstanding, and if not, start one. Note that you have to manage the "request outstanding" state yourself (with a flag or something,) because otherwise you will race with the OS. 4. The operation of "put stuff into the incoming buffer" must use a lock that's specific to the object, so each object probably has a lock (a CRITICAL_SECTION for example.) "Deal with data that was received" doesn't necessarily need a lock, if you "deal with data" before you "queue another read request." If you queue the read request before parsing the data, there's some risk that that second request will complete on another thread before you are done dealing with the data, so in that case, you also need a lock for the "deal with the data" read case. You'll typically also want to put a length value (say, 2 bytes, for a max length of 65535 bytes per packet) into the outgoing buffer before the actual bytes of the message. 5. "deal with the data" typically looks something like: Do I have less than 2 bytes? Just wait for more. Else, interpret those 2 bytes as a packet length, without consuming them. Do I have that many additional bytes? If not, just wait for more. Else, call some function to deal with a "complete, received packet, of size X" and then advance the "data read (buffer free)" pointer, and then go back to check whether you have another ready packet. 6. The handler for dealing with a packet may decide to forward the packet to other clients. To do so, iterate through the table of all clients, and call the "enqueue data" on each of those clients. The iteration over the table of all clients will need locking that table; calling write on each client object will lock each of those objects; there is risk for deadlock here if you're not careful! 7. You may run into the case where a client doesn't receive data fast enough, and there is no more buffer space left. You have the option of dropping the message and writing code that deals with not all messages getting to all clients, or detecting that the client can't keep up, and kick the client from the server. Those are your only two options. Specifically, you can NOT wait for the client to drain, because this will block a handling thread for an unknown amount of time -- potentially forever if the client is a suspended process that still responds to TCP ACKs but doesn't open the receive window. I recommend reasonably-sized buffers (256 kB per player?) and kicking players that don't receive their data fast enough. 8. Your "every so often" periodic message can be handled in a few different ways. One is to have a timer in the IOCP list of handles, and when it expires, iterate through all clients. Another is to have a separate thread which sleeps, and when it's time, wakes up, and iterates through all clients. A third is to have each client have a variable for when they last had one of those messages, and each time you receive something from the client (in the receive handling function,) check whether the time is right, and generate the outgoing packet and update the variable. As you can see, there are many, many, bits and pieces that need to go together "just so" to make a high-performance, correct, asynchronous networking server. You need to understand locking, threading, cyclic buffers, contention, TCP protocol details, timing, and a number of other concepts. If you can learn and apply all of these, you can build a beautiful, event-driven server that makes very good use of available machine resources. If you're new to all of these concepts at the same time, you will probably find that trying to climb 8 different walls at the same time will be quite a challenge. Good luck on your project, and make sure to keep us up to date on what you learn!
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!