summaryrefslogtreecommitdiff
path: root/xmake/core/base/socket.lua
diff options
context:
space:
mode:
authorruki <[email protected]>2019-11-04 22:20:02 +0800
committerGitHub <[email protected]>2019-11-04 22:20:02 +0800
commitb1eee1ec65dd0f9d3a26af52596d86c331160587 (patch)
tree6811a354f07c146ba4417e649d3a31d0c9aabd45 /xmake/core/base/socket.lua
parent20b40c57bbe9d28f822d5feac304a5b892b02790 (diff)
parentf00316a42943a9a75303658f8d646d35f9e8d1b9 (diff)
Merge pull request #593 from xmake-io/socket
Add io.socket support
Diffstat (limited to 'xmake/core/base/socket.lua')
-rw-r--r--xmake/core/base/socket.lua581
1 files changed, 581 insertions, 0 deletions
diff --git a/xmake/core/base/socket.lua b/xmake/core/base/socket.lua
new file mode 100644
index 000000000..4a2dfdcdc
--- /dev/null
+++ b/xmake/core/base/socket.lua
@@ -0,0 +1,581 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015 - 2019, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file socket.lua
+--
+
+-- define module
+local socket = socket or {}
+local _instance = _instance or {}
+
+-- load modules
+local io = require("base/io")
+local bytes = require("base/bytes")
+local table = require("base/table")
+local string = require("base/string")
+
+-- the socket types
+socket.TCP = 1
+socket.UDP = 2
+socket.ICMP = 3
+
+-- the socket families
+socket.IPV4 = 1
+socket.IPV6 = 2
+
+-- the socket events
+socket.EV_RECV = 1
+socket.EV_SEND = 2
+socket.EV_CONN = socket.EV_SEND
+socket.EV_ACPT = socket.EV_RECV
+
+-- new a socket
+function _instance.new(socktype, family, sock)
+ local instance = table.inherit(_instance)
+ instance._SOCK = sock
+ instance._TYPE = socktype
+ instance._FAMILY = family
+ setmetatable(instance, _instance)
+ return instance
+end
+
+-- get socket type
+function _instance:type()
+ return self._TYPE
+end
+
+-- get socket family
+function _instance:family()
+ return self._FAMILY
+end
+
+-- get socket rawfd
+function _instance:rawfd()
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return nil, errors
+ end
+
+ -- get rawfd
+ local result, errors = io.socket_rawfd(self._SOCK)
+ if not result and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ return result, errors
+end
+
+-- bind socket
+function _instance:bind(addr, port)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- bind it
+ local ok, errors = io.socket_bind(self._SOCK, addr, port, self:family())
+ if not ok and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ return ok, errors
+end
+
+-- listen socket
+function _instance:listen(backlog)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- listen it
+ local ok, errors = io.socket_listen(self._SOCK, backlog or 10)
+ if not ok and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ return ok, errors
+end
+
+-- accept socket
+function _instance:accept(opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- accept it
+ local sock, errors = io.socket_accept(self._SOCK)
+ if not sock and not errors then
+ opt = opt or {}
+ local events, waiterrs = self:wait(socket.EV_ACPT, opt.timeout or -1)
+ if events == socket.EV_ACPT then
+ sock, errors = io.socket_accept(self._SOCK)
+ else
+ errors = waiterrs
+ end
+ end
+ if not sock and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ if sock then
+ sock = _instance.new(self:type(), self:family(), sock)
+ end
+ return sock, errors
+end
+
+-- connect socket
+function _instance:connect(addr, port, opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- connect it
+ local ok, errors = io.socket_connect(self._SOCK, addr, port, self:family())
+ if ok == 0 then
+ opt = opt or {}
+ local events, waiterrs = self:wait(socket.EV_CONN, opt.timeout or -1)
+ if events == socket.EV_CONN then
+ ok, errors = io.socket_connect(self._SOCK, addr, port, self:family())
+ else
+ errors = waiterrs
+ end
+ end
+ if ok < 0 and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ return ok, errors
+end
+
+-- send data to socket
+function _instance:send(data, opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- data is bytes? unpack the raw address
+ local datasize = #data
+ if type(data) == "table" and data.caddr then
+ datasize = data:size()
+ data = {data = data:caddr(), size = data:size()}
+ end
+
+ -- init start and last
+ opt = opt or {}
+ local start = opt.start or 1
+ local last = opt.last or datasize
+
+ -- check start and last
+ if start > last or start < 1 then
+ return -1, string.format("%s: invalid start(%d) and last(%d)!", self, start, last)
+ end
+
+ -- send it
+ local send = 0
+ local real = 0
+ local wait = false
+ local errors = nil
+ if opt.block then
+ local size = last + 1 - start
+ while start <= last do
+ real, errors = io.socket_send(self._SOCK, data, start, last)
+ if real > 0 then
+ send = send + real
+ start = start + real
+ wait = false
+ elseif real == 0 and not wait then
+ local events, waiterrs = self:wait(socket.EV_SEND, opt.timeout or -1)
+ if events == socket.EV_SEND then
+ wait = true
+ else
+ errors = waiterrs
+ break
+ end
+ else
+ break
+ end
+ end
+ if send ~= size then
+ send = -1
+ end
+ else
+ send, errors = io.socket_send(self._SOCK, data, start, last)
+ if send < 0 and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ end
+ return send, errors
+end
+
+-- send file to socket
+function _instance:sendfile(file, opt)
+
+ -- ensure the socket opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- ensure the file opened
+ local ok, errors = file:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- init start and last
+ opt = opt or {}
+ local start = opt.start or 1
+ local last = opt.last or file:size()
+
+ -- check start and last
+ if start > last or start < 1 then
+ return -1, string.format("%s: invalid start(%d) and last(%d)!", self, start, last)
+ end
+
+ -- send it
+ local send = 0
+ local real = 0
+ local wait = false
+ local errors = nil
+ if opt.block then
+ local size = last + 1 - start
+ while start <= last do
+ real, errors = io.socket_sendfile(self._SOCK, file._FILE, start, last)
+ if real > 0 then
+ send = send + real
+ start = start + real
+ wait = false
+ elseif real == 0 and not wait then
+ local events, waiterrs = self:wait(socket.EV_SEND, opt.timeout or -1)
+ if events == socket.EV_SEND then
+ wait = true
+ else
+ errors = waiterrs
+ break
+ end
+ else
+ break
+ end
+ end
+ if send ~= size then
+ send = -1
+ end
+ else
+ send, errors = io.socket_sendfile(self._SOCK, file._FILE, start, last)
+ if send < 0 and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ end
+ return send, errors
+end
+
+-- recv data from socket
+function _instance:recv(size, opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- check size
+ if size == 0 then
+ return 0
+ elseif size == nil or size < 0 then
+ return -1, string.format("%s: invalid size(%d)!", self, size)
+ end
+
+ -- recv it
+ opt = opt or {}
+ local recv = 0
+ local real = 0
+ local wait = false
+ local data_or_errors = nil
+ if opt.block then
+ local results = {}
+ while recv < size do
+ real, data_or_errors = io.socket_recv(self._SOCK, size - recv)
+ if real > 0 then
+ recv = recv + real
+ wait = false
+ table.insert(results, bytes(data_or_errors))
+ elseif real == 0 and not wait then
+ local events, waiterrs = self:wait(socket.EV_RECV, opt.timeout or -1)
+ if events == socket.EV_RECV then
+ wait = true
+ else
+ data_or_errors = waiterrs
+ break
+ end
+ else
+ break
+ end
+ end
+ if recv == size then
+ data_or_errors = bytes(results)
+ else
+ recv = -1
+ end
+ else
+ recv, data_or_errors = io.socket_recv(self._SOCK, size)
+ if recv > 0 then
+ data_or_errors = bytes(data_or_errors)
+ end
+ end
+ if recv < 0 and data_or_errors then
+ data_or_errors = string.format("%s: %s", self, data_or_errors)
+ end
+ return recv, data_or_errors
+end
+
+-- send udp data to peer
+function _instance:sendto(data, addr, port, opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- only for udp
+ if self:type() ~= socket.UDP then
+ return -1, string.format("%s: sendto() only for udp socket!", self)
+ end
+
+ -- check address
+ if not addr or not port then
+ return -1, string.format("%s: sendto empty address!", self)
+ end
+
+ -- data is bytes? unpack the raw address
+ if type(data) == "table" and data.caddr then
+ data = {data = data:caddr(), size = data:size()}
+ end
+
+ -- send it
+ opt = opt or {}
+ local send = 0
+ local wait = false
+ local errors = nil
+ if opt.block then
+ while true do
+ send, errors = io.socket_sendto(self._SOCK, data, addr, port, self:family())
+ if send == 0 and not wait then
+ local events, waiterrs = self:wait(socket.EV_SEND, opt.timeout or -1)
+ if events == socket.EV_SEND then
+ wait = true
+ else
+ errors = waiterrs
+ break
+ end
+ else
+ break
+ end
+ end
+ else
+ send, errors = io.socket_sendto(self._SOCK, data, addr, port, self:family())
+ if send < 0 and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ end
+ return send, errors
+end
+
+-- recv udp data from peer
+function _instance:recvfrom(size, opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- only for udp
+ if self:type() ~= socket.UDP then
+ return -1, string.format("%s: sendto() only for udp socket!", self)
+ end
+
+ -- check size
+ if size == 0 then
+ return 0
+ elseif size == nil or size < 0 then
+ return -1, string.format("%s: invalid size(%d)!", self, size)
+ end
+
+ -- recv it
+ opt = opt or {}
+ local recv = 0
+ local wait = false
+ local data_or_errors = nil
+ if opt.block then
+ while true do
+ recv, data_or_errors, addr, port = io.socket_recvfrom(self._SOCK, size)
+ if recv > 0 then
+ data_or_errors = bytes(data_or_errors)
+ break
+ elseif recv == 0 and not wait then
+ local events, waiterrs = self:wait(socket.EV_RECV, opt.timeout or -1)
+ if events == socket.EV_RECV then
+ wait = true
+ else
+ recv = -1
+ data_or_errors = waiterrs
+ break
+ end
+ else
+ break
+ end
+ end
+ else
+ recv, data_or_errors, addr, port = io.socket_recvfrom(self._SOCK, size)
+ if recv > 0 then
+ data_or_errors = bytes(data_or_errors)
+ end
+ end
+ if recv < 0 and data_or_errors then
+ data_or_errors = string.format("%s: %s", self, data_or_errors)
+ end
+ return recv, data_or_errors, addr, port
+end
+
+-- wait socket events
+function _instance:wait(events, timeout)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- wait it
+ local events, errors = io.socket_wait(self._SOCK, events, timeout or -1)
+ if events < 0 and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ return events, errors
+end
+
+-- close socket
+function _instance:close()
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return false, errors
+ end
+
+ -- close it
+ ok = io.socket_close(self._SOCK)
+ if ok then
+ self._SOCK = nil
+ end
+ return ok
+end
+
+-- ensure the socket is opened
+function _instance:_ensure_opened()
+ if not self._SOCK then
+ return false, string.format("%s: has been closed!", self)
+ end
+ return true
+end
+
+-- tostring(socket)
+function _instance:__tostring()
+ local rawfd = self:rawfd() or "closed"
+ local types = {"tcp", "udp", "icmp"}
+ return string.format("<socket: %s%s/%s>", types[self:type()], self:family() == socket.IPV6 and "6" or "4", rawfd)
+end
+
+-- gc(socket)
+function _instance:__gc()
+ if self._SOCK and io.socket_close(self._SOCK) then
+ self._SOCK = nil
+ end
+end
+
+-- open a socket
+--
+-- @param socktype the socket type, e.g. tcp, udp, icmp
+-- @param family the address family, e.g. ipv4, ipv6
+--
+-- @return the socket instance
+--
+function socket.open(socktype, family)
+ socktype = socktype or socket.TCP
+ family = family or socket.IPV4
+ local sock, errors = io.socket_open(socktype, family)
+ if sock then
+ return _instance.new(socktype, family, sock)
+ else
+ return nil, errors or string.format("failed to open socket(%s/%s)!", socktype, family)
+ end
+end
+
+-- open tcp socket
+function socket.tcp(opt)
+ opt = opt or {}
+ return socket.open(socket.TCP, opt.family or socket.IPV4)
+end
+
+-- open udp socket
+function socket.udp(opt)
+ opt = opt or {}
+ return socket.open(socket.UDP, opt.family or socket.IPV4)
+end
+
+-- open and bind tcp socket
+function socket.bind(addr, port, opt)
+ local sock, errors = socket.tcp(opt)
+ if not sock then
+ return nil, errors
+ end
+ local ok, errors = sock:bind(addr, port)
+ if not ok then
+ sock:close()
+ return nil, string.format("bind %s:%s failed, errors: %s!", addr, port, errors or "")
+ end
+ return sock
+end
+
+-- open and connect tcp socket
+function socket.connect(addr, port, opt)
+ local sock, errors = socket.tcp(opt)
+ if not sock then
+ return nil, errors
+ end
+ local ok, errors = sock:connect(addr, port, opt)
+ if ok <= 0 then
+ sock:close()
+ return nil, string.format("connect %s:%s failed, errors: %s!", addr, port, errors or "")
+ end
+ return sock
+end
+
+-- return module
+return socket