K here is the networking example for both UDP and TCP. Its a bit lengthy, but unfortunately networking isn't a two-line thing. Some of the comments might be 'duh' for you or others, but hopefully will help those looking to get their feet wet with networking in Bmax.
notes:
- UDP server doesn't detect disconnects from clients. this takes way more logic/code to implement compared to TCP and is beyond the scope of the example.
- UDP and TCP don't mix. so don't try to connect to a UDP server with a TCP client.
this example can:
- create either a TCP or UDP server
- host multiple clients per server
- echo integers back and forth from client to server back to client.
SuperStrict
Rem
BlitzMax TCP / UDP networking example
Authors: Martin Ahrens , Alex Okafor
webpage: <a href="http://www.enddream.com" target="_blank">http://www.enddream.com</a>
End Rem
Rem
operation:
1. select server, client, or quit
2a. if server: select port to start on
2b. if client: select server IP and port to connect to.
3. press escape to quit/disconnect from server.
End Rem
' get the host name of this computer
Extern "win32"
Function GetComputerNameA(lpbuffer:Byte Ptr,nSize:Int Ptr)
EndExtern
Local Size:Int = 32 ' maximum length of cstring
Local Buffer:Byte Ptr = MemAlloc(Size) ' allocate memory to hold cstring
GetComputerNameA(buffer,Varptr(size)) ' ask for name
Local name$ = String.FromCString(buffer) ' convert cstring to string
MemFree buffer ' release cstring
Print "Connected on " + name$ + " (" + DottedIP(HostIp(name$)) + ")"
'Print "computer name contains " + size + " characters"
'------------------------------------
Rem
this is what the server keeps a hold of when someone connects
to the server
End Rem
Type Client
Field intIP:Int ' integer IP
Field port:Int
Field socket:TSocket
Field stream:TStream
Field tcp:Int ' is this client a TCP or UDP client. false = UDP.
Rem
create a TCP client
with the needed streams
and field data
End Rem
Function CreateTCP:Client(sock:TSocket)
Local newClient:Client = New Client
newClient.socket = sock
newClient.stream = CreateSocketStream(sock , True)
newClient.tcp = True
newClient.intIP = SocketRemoteIP(sock)
newClient.port = SocketRemotePort(sock)
If newClient.stream = Null Then
Print "cannot stream new client socket, abandoning client"
Return Null
EndIf
Return newClient
End Function
Rem
create UDP client
UDP clients don't
actual personal 'streams' like TCP
End Rem
Function createUDP:Client(intip:Int , port:Int)
Local nClient:Client = New Client
nClient.intip = intip
nClient.port = port
nClient.tcp = False
Return nClient
End Function
Rem
returns true / false if a TCP socket is still connected
used for server to detect if a client has disconnected.
End Rem
Method isConnected:Int()
Return SocketConnected(socket)
End Method
Rem
wrapper method for disconnecting a client's streams
End Rem
Method disconnect()
CloseStream(stream)
End Method
' getter methods to
' determine which
' type of clien this is
Method isTCP:Int()
Return tcp
End Method
Method isUDP:Int()
Return Not(tcp)
End Method
End Type
Local TorU:String = "" ' holds whether user chose to do a TCP or UDP network test
Local tuChoice:Int = 0 '1=T 2=U 3=Q where T = TCP; U = UDP; Q = Quit
'-----INPUT LOOP-----
' gather some input to
' setup the correct
' network connections
While tuChoice = 0
TorU:String = Lower$(Trim$(Input$("TCP (T) or UDP (U) or Quit (Q): ") ) )
If TorU = "t" Then
tuChoice = 1
ElseIf TorU = "u"
tuChoice = 2
ElseIf TorU = "q"
tuChoice = 3
Else
'tuChoice = 0
EndIf
Wend
If tuChoice = 3 Then End ' if user chose to quit then end the program.
'-------------------
'--------------------
Local SorC:String = "" ' holds if user wants to be a server or client
Local scChoice:Int = 0 '1=S 2=C 3=Q where S = Server; C = Client; Q = Quit
'-----INPUT LOOP-----
' gather more input
' to setup correct
' server or client streams
While scChoice = 0
SorC:String = Lower$(Trim$(Input$("Server (S) or Client (C) or Quit (Q): ") ) )
If SorC = "s" Then
scChoice = 1
ElseIf SorC = "c"
scChoice = 2
ElseIf SorC = "q"
scChoice = 3
Else
'scChoice = 0
EndIf
Wend
'--------------------
' run the demo app.
Select scChoice
Case 1
' SERVER
BeServer(tuChoice)
Case 2
' CLIENT
BeClient(tuChoice)
Case 3
End
End Select
'---------------------
' runs a simple echo server.
' this is both TCP and UDP
Function BeServer(tuChoice:Int)
Local sockTypeName:String = "" ' just a string to keep track of which type of server this is
If tuChoice = 1 Then
sockTypeName = "TCP"
ElseIf tuChoice = 2 Then
sockTypeName = "UDP"
EndIf
Local localPort:Int = -1
' ask user which port they want to start the server on.
While ( (localPort < 0) Or (localPort > 65535) )
localPort:Int = Int(Trim$(Input$("Port (1-65535, 0 to quit) : ") ) )
Wend
If localPort = 0 Then End
'-------------------
' create the sockets
Local servSocket:TSocket = Null
If tuChoice = 1 Then 'tcp
servSocket:TSocket = CreateTCPSocket:TSocket()
Else 'udp
servSocket:TSocket = CreateUDPSocket:TSocket()
EndIf
If servSocket = Null Then
Print "Cannot create " + sockTypeName + " server socket... "
End
Else
Print "Created server " + sockTypeName + " socket"
EndIf
' user must bind the socket to a port once it is created successfully
Local servSocketBound:Int = BindSocket(servSocket , localPort)
If Not(servSocketBound) Then
Print "Cannot bind " + sockTypeName + " server socket to local port... " + localPort
CloseSocket(servSocket)
Print sockTypeName + " socket closed"
End
Else
Print sockTypeName + " server socket bound To Local port " + localPort
EndIf
Rem
wrap the socket around the stream
End Rem
Local servSocketStream:TSocketStream = CreateSocketStream:TSocketStream(servSocket , True)
If servSocketStream = Null Then
CloseSocket(servSocket)
Print "Cannot stream " + sockTypeName + " server socket"
End
Else
Print "Streaming " + sockTypeName + " server socket"
EndIf
Rem
for TCP you must listen on the socket
for UDP this is not required.
End Rem
If tuChoice = 1 Then
If Not(SocketListen(servSocket)) Then
Print "Cannot Listen on " + sockTypeName + " server socket"
Else
Print "Listening on " + sockTypeName + " server socket"
EndIf
Else
Print "In UDP, skipping listen"
EndIf
'----------INIT------------
Local clients:TList = CreateList() ' a list to hold all client connections.
'--------MAIN LOOP---------
Print "Server GUI starting..."
Graphics 800 , 600 , 0 , 60
Local ft:TTimer = CreateTimer:TTimer(100) '10 hertz timer to trigger events
Repeat
WaitEvent()
Rem
listen for new connections
either in TCP or UDP.
notice UDP requires a bit
more setting up and checking to keep track of new clients.
End Rem
If tuChoice = 1 Then 'tcp
Local newClientSocket:TSocket = SocketAccept(servSocket)
If newClientSocket <> Null Then
Print "Heard a connect from " + DottedIP(SocketRemoteIP(newClientSocket))
ListAddLast(clients, Client.CreateTCP(newClientSocket))
EndIf
Else 'udp
Rem
in UDP the bytes array is nothing more than a byte ptr for an int.
since all we do is echo integers back and forth. The
recvFrom_() function just needs a byte ptr to be able to store the
information it recieves somewhere.
we instantly send back the integer through sendto_()
we check to see if the we've never seen this client before
by checking this client's ip and port against our list. if its
new, we add it to the list.
End Rem
Local sra:Int = SocketReadAvail(servSocket)
If sra > 0 Then
Local bytes:Int[1] ' this variable will recieve the incoming 'raw' data.
Local rip:Int = 0 ' remote ip
Local rport:Int = 0 ' remote port
'http://www.blitzmax.com/Community/posts.php?topic=57586#640994
recvfrom_(servSocket._socket , bytes , 4 , 0 , rip , rport)
sendto_(servSocket._socket , bytes , 4 , 0 , rip , rport)
Print CurrentTime() + " (" + DottedIP(rip) + ":" + rport + ") got: " + bytes[0] + ", replied"
If Not(clientExists(rip , rport , clients) ) Then
clients.addLast(Client.createUDP(rip , rport) )
End If
EndIf
EndIf
Rem
this checks to see if
any of the TCP clients have sent
anything to us.
Also if the TCP client has disconnected
we handle it here.
End Rem
For Local eClient:Client = EachIn clients
If eClient.isTCP() Then
If eClient.isConnected() Then
Local sra:Int = 1
While sra > 0
sra:Int = SocketReadAvail(eClient.socket)
If sra >= 4 Then 'can read an int (4 bytes)
' read from their stream
Local integer:Int = ReadInt(eClient.stream)
WriteInt(eClient.stream , integer) ' send it back
Print CurrentTime() + " (" + DottedIP(eClient.intIP) + ":" + eClient.port + ") got: " + integer + ", replied"
EndIf
Wend
Else
Print "Removed a client (DC)"
clients.remove(eClient)
eClient.disconnect()
End If
ElseIf eClient.isUDP() Then
End If
Next
' just draw number of clients connected.
Cls
DrawText("Clients: " + CountList(clients), 0,0)
Flip(False)
Until KeyHit(Key_Escape)
'--------------------------
' exiting the function
' close the server socket.
CloseSocket(servSocket)
Print sockTypeName + " socket closed"
End Function
'---------------------
Rem
tuChoice is tcp or udp. 1 = tcp
this handles being a client
either TCP or UDP
End Rem
Function BeClient(tuChoice:Int)
' gather more input to connect to a server
Local servIP:String = Trim(Input("Server IP: ") )
If servIP = "" Then End
Local servPort:Int = Int(Trim(Input("Server Port 1-65535, 0 to quit: " ) ) )
If servPort <= 0 Or servPort > 65535 Then End
Local cliSocket:TSocket = Null
Local socketTypeName:String
' create sockets
If tuChoice = 1 Then 'tcp
cliSocket = CreateTCPSocket:TSocket()
socketTypeName = "TCP"
Else 'udp
cliSocket = CreateUDPSocket:TSocket()
socketTypeName = "UDP"
End If
If cliSocket= Null Then
Print "Could not create " + socketTypeName + " client socket "
End
Else
Print "Created " + socketTypeName + " client socket"
End If
Local intIP:Int = getIntIP(servIP) ' get the integer IP of the server
Local connected:Int = ConnectSocket(cliSocket, intIP , servPort) ' connect to the server
If Not(connected) Then
Print "Connection failed on " + socketTypeName + " socket to server"
CloseSocket(cliSocket)
End
Else
Print "Connected to server on " + socketTypeName + " socket"
End If
' create a stream for easier reading/writing
Local netStream:TSocketStream = CreateSocketStream(cliSocket, True)
If netStream = Null Then
Print "Cannot create stream of type " + socketTypeName + " on socket"
CloseSocket(cliSocket)
End
Else
Print "Streaming " + socketTypeName + " client socket"
End If
'get the in/out streams from the netstream
Local netOut:TStream = WriteStream:TStream(netStream)
Local netIn:TStream = ReadStream:TStream(netStream)
Print "Client GUI starting..."
Graphics 100 , 100 , 0 , 60
Local ft:TTimer = CreateTimer:TTimer(100) '10 hertz timer to trigger events
Local currentInt:Int = 0
Local currentIntSent:Int = MilliSecs()
Repeat
WaitEvent()
Rem
everytime user clicks on graphics window
send a random int.
for automation purposes send a random int
at random intervals.
End Rem
If MouseHit(1) Or Rand(60)=1 Then
currentInt:Int = Rand(1000)
currentIntSent:Int = MilliSecs()
WriteInt(netOut , currentInt)
Print "Send int " + currentInt
EndIf
Local sra:Int = SocketReadAvail(cliSocket)
If sra >= 4 Then ' recieved an int into the stream [ints = 4 bytes]
Local integer:Int = ReadInt(netIn)
Print "Recv: " + integer
If integer = currentInt Then
Local timeTaken:Int = MilliSecs() - currentIntSent
Print "PING: " + timeTaken
EndIf
EndIf
'bleh :P
Cls
DrawText("Something",0,0)
Flip(False)
Until KeyHit(key_ESCAPE)
CloseSocket(cliSocket)
End Function
'---utility functions--
Rem
takes a normal dotted IP string
and returns the integer value of that IP
since IP's are used in integer format
when communicating , this is needed
for initial connections and sending
of data!
End Rem
Function getIntIP:Int(ip:String)
Local firstDot:Int = Instr(ip,".",1)
Local secDot:Int = Instr(ip,".",firstDot+1)
Local thirdDot:Int = Instr(ip,".",secDot+1)
Local first:Int = Int(Mid(ip,1,firstDot))
Local sec:Int = Int(Mid(ip,firstDot+1,secDot-firstDot))
Local third:Int = Int(Mid(ip , secDot + 1 , thirdDot - secDot))
Local fourth:Int = Int(Mid(ip , thirdDot+1))
Print first + "." + sec + "." + third + "." + fourth
Return dottedToIntIP(first , sec , third , fourth)
End Function
Rem
a helper function for getIntIP() to translate a
dotted IP to integer format.
End Rem
Function dottedToIntIP:Int(oc1:Int , oc2:Int , oc3:Int , oc4:Int) 'take 4 octets, return bitwise concat int
Return ((oc1 Shl 24) | (oc2 Shl 16) | (oc3 Shl 8) | (oc4))
End Function
Rem
used in UDP server to see if the integer it
recieved came from a client that it already knows about or
is a new connection.
End Rem
Function clientExists:Int(ip:Int , port:Int, list:Tlist)
For Local c:Client = EachIn list
If c.intip = ip And c.port = port Then
Return True
End If
Next
Return False
End Function
I went through each of the relevant functions and commented them with what they do and any other notes I deemed might make things clearer.
Might think this is alot to just echo back integers, but this is a decent setup to get the clients/servers doing what you want. Its a crude framework, but by editing the sends on either end you *could* mold this to do other simple tasks.
Hope this helps.