recvfrom_ signature and use

BlitzMax Forums/BlitzMax Programming/recvfrom_ signature and use

Okay, so I am working on a little peer to peer code for fun. I decided to use UDP (pretty obvious), and I need to know the source of messages sent to a node. So I can't use streams, which allow me to do everything but that.

Anyway, I got some code from the forums, it looks like the recommended way to do this is to use recvfrom_. Here is my question:

All of the code samples I can find (68756, 62117, 68758, 61507) seem to use this signature for recvfrom_:

recvfrom_(socket, buffer, buffer_length , flags, senderip, senderport )


However the MSDN page for recvfrom (which I can only assume is the same function, since recvfrom_ is refered to frequently as a system call, API, etc.) has this signature:

int recvfrom(
  SOCKET s,
  char* buf,
  int len,
  int flags,
  struct sockaddr* from,
  int* fromlen
);


The remarks for from and fromlen are:

from
[out] Optional pointer to a buffer in a sockaddr structure that will hold the source address upon return.
fromlen
[in, out] Optional pointer to the size, in bytes, of the from buffer.

This is, to say the least, not what people have been writing.

What's going on?

Function recvfrom_ in stdC.bmx has arguments :
Function recvfrom_( socket,buf:Byte Ptr,size,flags,sender_ip Var,sender_port Var)

This function calls stdc.c which contains
int recvfrom_( int socket,char *buf,int size,int flags,int *_ip,int *_port){

This function then calls the API Recvfrom
count=recvfrom(socket,buf,size,flags,(void*)&sa,&sasize);

which matches the documentation you point to.

Update: Updated code to working version.


Super! Just wanted to make sure I wasn't loosing my mind. I didn't even notice the stdC module before.

So here's what I've got so far. It isn't exactly working though; strangely, even though I have two sockets open, it reads the last bit of data that it sent. I probably missed something in conversion from using streams. Yes, this is a total hack.

Start two of them up, and enter
/connect localhost ####


Replacing the #### with the port number that the other instance reported.

Then enter anything (that doesn't start with a / or ^) in either window and it is supposed to show up in the other.

peer.bmx
SuperStrict

Import "lib/strings.bmx"

Framework BRL.Map
Import BRL.Socket
Import BRL.StandardIO
Import BRL.Win32MaxGUI
Import BRL.Timer
Import BRL.Retro

Const BUF_SIZE:Int = 1024

AppTitle = "Peer"
Global EditWindow:TGadget
Global txtOutput:TGadget
Global eMsg:TGadget
Global btnSend:TGadget


Global Peers:TMap = CreateMap()
Global Sock:TSocket = CreateUDPSocket()
Global SendSock:TSocket = CreateUDPSocket()

Type TPeer
	Field Name:String
	Field IP:Int
	Field Port:Int

	Field Score:Int
	
	Function Create:TPeer(Name:String, IP:Int, Port:Int, Score:Int)
		Local Result:TPeer = New TPeer
		Result.Name = Name
		Result.IP = IP
		Result.Port = Port
		Result.Score = Score
		Return Result
	EndFunction
EndType

Function Broadcast(Msg:String)
	Local p:Object
	For p = EachIn Peers.Values()
		Send(TPeer(p), Msg)
	Next
EndFunction

Function Send(p:TPeer, Msg:String)
	Local Buf:Byte Ptr = Msg.ToCString()
	Local Sent:Int = sendto_(SendSock._socket, Buf, Msg.length, 0, p.IP, p.Port)
	MemFree Buf
	SocketListen(Sock)
EndFunction

Function gPrint(Msg:String)
	Print Msg
	AddTextAreaText(txtOutput, Msg + "~r~n")
EndFunction

Const Width:Int = 397
Const Height:Int = 335

EditWindow = CreateWindow(AppTitle, GadgetWidth(Desktop()) / 2 - Width / 2, ..
		GadgetHeight(Desktop()) / 2 - Height / 2, ..
		Width, Height, Desktop(), WINDOW_TITLEBAR|WINDOW_RESIZABLE)

txtOutput=CreateTextArea(8, 8, 376, 264, EditWindow, 0)
SetGadgetLayout txtOutput, 1, 1, 1, 1

eMsg = CreateTextField(8, 280, 320, 20, EditWindow)
SetGadgetLayout eMsg, 1, 1, 0, 1
SetGadgetText eMsg, "/connect localhost "

btnSend = CreateButton("Send", 336, 280, 48, 24, EditWindow, BUTTON_OK)
SetGadgetLayout btnSend, 0, 1, 0, 1

ActivateGadget(eMsg)


' Start listening on a new port
BindSocket(Sock, 0)
BindSocket(SendSock, SocketLocalPort(Sock) + 1)
SocketListen(Sock)

gPrint("Listening on " + SocketLocalPort(Sock))
SetGadgetText(EditWindow, AppTitle + " " + SocketLocalPort(Sock))

Local buf:Byte[BUF_SIZE]
Local receivedLen:Int
Local size:Int
Local fromIP:Int
Local fromPort:Int

Local Raw:String
Local Msg:String

Local Parms:String[]
Local Peer:TPeer
Local Peer2:TPeer
Local PeerKey:String

Local i:Int
Local Messages:String[]


' Allows us to use a single thread by interrupting WaitEvent() twice a second. This way
' we don't eat the CPU, but we can still poll the socket pretty well.
CreateTimer(8)

Repeat
	Select WaitEvent()
		Case EVENT_TIMERTICK 
		Case EVENT_GADGETACTION
			Select EventSource()
				Case btnSend
					Msg = GadgetText(eMsg)
					If Left$(Msg, 1) = "/" Then
						Parms = Split(Msg, " ")
						Select Parms[0]
							Case "/connect"
								If Parms.Length = 3 Then
									' Ask existing peers to add this peer
									Peer = TPeer.Create(Parms[1], HostIp(Parms[1]), Parms[2].ToInt(), 0)
									MapInsert(Peers, Parms[1] + ":" + Parms[2], Peer)

									' Send request to add this node to the newly added node
									Send(Peer, "^add " + HostName$(SocketLocalIP(Sock)) + " " + SocketLocalPort(Sock))
									
									' Ask for thier list of peers
									Send(Peer, "^peers")
									
									
									gPrint("Added " + Parms[1] + ":" + Parms[2] + " to peer list")
								Else
									gPrint("Usage: /connect host port")
								EndIf
							Case "/test"
								Notify "Hello world"
							Case "/quit"
								Exit	
						EndSelect
					Else
						gPrint("< " + Msg)
						Broadcast(Msg)
					EndIf
					SetGadgetText(eMsg, "")
					ActivateGadget(eMsg)
			End Select
		Case EVENT_WINDOWCLOSE
			Exit
	EndSelect
	
	size = SocketReadAvail(Sock)
	If size Then
		fromIP = 0
		fromPort = 0
		receivedLen = 0
		receivedLen = recvfrom_(sock._socket, buf, size, 0, fromIP, fromPort)
		Assert receivedLen > 0, "No data read"
		Raw = String.FromBytes(buf, receivedLen)


		fromPort = fromPort - 1
		PeerKey = HostName$(fromIP) + ":" + fromPort

		Peer = TPeer(MapValueForKey(Peers, PeerKey))
		If Instr(Raw, "^") Then
			Messages = Split(Raw, "^", "`")
			If Messages.Length = 0 Then
				Messages[0] = Raw
			EndIf

			For i = 0 To Messages.Length - 1
				Msg = Messages[i]
				If Msg <> "" Then
					Parms = Split(Msg, " ")
					If Parms.Length > 0 Then
						Select Parms[0]
							Case "peers"
								' Send a list of add commands back to the asking peer
								If Peer <> Null Then
									For Peer2 = EachIn Peers.Values()
										Send(Peer, "^add " + HostName$(Peer2.IP) + " " + Peer2.Port)
									Next
									' Ask the peers to add this new peer
									Broadcast("^add " + HostName$(Peer.IP) + " " + Peer.Port)
								Else
									gPrint "Can't send peer list to unknown peer key: " + PeerKey
								EndIf
							Case "add"
								' Don't want to add ourselves
								If HostIp(Parms[1]) <> SocketLocalIP(Sock) And Parms[2].ToInt() <> SocketLocalPort(Sock) Then
									Peer = TPeer.Create(Parms[1], HostIp(Parms[1]), Parms[2].ToInt(), 0)
									MapInsert(Peers, Parms[1] + ":" + Parms[2], Peer)
									gPrint("[Network] Added " + Parms[1] + ":" + Parms[2] + " to peer list")
								EndIf
						EndSelect
					EndIf
				EndIf
			Next
		Else
			gPrint(PeerKey + "> " + Raw)
		EndIf
		Msg = ""
	EndIf
Forever

CloseSocket(Sock)
CloseSocket(SendSock)


Can't find where I got this, it was on the forum somewhere, the function was originally called "SmartSplit" if I recall correctly:

lib/strings.bmx
Function Split:String[](str:String, dels:String, text_qual:String = "~q")
	Local Parms:String[] = New String[1]
	Local pPtr:Int = 0
	Local chPtr:Int = 0
	Local delPtr:Int = 0
	Local qt:Int = False
	Local str2:String = ""
	
	Repeat
		Local del:String = Chr(dels[delPtr])
		Local ch:String = Chr(str[chPtr])
		If ch = text_qual Then 
			If qt = False Then
				qt = True
			Else
				qt = False
			End If
		End If
		If ch = del Then
			If qt = True Then str2:+ ch
		Else
			str2:+ ch
		End If
		If ch = del Or chPtr = str.Length - 1 Then
			If qt = False Then
				Parms[pPtr] = str2.Trim()
				str2 = ""
				pPtr:+ 1
				Parms = Parms[..pPtr + 1]
				If dels.length > 1 And delPtr < dels.length Then delPtr:+ 1
			End If
		End If
		chPtr:+ 1
		If chPtr = str.Length Then Exit
	Forever
	If Parms.Length > 1 Then Parms = Parms[..Parms.Length - 1]
	Return Parms
			
End Function


Just updated the code for peer.bmx. Works now, basically. I need to think about it a bit more - each node sends UDP messages directly to each other node. For my purposes this will probably work.