#encoding: utf-8
module NET
#USER_DEFINED_CONST
MAX_BUFFER = 4096
#const
WSADESCRIPTION_LEN = 256
WSASYS_STATUS_LEN = 128
AF_INET = 2
SOCK_STREAM = 1
IPPROTO_TCP = 6
INVALID_SOCKET = ~0
SOCKET_ERROR = -1
INADDR_ANY = 0x00000000
SOMAXCONN = 0x7fffffff
FIONBIO = 2147772030
EINPROGRESS = 10036
EISCONN = 10056
EWOULDBLOCK = 10035
EINVAL = 10022
#win32api
API_WSAStartup = Win32API.new('ws2_32','WSAStartup','ip','i')
API_WSACleanup = Win32API.new('ws2_32','WSACleanup','v','i')
API_socket = Win32API.new('ws2_32','socket','iii','i')
API_htons = Win32API.new('ws2_32','htons','i','i')
API_ntohs = Win32API.new('ws2_32','ntohs','i','i')
API_htonl = Win32API.new('ws2_32','htonl','L','L')
API_inet_addr = Win32API.new('ws2_32','inet_addr','p','L')
API_inet_ntoa = Win32API.new('ws2_32','inet_ntoa','L','p')
API_ioctlsocket = Win32API.new('ws2_32','ioctlsocket','iip','i')
API_bind = Win32API.new('ws2_32','bind','ipi','i')
API_listen = Win32API.new('ws2_32','listen','ii','i')
API_accept = Win32API.new('ws2_32','accept','ipp','i')
API_connect = Win32API.new('ws2_32','connect','ipi','i')
API_recv = Win32API.new('ws2_32','recv','ipii','i')
API_send = Win32API.new('ws2_32','send','ipii','i')
API_closesocket = Win32API.new('ws2_32','closesocket','i','i')
API_WSAGetLastError = Win32API.new('ws2_32','WSAGetLastError','v','i')
def self.init
#最后两个char是为了内存对齐(byte alignment)理论上说并非必须
wsdata_types = "SSc#{WSADESCRIPTION_LEN+1}c#{WSASYS_STATUS_LEN+1}SSLcc"
wsadata = ([0]*(WSADESCRIPTION_LEN+WSASYS_STATUS_LEN+9)).pack(wsdata_types)
version = [2,2].pack("CC").unpack("S")[0]
if API_WSAStartup.call(version,wsadata) != 0
false
else
@inited = true
end
end
def self.inited?
return @inited
end
class Socket
attr :state
attr :errcode
attr :accepted_sockets
def initialize
@state = :not_ready
@errcode = 0
@accepted_sockets = []
return false unless NET.inited?
@socket = API_socket.call(AF_INET,SOCK_STREAM,IPPROTO_TCP)
if @socket == INVALID_SOCKET
return false
end
flag = [1].pack('L')
if API_ioctlsocket.call(@socket,FIONBIO,flag) == SOCKET_ERROR
return false
end
@state = :ready
end
def connected?
return @state == :connected
end
def error?
return @state == :error
end
def sending?
return @state == :sending
end
def listening?
return @state == :listening
end
def update
@recv = nil
@accepted_sockets.each{ |cs| cs.update }
case @state
when :connecting
if @connect_info.nil?
@state = :ready
else
if API_connect.call(@socket,@connect_info,@connect_info.size) == SOCKET_ERROR
ref = API_WSAGetLastError.call
if ref == EISCONN
@state = :connected
elsif ref != EWOULDBLOCK && ref != EINPROGRESS && ref != EINVAL
puts "Error occured when connecting"
@state = :error
@errcode = ref
end
p ref
else
@state = :connected
end
end
when :connected
@buf = "\0"*MAX_BUFFER if @buf.nil?
if (response_size = API_recv.call(@socket,@buf,@buf.size,0)) == SOCKET_ERROR
ref = API_WSAGetLastError.call
if(ref != EWOULDBLOCK)
@state = :error
@errcode = ref
end
else
@recv = @buf[0...response_size]
end
when :sending
if API_send.call(@socket,@message,@message.size,0) == SOCKET_ERROR
ref = API_WSAGetLastError.call
if ref != EWOULDBLOCK
@state = :error
@errcode = ref
puts "Error occured when sending"
end
else
@state = :connected
end
when :listening
@ci = '\0'*16 if @ci.nil?
len = [@ci.size].pack('i')
if (cs = API_accept.call(@socket,@ci,len)) == SOCKET_ERROR
ref = API_WSAGetLastError.call
if ref != EWOULDBLOCK
@state = :error
@errcode = ref
end
else
@accepted_sockets.push NET::ClientSocket.new({:socket => cs,
:client_info => @ci})
end
end
end
def connect(ip,port)
p ip,port.to_i
return false if @state != :ready
sockaddr_in = [AF_INET,API_htons.call(port.to_i),API_inet_addr.call(ip)]
sockaddr_in += ([0]*8)
@connect_info = sockaddr_in.pack('sSLc*8')
@state = :connecting
end
def send(message)
return false if @state != :connected && @state != :sending
@message = message
if API_send.call(@socket,@message,@message.size,0) == SOCKET_ERROR
ref = API_WSAGetLastError.call
if ref != EWOULDBLOCK
puts "Error occured when send"
@state = :error
@errcode = ref
else
@state = :sending
end
else
@state = :connected
end
end
def recv
return false if @state != :connected
return @recv
end
def listen(port)
return if @state != :ready
#server info
sockaddr_in_server = [AF_INET,API_htons.call(port),API_htonl.call(INADDR_ANY)]
sockaddr_in_server += ([0]*8);
server_info = sockaddr_in_server.pack('sSLc*8')
#bind
if API_bind.call(@socket,server_info,server_info.size) == SOCKET_ERROR
return false
end
#listen
if API_listen.call(@socket,SOMAXCONN) == SOCKET_ERROR
return false
end
@state = :listening
end
def stop_listening
@state = :listening_stoped
end
def continue_listening
@state = :listening
end
end
class ClientSocket < Socket
attr :ip
attr :port
def initialize(params)
@state = :not_ready
@accepted_sockets = []
return false unless NET.inited?
@state = :connected
@socket = params[:socket]
@connect_info = params[:client_info]
info = @connect_info.unpack('sSLc*8')
@ip = API_inet_ntoa.call(info[2])
@port = API_ntohs.call(info[1])
end
end
end