Garry's Mod Wiki

Revision Difference

Net_Library_Usage#544652

<cat>Dev.Lua</cat> <note>Before reading this page, please ensure you understand the basics of Lua, or you will get stuck!</note> # What is the net library? The net library is one of a number of ways to send data between the client and server. The major advantages of the net library is the large size limit compared to usermessage and console commands, 65533 bytes (around 64KiB) per message, and the ability to send data backwards - from the client to the server. # Using the net library ## Precaching messages When you send a net message, you will most likely get an error which contains a shortened URL. This is because the message isn't precached. To precache a message, just call this once server-side: ```lua util.AddNetworkString( "YourMessageName" ) ``` <note>This needs some time after you can use this net name.</note> ## Receiving messages To be able to send data with the net library, there must be something to handle that data when it's received. We use the function <page>net.Receive</page>, which has two arguments: the name of the net message and the callback function to run when the message is received. Below is an example of receiving a net message: ```lua net.Receive( "MyMessage", function( len, ply ) print( "I got it!" ) end ) ``` Once our net message "MyMessage" is sent, the receiving function is called. The first argument of the receive function is the length of the message (it can be used for debugging or networking protection), the second is the player who sent it, this is only used when <page>net.SendToServer</page> is called (see <page text="below">Net_Library_Usage#Sending_messages</page>). ## Sending messages Now that we have a function to receive it, we need to send the message. Prior to sending a net message, the string name of the message must be precached once serverside with <page>util.AddNetworkString</page>. ```lua util.AddNetworkString( "myMessage" ) ``` Then to start sending a net message, call <page>net.Start</page> with the name of the message. Then, * Serverside: Use <page>net.Send</page>, which has one argument: a <page>Player</page> or a table of players. If you're sending to all players, use <page>net.Broadcast</page>. * Clientside: Use <page>net.SendToServer</page>. For example, if you wanted to send the myMessage net message to the first player on the server, you would do ```lua net.Start( "myMessage" ) net.Send( Entity( 1 ) ) ``` We send an empty net message, next we will look at sending data in net messages. Generally empty net messages are used as a simple way of alerting the player of something that has happened on the server which the client does not know about unless the server tells them about it, for example, after a certain time has passed on the server, an empty net message could be sent to the client to tell them that a menu should be opened. ## Transferring data To send data, after calling the <page>net.Start</page> function, you can use the following functions, each for their specified data type: - <page>net.WriteBool</page> - <page>net.WriteString</page> - <page>net.WriteInt</page> - <page>net.WriteUInt</page> - <page>net.WriteFloat</page> - <page>net.WriteDouble</page> - <page>net.WriteAngle</page> - <page>net.WriteVector</page> - <page>net.WriteNormal</page> - <page>net.WriteColor</page> - <page>net.WriteTable</page> - <page>net.WriteEntity</page> - <page>net.WriteData</page> - <page>net.WriteBit</page> - <page>net.WriteType</page> To send the message, use <page>net.Send</page> on the server, or <page>net.SendToServer</page> on the client. You can also send a message to every connected player serverside using <page>net.Broadcast</page> For reading data, there are functions for each writable type of data above, generally being net.Read(Type). <note>You must read data in the same order that you sent it.</note> ## Examples It is recommended you have a basic understanding of hooks before you attempt these. ### Example 1: Telling the client a player has died. <page>GM:PlayerDeath</page> is a hook that is only called serverside. If we want to tell every player that a player has died we can use the net library. Serverside: ```lua util.AddNetworkString( "PlayerDied" ) hook.Add( "PlayerDeath", "NotifyClient", function( ply, inf, att ) net.Start( "PlayerDied" ) net.WriteEntity( ply ) net.Broadcast() end ) ``` Clientside: ```lua net.Receive( "PlayerDied", function() local ply = net.ReadEntity() if not ply:IsValid() then return end -- Make sure the dead player exists chat.AddText( ply, Color( 255, 0, 0 ), "has died!" ) end ) ``` Explanation: Since chat.AddText is a function that we can only use on the client we need to find a way to tell the client that a player died. The solution is simple, in the <page>GM:PlayerDeath</page> hook, use <page>net.WriteEntity</page> to send the entity to the client, read the entity then run chat.AddText with the colors we want. Challenges (Don't progress until you have done these!): - Can you make it tell everyone who the player was killed by? - Can you make it so that it only tells the attacker they killed a player? ### Example 2: Giving the server information from the client. We want to know how old the player is, and we also want everyone on the server to know about it. The problem is, VGUI functions can only be called clientside, but don't worry, the net library is here! Clientside: ```lua local frame = vgui.Create( "DFrame" ) frame:SetSize( 400, 400 ) frame:SetTitle( "What is your age?" ) frame:Center() local age = vgui.Create( "DNumberWang", frame ) age:Dock( TOP ) age:SetDecimals( 0 ) age:SetMin( 0 ) age:SetMax( 100 ) local send = vgui.Create( "DButton", frame ) send:Dock( TOP ) send:SetText( "Send my age!" ) send.DoClick = function() net.Start( "SendAge" ) net.WriteUInt( age:GetValue(), 8 ) -- The second argument is 8 since the age will never be above 255. Doing this saves bandwidth and can reduce bandwidth. net.SendToServer() end ``` Notice how we don't actually tell the server who we are using <page>net.WriteEntity</page>. This is because serverside, <page>net.Receive</page>'s second argument is the player who sent it. We'll go in to more depth about net message security after this example. Serverside: ```lua util.AddNetworkString( "SendAge" ) net.Receive( "SendAge", function( len, ply ) -- len is the net message length, which we don't care about, ply is the player who sent it. local age = net.ReadUInt( 8 ) -- Notice how ReadUInt requires an argument. This is the second argument of WriteUInt, which tells us how big the number is. PrintMessage( HUD_PRINTTALK, ply:Nick() .. " is " .. age .. " years old!" ) end ) ``` Challenges (Don't continue until you have done these!): Can you make the age message show in color? Can you make the message also show the players favorite food? ## Security When sending a net message from client to server, you pose a huge security risk. Let's pretend for a moment that someone has written this code serverside: ```lua util.AddNetworkString( "BanPlayer" ) net.Receive( "BanPlayer", function( len, ply ) local toBan = net.ReadEntity() local time = net.ReadUInt( 32 ) toBan:Ban( time, true ) end ) ``` Take a look at this code. Can you figure out what could happen if this code was actually used on a server? Let's break it down. This net message bans a player when the BanPlayer net message is sent to the server. However, there are no checks to see if the player is actually an admin or not. This means people could send their own net messages, from outside the script that it was created in, gaining access to ban any player on your server. To prevent this, follow 1 simple rule: **DON'T TRUST THE CLIENT** Don't preform checks on user input clientside then assume that they are fine serverside. Don't be lazy and only check data in the realm where it can be easily manipulated. You should look out for negative numbers, people sending net messages very fast, people trying to write entities that should never be written, as long as many other things. Before you publish your code, try as hard as you can to break it. You are still able to ask user for input, the concern is in validity of that input. <warning>This is a common issue when you want to know who sent the message and instead of using the second argument in net.Receive you do `net.WriteEntity(LocalPlayer())` on clientside and `local caller = net.ReadEntity()` on serverside. Do not do this, this will expose your server code and it can be exploited!</warning> An easy way to secure your Net functions: ```lua net.Receive("YourNetName", function( len, ply ) if !ply:IsSuperAdmin() then return end -- leaves the function if the sender isn't superadmin print( "This is a secured Net Message!" ) end) ``` # Improving <note>This section is not recommended for beginners due to complexity.</note> When you design your net communication it's important to make it effective as possible. Here is some sections you can check: ## Simplify The less data you can send with the same efficiency, the better. ### Find the data you send that can be known already for both sides. Try to avoid <page>Player:SendLua</page> and <page>Global.BroadcastLua</page>. You don't need to send the whole derma menu to client every time. Create a clientside function that opens this menu and send empty net message from serverside. Net cost before: `(length of the code + 1) * 8` bits per message. Net cost after: `0` bits per message. ### Find appropriate amount of bits for Ints and UInts As a beginner you are allowed to use 32 bits in the second parameter in <page>net.WriteUInt</page> and <page>net.WriteInt</page>, however this is too much if you send small numbers. <page>net.WriteUInt</page> and <page>net.WriteInt</page> pages have tables you can use to find amount of bits you need. For example player sends a number he chose, you know the number should be in range from 0 to 64. The number represents amount of players to affect (server has 64 slots). We don't have negative values, so we use <page>net.WriteUInt</page>. We need 7 bits to fit 65 numbers (0-64). 7 bits is better than 32 for sure, however here we can use our logic: if this is sent by a player then there is at least one player, so it can't be `0`, we can make player to choose from 1 to 64. This is wtill 7 bits, but if we subtract 1 when sending (and adding 1 back on reading) we can do 6 bits. 7 bits is better than 32 for sure, however here we can use our logic: if this is sent by a player then there is at least one player, so it can't be `0`, we can make player to choose from 1 to 64. This is still 7 bits, but if we subtract 1 when sending (and adding 1 back on reading) we can do 6 bits. ``` send.DoClick = function() net.Start( "SelectPlayers" ) net.WriteUInt( slider:GetValue() - 1, 6 ) net.SendToServer() end ``` ``` net.Receive( "SelectPlayers", function() local amount = net.ReadUInt(6) + 1 print( "Amount:", amount ) end ) ``` Net cost before: `32` bits per message. Net cost after: `6` bits per message. ### Try to send static structure instead of net.WriteTable <page>net.WriteTable</page> is very beginner-friendly function but it's extremely expensive. Let's say you want to send <page>Structures/SunInfo</page> to the server. This structure has 2 fields: one vector and one number. <page>net.WriteTable</page>'s problem it doesn't know how large your number can be, it doesn't differ vectors from normalized vectors. <page>net.WriteTable</page> sends key type, key data, value type, value data. Writing the type costs 8 bits. Vector costs vary amount of bits. <page>net.WriteTable</page> writes all numbers as doubles which is 64 bits. <page>net.WriteTable</page> writes all vectors (normalized and not) as vectors. So if you send <page>Structures/SunInfo</page> by <page>net.WriteTable</page> it will send: ``` net.WriteTable( util.GetSunInfo() ) ``` |Bits written|Value type|Value|Description| |:----------:|:--------:|:---:|:---------:| |8|`number`|`TYPE_STRING`|Key type (UInt)| |80|`string`|`direction`|Key| |8|`number`|`TYPE_VECTOR`|Value type (UInt)| |3-69|`Vector`|`util.GetSunInfo().direction`|Value| |8|`number`|`TYPE_STRING`|Key type (UInt)| |96 |`string`|`obstruction`|Key| |8|`number`|`TYPE_NUMBER`|Value type (UInt)| |64|`number`|`util.GetSunInfo().obstruction`|Value| |8|`number`|`TYPE_NIL`|End of the table| Total: **283 bits per message minimum. ~349 bits per message maximum**. This is super expensive, since we know our fields in the table we can get rid of keys (From 283-349 to 91-157). We also know our values so we don't need to send their type. At this point you should stop using <page>net.WriteTable</page>. Let's use <page>net.WriteNormal</page> for the normalized vector (costs less bits and doesn't lose precision that much) and <page>net.WriteFloat</page> for normalized number (if you need full precision use <page>net.WriteDouble</page>, but here it's not that important): ``` local suninfo = util.GetSunInfo() net.WriteNormal( suninfo.direction ) net.WriteFloat( suninfo.obstruction ) ``` |Bits written|Value type|Value| |:----------:|:--------:|:---:| |3-27|`Vector`|`suninfo.direction`| |32|`number`|`suninfo.obstruction`| Total: 35 bits per message minimum. 59 bits per message maximum. I think the difference is pretty obvious. ### Compression If you need to send large amount of data you can compress it. This method saves net bandwidth but may increase CPU time. <warning>This method is not recommended for JSON due to hash-DOS if used for client to server communication.</warning> Functions used for compression are <page>util.Compress</page>, <page>util.Decompress</page>, <page>net.WriteData</page>, <page>net.ReadData</page>, <page>net.WriteUInt</page> and <page>net.ReadUInt</page>. Sending: ``` local compressedString = util.Compress( largestring ) local len = #compressedString net.WriteUInt( len, 16 ) net.WriteData( compressedString, len ) ``` First we need to compress our string and send it by <page>net.WriteData</page>. However <page>net.ReadData</page> requires the length, so we need to send it first and the data itself. Reading: ``` local len = net.ReadUInt( 16 ) local compressedString = net.ReadData( len ) local largestring = util.Decompress( compressedString ) ``` First we need to get the length of the compressed string, then read it and decompress. <note>If you net message contains only a compressed string you can use a `len` argument from the <page>net.Receive</page> and use it instead of `local len = net.ReadUInt( 16 )`</note> <note>If your net message contains only a compressed string you can use a `len` argument from the <page>net.Receive</page> and use it instead of `local len = net.ReadUInt( 16 )`</note> ## Balance Net channel may overflow if too many messages or too much data were sent. Here is what can be done for this: ### Find an optimal rate for your net messages You can find a balance between time and size of net messages. If you experienced lag, you should probably split the net message into parts, unless you need to send it instantly. Find an optimal delay for sending messages. Can you send them once per minute instead of a second? ### Compression If you need to send large data and you can't delay it or split use compression method <page text="mentioned above">Net Library Usage#compression</page>. ### Unreliable mode <page>net.Start</page> has second parameter `unreliable`. Which decide how to send your net message: on reliable layer or not. Reliable layer may overflow on large data. You can switch to unreliable mode. This should help your net messages not to overflow but they are not guaranteed to reach the client. This can be used to send information that doesn't break anything if it gets lost.