Socket

From Pragma
Jump to: navigation, search

Name: wv_socket

State:

SHARED

Description

By loading this module, the following classes are made available:

As well as the following functions in the net library:

Examples

HTTP GET Request

This script allows you to dispatch a simple HTTP GET request to a webserver for a url and retrieve the output code:

local r = engine.load_library("socket/wv_socket")
if(r ~= true) then
	print("WARNING: An error occured trying to load the 'wv_socket' module: ",r)
	return
end

function http_get(host,target,callback,timeout)
	timeout = timeout or 0
	local function read_content(socket,callback,content)
		content = content or ""
		socket:ReceiveAtLeast(1,function(err,packet) -- Read the next batch; 'err' will be SOCKET_ERROR_EOF when all data has been read
			if(err:IsError() == true) then
				socket:Close()
				if(err:GetValue() ~= net.SOCKET_ERROR_EOF) then
					callback(err)
					return
				end
				callback(err,content)
				return
			end
			local n = packet:ReadString()
			content = content .. n
			read_content(socket,callback,content) -- Keep reading
		end)
	end
	net.resolve(net.SOCKET_PROTOCOL_TCP,host,"http",function(err,ep) -- Resolve the address into an endpoint (ip +port)
		if(err:IsError() == true) then
			callback(err)
			return
		end
		local socket = net.create_socket(net.SOCKET_PROTOCOL_TCP)
		socket:SetTimeoutDuration(timeout)
		socket:Connect(ep,function(err) -- Connect to the webserver
			if(err:IsError() == true) then
				socket:Close()
				callback(err)
				return
			end
			local request = net.Packet()
			request:WriteString( -- Header for a GET-request
				"GET " .. target .. " HTTP/1.0\r\n" ..
				"Host: " .. host .. "\r\n" ..
				"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\r\n" ..
				"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" ..
				"Accept-Language: en-us,en;q=0.5\r\n" ..
				"Accept-Encoding: gzip,deflate\r\n" ..
				"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" ..
				"Keep-Alive: 300\r\n" ..
				"Connection: keep-alive\r\n" ..
				"Pragma: no-cache\r\n" ..
				"Cache-Control: no-cache\r\n" ..
				"\r\n"
			)
			socket:Send(request,function(err) -- Send the header
				if(err:IsError() == true) then
					socket:Close()
					callback(err)
					return
				end
				socket:ReceiveUntil("\r\n",function(err,packet) -- Receive the first line of the response, which contains status information
					if(err:IsError() == true) then
						socket:Close()
						callback(err)
						return
					end
					local str = packet:ReadString()
					local data = str:split(" ")
					if(#data >= 3) then
						local httpVersion = data[1] or ""
						local statusCode = tonumber(data[2] or "")
						local statusMessage = data[3] or ""
						if(httpVersion:sub(0,5) ~= "HTTP/") then
							socket:Close()
							callback(ErrorCode("Invalid response",-1))
						elseif(statusCode ~= 200) then
							socket:Close()
							callback(ErrorCode("Invalid status code " .. statusCode,-1))
						else
							socket:ReceiveUntil("\r\n\r\n",function(err,packet) -- Read the rest of the response header
								if(err:IsError() == true) then
									socket:Close()
									callback(err)
									return
								end
								--[[local headers = packet:ReadString()
								headers = headers:split("\r\n")
								local contentLength = -1
								for _,h in ipairs(headers) do
									if(h:sub(0,16) == "Content-Length: ") then
										contentLength = tonumber(h:sub(17))
										break
									end
								end]]
								read_content(socket,callback) -- Start reading the website content
							end)
						end
					else
						socket:Close()
						callback(ErrorCode("Invalid response",-1))
					end
				end)
			end)
		end)
	end)
end

Usage example:

http_get("htmlandcssbook.com","/about/",function(err,content)
	console.print_messageln("Result: ",err)
	if(err:IsError() == false or err:GetValue() == net.SOCKET_ERROR_EOF) then
		file.create_directory("data")
		local f = file.open("data/out.html","w")
		if(f ~= nil) then
			f:WriteString(content) -- Write the html code to our file
			f:Close()
		end
	end
end)

Cross-Server Chat

This is a TCP connection example, which can be used to transmit text messages between two servers.
The script consists of a server- and a clientside part.

Serverside:

local r = engine.load_library("socket/wv_socket")
if(r ~= true) then
	print("WARNING: An error occured trying to load the 'wv_socket' module: ",r)
	return
end

local protocol = SOCKET_PROTOCOL_V4
local port = 55085

local CHAT_HEADER_SIZE = SIZEOF_INT

class 'ChatServer' (ChatServer)
function ChatServer:__init()
	self.m_clients = {}
end

function ChatServer:AcceptNextClient()
	self.m_acceptor:Accept(function(err,socket)
		if(err:IsError() == true) then
			acceptor:Close()
			self:OnError(err)
			return
		end
		local cl = ChatServerClient(self,socket)
		table.insert(self.m_clients,cl)
		self:OnClientConnected(cl)
		cl:Start()
		self:AcceptNextClient()
	end)
end

function ChatServer:Broadcast(msg)
	for _,cl in ipairs(self.m_clients) do
		cl:SendMessage(msg)
	end
end

function ChatServer:KickClients()
	for _,cl in ipairs(self.m_clients) do
		cl:Close()
	end
	self.m_clients = {}
end

function ChatServer:OnClientConnected(cl)
	msgn("[ChatServer] New Client '" .. cl:GetAddress() .. "' has connected!")
end

function ChatServer:OnError(err,cl)
	if(cl == nil) then
		msgn("[ChatServer] Server Error: " .. err:GetMessage())
		return
	end
	msgn("[ChatServer] Client Error: " .. err:GetMessage())
	for _,clOther in ipairs(self.m_clients) do
		if(cl:GetSocket() == clOther:GetSocket()) then
			table.remove(self.m_clients,_)
			break
		end
	end
end

function ChatServer:OnMessageReceived(cl,msg)
	msgn("[ChatServer] Received message from client '" .. cl:GetAddress() .. "': " .. msg)
	for _,clOther in ipairs(self.m_clients) do
		if(clOther:GetSocket() ~= cl:GetSocket()) then
			clOther:SendMessage(msg)
		end
	end
end

function ChatServer:Start()
	self.m_endPoint = net.create_endpoint(SOCKET_PROTOCOL_TCP,protocol,port)
	self.m_acceptor = net.create_tcp_acceptor(self.m_endPoint)
	self:AcceptNextClient()
end

-------------------------------

class 'ChatServerClient' (ChatServerClient)
function ChatServerClient:__init(sv,socket)
	self.m_server = sv
	self.m_socket = socket
end

function ChatServerClient:GetSocket() return self.m_socket end

function ChatServerClient:GetAddress()
	local ep = self.m_socket:GetRemoteEndpoint()
	return ep:GetAddress() .. ":" .. ep:GetPort()
end

function ChatServerClient:OnError(err)
	self.m_socket:Close()
	self.m_server:OnError(err,self)
end

function ChatServerClient:ReceiveHeader()
	-- Wait for header
	self.m_socket:Receive(CHAT_HEADER_SIZE,function(err,packet)
		if(err:IsError() == false) then
			local szMsg = packet:ReadUInt() -- Message Length
			self:ReceiveBody(szMsg)
			return
		end
		self:OnError(err)
	end)
end

function ChatServerClient:ReceiveBody(sz)
	-- Wait for body
	self.m_socket:Receive(sz,function(err,packet)
		if(err:IsError() == false) then
			local msg = packet:ReadString()
			self.m_server:OnMessageReceived(self,msg)
			self:ReceiveHeader()
			return
		end
		self:OnError(err)
	end)
end

function ChatServerClient:SendMessage(msg)
	local body = NetPacket()
	body:WriteString(msg)

	local header = NetPacket()
	header:WriteUInt(body:GetSize())
	self.m_socket:Send(header,function(err,bytesTransferred)
		if(err:IsError() == false) then
			self.m_socket:Send(body,function(err,bytesTransferred)
				if(err:IsError() == false) then
					-- Message sent successfully
					return
				end
				self:OnError(err)
			end)
			return
		end
		self:OnError(err)
	end)
end

function ChatServerClient:Start()
	self:ReceiveHeader()
end

function ChatServerClient:Close()
	self.m_socket:Close()
end

local chat_server = nil
cvar.create_concommand("chat_server_start",function(pl)
	chat_server = ChatServer()
	chat_server:Start()
end)

cvar.create_concommand("chat_server_broadcast",function(pl,msg)
	if(msg == nil or chat_server == nil) then return end
	chat_server:Broadcast(msg)
end)

cvar.create_concommand("chat_server_kick_clients",function(pl)
	if(chat_server == nil) then return end
	chat_server:KickClients()
end)


Clientside:

local r = engine.load_library("socket/wv_socket")
if(r ~= true) then
	print("WARNING: An error occured trying to load the 'wv_socket' module: ",r)
	return
end

local protocol = SOCKET_PROTOCOL_V4
local serverIp = "127.0.0.1"
local serverPort = 55085

local CHAT_HEADER_SIZE = SIZEOF_INT

class 'ChatClient' (ChatClient)
function ChatClient:__init()
end

function ChatClient:OnError(err)
	self.m_socket:Close()
	msgn("[ChatClient] Error: " .. err:GetMessage())
end

function ChatClient:Connect(ip,port)
	self.m_socket = net.create_socket(SOCKET_PROTOCOL_TCP)
	self.m_socket:Connect(ip,port,function(err)
		if(err:IsError() == false) then
			self:ReceiveHeader()
			return
		end
		self:OnError(err)
	end)
end

function ChatClient:ReceiveHeader()
	-- Wait for header
	self.m_socket:Receive(CHAT_HEADER_SIZE,function(err,packet)
		if(err:IsError() == false) then
			local szMsg = packet:ReadUInt() -- Message Length
			self:ReceiveBody(szMsg)
			return
		end
		self:OnError(err)
	end)
end

function ChatClient:ReceiveBody(sz)
	-- Wait for body
	self.m_socket:Receive(sz,function(err,packet)
		if(err:IsError() == false) then
			local msg = packet:ReadString()
			self:OnMessageReceived(msg)
			self:ReceiveHeader()
			return
		end
		self:OnError(err)
	end)
end

function ChatClient:OnMessageReceived(msg)
	msgn("[ChatServer] Received message: " .. msg)
end

function ChatClient:SendMessage(msg)
	local body = NetPacket()
	body:WriteString(msg)

	local header = NetPacket()
	header:WriteUInt(body:GetSize())
	self.m_socket:Send(header,function(err,bytesTransferred)
		if(err:IsError() == false) then
			self.m_socket:Send(body,function(err,bytesTransferred)
				if(err:IsError() == false) then
					-- Message sent successfully
					return
				end
				self:OnError(err)
			end)
			return
		end
		self:OnError(err)
	end)
end

function ChatClient:Close()
	self.m_socket:Close()
end

local chat_client = nil
cvar.create_concommand("chat_client_connect",function(pl,ip,port)
	if(ip == nil) then
		ip = serverIp
		port = serverPort
	end
	if(ip == nil or port == nil) then return end
	chat_client = ChatClient()
	chat_client:Connect(ip,port)
end)

cvar.create_concommand("chat_client_send",function(pl,msg)
	if(msg == nil or chat_client == nil) then return end
	chat_client:SendMessage(msg)
end)

cvar.create_concommand("chat_client_disconnect",function(pl)
	if(chat_client == nil) then return end
	chat_client:Close()
	chat_client = nil
end)


Usage:
Server #1 can use the 'chat_server_start' command to start listening for messages. Server #2 can then use 'chat_client_connect' to connect to server #1, and transmit messages using 'chat_client_send <msg>'.

See Also