|
1 | | - |
2 | 1 | # Migrating to SDL_net 3.0 |
3 | 2 |
|
4 | | -This guide provides useful information for migrating applications from SDL_net 2.0 to SDL_net 3.0. |
| 3 | +SDL_net 3.0 (aka "SDL3_net") is a dramatically different library than |
| 4 | +previous versions. The API has been completely redesigned. There is no |
| 5 | +compatibility layer. If you want to use it, you have to migrate to it. |
| 6 | + |
| 7 | +SDL3_net requires SDL3. It relies on many features that are new to SDL3, |
| 8 | +both internally and in the public API, so if your project is on SDL 1.2 or |
| 9 | +SDL2, you'll have to move your project to SDL3 at the same time. |
| 10 | + |
| 11 | +That being said, we think SDL3_net and SDL3 are both great pieces of |
| 12 | +software--significant improvements over their previous versions--and we |
| 13 | +think that once you move to them, you'll be quite happy you did. |
| 14 | + |
| 15 | +There are some things that don't have simple replacements that can be changed |
| 16 | +mechanically to migrate to SDL3_net. The new API is in many ways more |
| 17 | +powerful, but also much simpler. |
| 18 | + |
| 19 | +This migration guide will attempt to walk through the important details but |
| 20 | +it's possible that some things can't be done the same way. Feel free to open |
| 21 | +bug reports if you're totally stuck. |
| 22 | + |
| 23 | +The end of this document provides a "tl;dr" section that lists each SDL2_net |
| 24 | +function name and a brief explanation about what to do with it. |
| 25 | + |
| 26 | + |
| 27 | +## Things that are totally gone |
| 28 | + |
| 29 | +- Building SDL_net without SDL at all (the `WITHOUT_SDL` define in SDL2_net) |
| 30 | + is no longer an option. SDL3 is required, both in including the SDL_net |
| 31 | + public headers and in the library's implementation code. |
| 32 | +- Network addresses used to be in a public struct (IPaddress). The new object |
| 33 | + (NET_Address) is an opaque type. If your app was setting or reading IPv4 |
| 34 | + addresses directly in the struct, this will have to change. SDL3_net offers |
| 35 | + functions to create and query NET_Address objects. |
| 36 | +- UDP transmission no longer deals with "channels." This was a confusing |
| 37 | + interface in general. One could build a channel system on top of SDL3_net |
| 38 | + easily with an array of NET_Address objects, but NET_DatagramSocket only |
| 39 | + deals with sending and receiving from addresses now. |
| 40 | +- UDP packets had several API functions for memory management. In SDL3_net, |
| 41 | + you just provide a pointer to data you want to send, or a buffer to receive |
| 42 | + into, and that's it. Internal memory management is done with SDL_malloc(). |
| 43 | +- Socket sets are gone. The entire API has collapsed down into |
| 44 | + NET_WaitUntilInputAvailable(), which just takes an array of |
| 45 | + sockets to check. |
| 46 | + |
| 47 | + |
| 48 | +## Including SDL3_net |
| 49 | + |
| 50 | +The proper way to include SDL3_net's header is: |
| 51 | + |
| 52 | +```c |
| 53 | +#include <SDL3_net/SDL_net.h> |
| 54 | +``` |
| 55 | + |
| 56 | +Like SDL3, the new convention is to use `<>` brackets and a subdirectory. |
| 57 | + |
| 58 | + |
| 59 | +## Symbol names |
| 60 | + |
| 61 | +In SDL2_net, all functions started with `SDLNet_` and macros started with |
| 62 | +`SDLNET_`. In SDL3_net, everything starts with `NET_`. |
| 63 | + |
| 64 | + |
| 65 | +## Versioning |
| 66 | + |
| 67 | +Versions are now bits packed into a single int instead of a struct, using |
| 68 | +SDL3's usual magic for unpacking these. |
| 69 | + |
| 70 | + |
| 71 | +## Return values |
| 72 | + |
| 73 | +Most things that returned an int in SDL2_net would return a -1 or 0 to report |
| 74 | +error or success, respectively. In SDL3_net, these sorts of functions return |
| 75 | +bools, with true for success and false for failure. Be careful in migrating, |
| 76 | +as your compiler might catch this and warn you... |
| 77 | + |
| 78 | +```c |
| 79 | +if (NET_Init() < 0) { /* failure! */ } |
| 80 | +``` |
| 81 | +
|
| 82 | +...but it won't catch this... |
| 83 | +
|
| 84 | +```c |
| 85 | +if (!NET_Init()) { /* Success in SDL2_net, but failure in SDL3_net! */ } |
| 86 | +``` |
| 87 | + |
| 88 | + |
| 89 | +## Multiple protocols |
| 90 | + |
| 91 | +SDL2_net's public API was hardcoded to offer IPv4 networking. SDL3_net has |
| 92 | +replaced its IPaddress struct with the opaque NET_Address. This can handle |
| 93 | +addresses from different network protocols, including future ones that haven't |
| 94 | +been conceived of yet. But most notably: it means that your app can abstractly |
| 95 | +handle both IPv4 and IPv6. |
| 96 | + |
| 97 | +(In theory, this could also work with, say, an IPX or AppleTalk network in the |
| 98 | +wild, but it might require small fixes to SDL3_net's code. Send patches.) |
| 99 | + |
| 100 | +Using NET_ResolveHostname() might give you any available protocol, letting |
| 101 | +the OS decide the best option for the system. |
| 102 | + |
| 103 | +SDL3_net goes even further: it offers "dual-stack" sockets. If you create a |
| 104 | +server or datagram socket bound to a NULL address, it will try to create both |
| 105 | +IPv4 and IPv6 OS-level sockets under the hood, and listen on both. This is |
| 106 | +not just an IPv4-over-IPv6 bridge. This makes it possible that you'll get |
| 107 | +traffic from both protocols at the same time, with clients coming from |
| 108 | +addresses that look like 157.90.7.176 and 2a01:4f8:251:5583::2 simultaneously, |
| 109 | +and your server can trivially bridge between them. |
| 110 | + |
| 111 | + |
| 112 | +## Blocking vs Non-blocking |
| 113 | + |
| 114 | +SDL2_net was (mostly) a blocking API. If it took time to complete a network |
| 115 | +operation, you would have to wait. Some waits might be short, like passing a |
| 116 | +datagram to the kernel's transmit buffers, but others might take many seconds, |
| 117 | +depending on network conditions. It offered a wrapper over the select() call |
| 118 | +with what it called "socket sets" to query if data was available, to avoid |
| 119 | +blocking. A DNS lookup was going to take as long as it takes. |
| 120 | + |
| 121 | +SDL3_net takes the opposite approach: everything is _non-blocking_ now. If |
| 122 | +data is not available, read calls will return immediately, reporting this. |
| 123 | +If data cannot be sent right away, it will queue internally until the library |
| 124 | +can send it later. DNS queries happen on a pool of background threads and the |
| 125 | +app can check in from time to time to see if there are results available. |
| 126 | + |
| 127 | +The concept of "socket sets" has collapsed down into a single function: |
| 128 | +NET_WaitUntilInputAvailable(), which takes an array of various SDL_net |
| 129 | +objects and a timeout. A non-blocking query has a timeout of zero, an |
| 130 | +indefinite wait until _something_ happens is a timeout of -1. |
| 131 | + |
| 132 | +If you prefer blocking behavior, there are functions to wait for specific |
| 133 | +things to happen (with an optional timeout). These will put the calling |
| 134 | +thread to sleep for efficiency. |
| 135 | + |
| 136 | +- NET_WaitUntilResolved: Wait for a specific DNS query to finish. |
| 137 | +- NET_WaitUntilConnected: Wait for a specific stream socket to connect. |
| 138 | +- NET_WaitUntilStreamSocketDrained: Wait for a stream socket to send all |
| 139 | + queued data. |
| 140 | +- NET_WaitUntilInputAvailable: Like the other NET_WaitUntil* functions, but |
| 141 | + check multiple things at the same time. |
| 142 | + |
| 143 | +There are simple non-blocking query functions, too: NET_GetAddressStatus, |
| 144 | +NET_GetConnectionStatus, NET_GetStreamSocketPendingWrites. |
| 145 | + |
| 146 | + |
| 147 | +## Thread safety |
| 148 | + |
| 149 | +All functions in SDL3_net are thread safe, with one caveat: you can not |
| 150 | +operate on the same socket from two threads at once. Two threads can work |
| 151 | +with two separate sockets at the same time without problems. |
| 152 | + |
| 153 | + |
| 154 | +## Network byte order |
| 155 | + |
| 156 | +In SDL2_net, things like network addresses and port numbers might need to be |
| 157 | +in "network byte order" (bigendian) in some cases. In SDL3_net, these values |
| 158 | +are always in "host byte order" (the native byte order of the system), and |
| 159 | +SDL3_net will manage byteswapping behind the scenes as necessary. |
| 160 | + |
| 161 | +Of course any payload bytes sent over the internet might comes from any |
| 162 | +computer with any byte order, so the app still needs to deal with that exactly |
| 163 | +the same as they did with SDL2_net. |
| 164 | + |
| 165 | + |
| 166 | +## Initialization |
| 167 | + |
| 168 | +SDL2_net expected you to call SDL_Init() before SDLNet_Init(). Now you don't |
| 169 | +have to. |
| 170 | + |
| 171 | +```c |
| 172 | +if (!NET_Init()) { |
| 173 | + SDL_Log("NET_Init failed: %s", SDL_GetError()); |
| 174 | +} else { |
| 175 | + SDL_Log("SDL_net is ready!"); |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +NET_Init() is reference-counted; it's safe to call it more than once, and |
| 180 | +only the first call will do actual initialization tasks. Likewise, actual |
| 181 | +deinitialization with NET_Quit() will only happen when it has been called |
| 182 | +as many times as NET_Init() has. This allows different parts of the program |
| 183 | +to initialize and use SDL3_net without knowing about other parts using it |
| 184 | +too. |
| 185 | + |
| 186 | + |
| 187 | +## Stream sockets |
| 188 | + |
| 189 | +SDL2_net represents server ("listen") sockets with the same type as connected |
| 190 | +sockets: a TCPsocket object. In SDL3_net, these are split into two separate |
| 191 | +objects: NET_Server and NET_StreamSocket. |
| 192 | + |
| 193 | +NET_StreamSocket is the thing that actually transmits data over the network |
| 194 | +between a client and server; both ends of the connection have one and both |
| 195 | +can read or write to theirs. |
| 196 | + |
| 197 | +NET_Server is the thing that will listen for new connections from clients, and |
| 198 | +generate the server's end of the NET_StreamSocket for each one. |
| 199 | + |
| 200 | +Creating NET_Server with a NULL address will possibly listen on both IPv4 and |
| 201 | +IPv6 networks separately under the hood. |
| 202 | + |
| 203 | + |
| 204 | +## Datagram sockets |
| 205 | + |
| 206 | +SDLNet_UDP_Open() would let you specify a port but only binds to INADDR_ANY. |
| 207 | +SDL3_net's NET_CreateDatagramSocket() will let you bind to specific interfaces |
| 208 | +or specify a NULL address for the equivalent of INADDR_ANY (but it might |
| 209 | +listen on both IPv4 and IPv6 interfaces under the hood). |
| 210 | + |
| 211 | + |
| 212 | +## Broadcasting |
| 213 | + |
| 214 | +In SDL2_net, you could send UDP packets to the subnet's broadcast address |
| 215 | +(if you could figure it out), or 255.255.255.255 (which wouldn't work on |
| 216 | +Windows). |
| 217 | + |
| 218 | +In SDL3_net, you create a datagram socket with the |
| 219 | +`NET_PROP_DATAGRAM_SOCKET_ALLOW_BROADCAST_BOOLEAN` property, and call |
| 220 | +NET_SendDatagram() with a NULL address. |
| 221 | + |
| 222 | +This abstracts out broadcasting for different protocols (including IPv6, |
| 223 | +where SDL3_net fakes this under the hood with multicasting on your behalf). |
| 224 | +SDL3_net will also make efforts to broadcast to all network interfaces if |
| 225 | +appropriate, and broadcast to different network protocols. As such, it's |
| 226 | +possible that a single broadcast packet will arrive for the same app on |
| 227 | +multiple interfaces, and it might appear to come from different computers, |
| 228 | +since it might have an IPv4 and IPv6 address. Plan accordingly, so you know |
| 229 | +whether to drop duplicate packets. |
| 230 | + |
| 231 | +Futher, if you broadcast from a client in order to locate servers, and that |
| 232 | +server is listening on both IPv4 and IPv6, you might get two responses that |
| 233 | +appear to be different servers. Plan to add something unique in the payload |
| 234 | +so you can recognize they are the same machine, or separate out IPv4/IPv6 |
| 235 | +servers in your server browser, etc. |
| 236 | + |
| 237 | +(And, of course, please limit the amount of broadcasting your app does in |
| 238 | +general, to be a good citizen of your subnet!) |
| 239 | + |
| 240 | + |
| 241 | +## Simulating failure |
| 242 | + |
| 243 | +SDL2_net had SDLNet_UDP_SetPacketLoss(). SDL3_net extends this to other pieces |
| 244 | +of the network stack: you can set up separate simulated failure percentages |
| 245 | +for stream sockets (being reliable, they will introduce delays into the stream |
| 246 | +and possibly just drop the connection outright sometimes), and DNS lookups |
| 247 | +(some will take longer, and some will fail, as if packets aren't arriving from |
| 248 | +the DNS server or domains are mysteriously missing). |
| 249 | + |
| 250 | +These functions are NET_SimulateAddressResolutionLoss(), |
| 251 | +NET_SimulateStreamPacketLoss(), and NET_SimulateDatagramPacketLoss(). |
| 252 | + |
| 253 | + |
| 254 | +## tl;dr |
| 255 | + |
| 256 | +A very brief comment on what to do with each symbol in SDL2_net. |
| 257 | + |
| 258 | +Some of these are listed as "no equivalent in SDL3_net" but could possibly |
| 259 | +be added if there is a need. If you're stuck, please file an issue and we |
| 260 | +can discuss it! |
| 261 | + |
| 262 | +- SDL_NET_MAJOR_VERSION => SDL_NET_MAJOR_VERSION |
| 263 | +- SDL_NET_MINOR_VERSION => SDL_NET_MAJOR_VERSION |
| 264 | +- SDL_NET_PATCHLEVEL => SDL_NET_MICRO_VERSION |
| 265 | +- SDL_NET_VERSION => SDL_NET_VERSION |
| 266 | +- SDL_NET_VERSION_ATLEAST => SDL_NET_VERSION_ATLEAST |
| 267 | +- SDLNet_Linked_Version => NET_Version |
| 268 | +- SDL_NET_COMPILEDVERSION => SDL_NET_VERSION |
| 269 | +- SDLNet_Init => NET_Init |
| 270 | +- SDLNet_Quit => NET_Quit |
| 271 | +- IPaddress => NET_Address |
| 272 | +- SDLNet_ResolveHost => NET_ResolveHostname (the port number from SDL2_net is specified in other functions). |
| 273 | +- SDLNet_ResolveIP => NET_GetAddressString (this does not attempt to figure out the hostname via DNS, though! File an issue if you need this!) |
| 274 | +- SDLNet_GetLocalAddresses => NET_GetLocalAddresses |
| 275 | +- TCPsocket => NET_StreamSocket |
| 276 | +- SDLNet_TCP_OpenServer => NET_CreateServer |
| 277 | +- SDLNet_TCP_OpenClient => NET_CreateClient |
| 278 | +- SDLNet_TCP_Open => No direct equivalent, use NET_CreateServer or NET_CreateClient as appropriate. |
| 279 | +- SDLNet_TCP_Accept => NET_AcceptClient |
| 280 | +- SDLNet_TCP_GetPeerAddress => NET_GetStreamSocketAddress |
| 281 | +- SDLNet_TCP_Send => NET_WriteToStreamSocket |
| 282 | +- SDLNet_TCP_Recv => NET_ReadFromStreamSocket |
| 283 | +- SDLNet_TCP_Close => NET_DestroyStreamSocket |
| 284 | +- SDLNET_MAX_UDPCHANNELS => no equivalent in SDL3_net. |
| 285 | +- SDLNET_MAX_UDPADDRESSES => no equivalent in SDL3_net. |
| 286 | +- UDPpacket => NET_Datagram |
| 287 | +- SDLNet_AllocPacket => no equivalent in SDL3_net. |
| 288 | +- SDLNet_ResizePacket => no equivalent in SDL3_net. |
| 289 | +- SDLNet_FreePacket => NET_DestroyDatagram |
| 290 | +- SDLNet_AllocPacketV => no equivalent in SDL3_net. |
| 291 | +- SDLNet_FreePacketV => NET_DestroyDatagram in a loop. |
| 292 | +- UDPsocket => NET_DatagramSocket |
| 293 | +- SDLNet_UDP_Open => NET_CreateDatagramSocket |
| 294 | +- SDLNet_UDP_SetPacketLoss => NET_SimulateDatagramPacketLoss |
| 295 | +- SDLNet_UDP_Bind => no equivalent in SDL3_net. |
| 296 | +- SDLNet_UDP_Unbind => no equivalent in SDL3_net. |
| 297 | +- SDLNet_UDP_GetPeerAddress => no equivalent in SDL3_net. NET_Datagram::addr has it in received packets. |
| 298 | +- SDLNet_UDP_SendV => NET_SendDatagram in a loop. |
| 299 | +- SDLNet_UDP_Send => NET_SendDatagram |
| 300 | +- SDLNet_UDP_RecvV => NET_ReceiveDatagram in a loop. |
| 301 | +- SDLNet_UDP_Recv => NET_ReceiveDatagram |
| 302 | +- SDLNet_UDP_Close => NET_DestroyDatagramSocket |
| 303 | +- SDLNet_GenericSocket => `void *` |
| 304 | +- SDLNet_SocketSet => no equivalent in SDL3_net. Just pass an array of things to NET_WaitUntilInputAvailable(). |
| 305 | +- SDLNet_AllocSocketSet => no equivalent in SDL3_net. Just pass an array of things to NET_WaitUntilInputAvailable(). |
| 306 | +- SDLNet_AddSocket => no equivalent in SDL3_net. Just pass an array of things to NET_WaitUntilInputAvailable(). |
| 307 | +- SDLNet_TCP_AddSocket => no equivalent in SDL3_net. Just pass an array of things to NET_WaitUntilInputAvailable(). |
| 308 | +- SDLNet_UDP_AddSocket => no equivalent in SDL3_net. Just pass an array of things to NET_WaitUntilInputAvailable(). |
| 309 | +- SDLNet_DelSocket => no equivalent in SDL3_net. Just pass an array of things to NET_WaitUntilInputAvailable(). |
| 310 | +- SDLNet_TCP_DelSocket => no equivalent in SDL3_net. Just pass an array of things to NET_WaitUntilInputAvailable(). |
| 311 | +- SDLNet_UDP_DelSocket => no equivalent in SDL3_net. Just pass an array of things to NET_WaitUntilInputAvailable(). |
| 312 | +- SDLNet_CheckSockets => NET_WaitUntilInputAvailable |
| 313 | +- SDLNet_SocketReady => no equivalent in SDL3_net. Just try to use them, they're all non-blocking. |
| 314 | +- SDLNet_FreeSocketSet => no equivalent in SDL3_net. Just pass an array of things to NET_WaitUntilInputAvailable(). |
| 315 | +- SDLNet_SetError => SDL_SetError |
| 316 | +- SDLNet_GetError => SDL_GetError |
| 317 | +- SDLNet_Write16 => SDL_SwapBE16 |
| 318 | +- SDLNet_Write32 => SDL_SwapBE32 |
| 319 | +- SDLNet_Read16 => SDL_SwapBE16 |
| 320 | +- SDLNet_Read32 => SDL_SwapBE32 |
5 | 321 |
|
6 | | -SDLNet_GetError() has been replaced with SDL_GetError(). |
|
0 commit comments