TCP is a streaming protocol. The sender sends a byte stream and the receiver receives byte stream data. Usually, in the application layer, a single protocol package will be identified in the byte stream through header + body. The sender packages the original data into header + body. The header is a fixed byte packet header, which identifies how many bytes of data the body contains. The receiver reads the fixed byte header first, and then reads the specific body data according to the header.
In the game, you always need to write some robot clients that communicate with the server. Our project will be used to using Lua to implement, so it is inevitable to parse TCP network data. The logic is very simple, usually using string connection, which can be completed in a few lines of code. Complete code Click here , the main code snippets are listed below.
function mt:init(header_bytes) self.cache = "" self.header_bytes = header_bytes end function mt:input(str) self.cache = self.cache .. str end function mt:output() local hb = self.header_bytes local total = #self.cache if total <= hb then return end local body_bytes = string.unpack(">I2", self.cache) if hb + body_bytes > total then return end local body = self.cache:sub(hb + 1, hb + body_bytes) self.cache = self.cache:sub(hb + body_bytes + 1) return body end
The input function is used to cache the received data, and the output function is used to parse the received byte stream into a single protocol packet. The string operations involved in input and output are inefficient when they are called frequently. If the efficiency requirements of tools are improved, they will no longer meet the needs. But I want this robot to be as simple as possible. I will first consider using pure Lua to solve this problem.
The problem of the above scheme is that the efficiency of string connection is relatively low. When receiving data more frequently, string operation occupies a lot of CPU resources. Therefore, the idea of the new scheme is to avoid string connection as much as possible, as shown below.
function mt:init(header_bytes) self.cache_list = {} self.total_size = 0 self.header_bytes = header_bytes self.body_list = {} end function mt:input(str) local cache = self.cache_list local block = cache[#cache] if block and #block < self.header_bytes then cache[#cache] = block .. str else cache[#cache + 1] = str end self.total_size = self.total_size + #str end function mt:output() local body_list = self.body_list local cache_body = body_list[1] if cache_body then table.remove(body_list, 1) return cache_body end local total_str if #self.cache_list == 1 then total_str = self.cache_list[1] else total_str = table.concat(self.cache_list) self.cache_list = {total_str} end local hb = self.header_bytes local start_index = 1 while true do if not total_str or #total_str < hb then break end if self.total_size <= hb then break end local header = total_str:sub(start_index, start_index + hb - 1) local body_bytes = string.unpack(">I2", header) if hb + body_bytes > self.total_size then break end self.total_size = self.total_size - hb - body_bytes local new_index = start_index + hb + body_bytes local body = total_str:sub(start_index + hb, new_index - 1) if cache_body then body_list[#body_list + 1] = body else cache_body = body end start_index = new_index end if start_index > 1 then self.cache_list = {total_str:sub(start_index)} end return cache_body end
Instead of string connection, the input function saves the received data to self cache_ List. Then, in the output function, parse the protocol data as much as possible at one time, and then save it in self body_ List, if self body_ If the list has data, you can directly return the data here.
See for test method here . The new method can basically analyze 64M data in an instant.
It is better to call the output function once after a period of time, which will be more efficient. The frame rate of mobile game client is generally 30 FPS or 60 FPS. Therefore, you can call the output function once every 1 / 60 seconds, or even once every 1 / 100 seconds.