May 05, 2024, 03:10:04 PM

News:

IonicWind Snippit Manager 2.xx Released!  Install it on a memory stick and take it with you!  With or without IWBasic!


UDP routines for CB

Started by Egil, February 11, 2011, 06:56:40 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Egil

February 11, 2011, 06:56:40 AM Last Edit: February 11, 2011, 07:10:33 AM by Egil
For a long time I have wanted to make UDP routines in CB. But since I never really needed it till now, nothing was ever done. Last weekend I tried to make a working program using the mesock32.dll. But my trial program was not very stable, it crashed every now and then.
This week I have been to a conference. But since the conference started every d aay at 1000, and I am an early bird, I have used the morning hours to think about CB UDP routines.

Converting code between IWB and CB is fairly easy. There are just three things you have to remember:

1 - Define all variables this way:  DEF myvar:INT  (or whatever type of variable you need)
2 - CB subroutines use RETURN only (ENDSUB is never used)
3 - CB can't use threads

With this in mind, I decided to have a close look at the IWB UDP example found here: http://ebasic-aurora.com/forums/index.php?topic=3293.0

Valentins code doesn't use threads, so I deleted all ENDSUBs and checked that all variable definitions were done according to point 1 above.

When trying to run the program, it stopped with a type declaration error in the receive module. A close inspection showed that the program does not use parameter number five in the rcvfrom call. The parameter is declared as a pointer, while in the code a 0 (zero) is inserted since the parameter is not used. I fixed the problem by declaring the address parameter as an integer and making an aliased API call. Maybe not the cleanest way to do it, but since the parameter is not used by the program, it works just fine. You can see the changes about half way down the API declarations.
After these small adjustments, the code now runs just fine in CB. The new code is inserted below.


Have fun!

Egil


PS.

Almost forgot to say that you have to disregard Valentins description on making a lib file before using. Here we call the API routines directly.



'UDP "PC-to-PC" Network program for IBPro, written by Valentin Angelovski 15/1/2005
'Version 2.0  ***  Converted to IbasicPro on 16th Feb 2005 ***
'Version 3.0  ***  corrected for an over-zealous error-checking routine ***
'I have tested this code and have found the winsock functions to work,
'feel free to experiment with this code as you see fit, so long as...
'You accept this program 'as-is' ie: use it at your own risk!
'
'   The following SIX subroutines and their functions are desbribed below:
'
' startup()   - Initialises ie: setup and configures the winsock2.DLL
'  create()    - Creates the socket for both TX and RX
'  bindit()    - setup our created socket to make it a listening port
'  sendit()    - Sends a packet of data via the TX socket
'  recvit()    - Receives a packet of data (if available) from the RX port
' closedown() - shuts down the winsock2.DLL and free-up program memory spaces
'
' PRE-RUNNING HINTS:
' Before you can run this program in IBPro, you will have to create an import library
' for the ws2_32.dll
' This can be found in the IDE menu under 'Tools', select 'create import library',
' find the ws2_32.dll file on your system, select it, and click on the open button -
' simple! :)

' INSTRUCTIONS:
' When you run this program, a dialog window will appear. This window asks you to
' enter the IP address and/or Port number of the remote computer to communicate with.
' By default, the parameters are set for the loopback address 127.0.0.1 and port
' 20000 respectively - so the demo can run without the need for a second computer.
' If you install this program on two machines, be sure to have the IP address of the
' remote computer entered in the Configuration dialog window.
' Leave the socket number as is, unless another application is using it
' (possible, but unlikely).
' Once this is done, a standard window will open up, starting the network demo.
' You exit the program by merely closing the demo window.
' If the program encounters a network error, the program exits immediately and
' an error box will appear.
'
'
' If you have any questions/suggestions/whatever, please feel free to comment.
'
'
' Enjoy!  
'
' Valentin
'
'
'
' Valentins code has now been converted to CB-code by Egil Ingebrigtsen


'
DEF w1:WINDOW
DEF d1:DIALOG
DEF setaddress:STRING
DEF setport:INT
DEF result:INT

'********* Declare the underlying winsock2 DLL functions here ***********
DECLARE "ws2_32.dll",ioctlsocket(s:uint,cmd:int,argp:pointer),int
DECLARE "ws2_32.dll",WSAGetLastError(),int
DECLARE "ws2_32.dll",WSAStartup(wVR :int, lpWSAD:MEMORY),int
DECLARE "ws2_32.dll",WSACleanup(),int
DECLARE "ws2_32.dll",socket(af:int,s_type:int,protocol:int),int
DECLARE "ws2_32.dll",bind(s:uint, addr:pointer, namelen:INT),INT
DECLARE "ws2_32.dll",htons(hostshort:int),int
DECLARE "ws2_32.dll",htonl(hostlong:int),INT
DECLARE "ws2_32.dll",inet_addr(cp:String),int
DECLARE "ws2_32.dll",connect(s:uint, addr:INT, namelen:INT),int
DECLARE "ws2_32.dll",listen(s:uint, backlog:INT),INT
DECLARE "ws2_32.dll",send(s:uint,buf:string,buflen:int,flags:int),int
DECLARE "ws2_32.dll",recv(s:uint,buf:string,buflen:int,flags:int),int
DECLARE "ws2_32.dll",sendto(s:uint,buf:string,buflen:int,flags:int,addr:pointer, namelen:int),int

'original recvfrom declaration:
'DECLARE "ws2_32.dll",recvfrom(s:uint,buf:string,buflen:int,flags:int,addr:pointer, namelen2:int),int

'modified recvfrom declaration:
DECLARE "ws2_32.dll",MY_rcvfrom Alias recvfrom(s:uint,buf:string,buflen:int,flags:int,addr:int, namelen2:int),int

DECLARE "ws2_32.dll",accept(s:uint, addr:int, addrlen:INT),INT
DECLARE "ws2_32.dll",shutdown(s:uint, how:INT),INT
DECLARE "ws2_32.dll",closesocket(s:uint),INT
DECLARE "ws2_32.dll",gethostname(hostname:string, namelen :int),int
DECLARE "ws2_32.dll",gethostbyname(hostname:string),int
DECLARE "ws2_32.dll",inet_ntoa(inn :int),int


'********* Define Winsock2 data structures here ***********

TYPE WSADataType
  DEF wVersion:WORD  
  DEF wHighVersion:WORD  
  DEF strDescription[257]:ISTRING  
  DEF strSystemStatus[129]:ISTRING  
  DEF iMaxSockets:INT  
  DEF iMaxUdpDg:INT  
  DEF lpVendorInfo:POINTER  
ENDTYPE

  def StartupData:WSADataType


TYPE sockaddr_local
  DEF   sin_family : word
  DEF   sin_port : word
  DEF   sin_addr : int
  DEF   sin_zero[8] : ISTRING
ENDTYPE

  def StructServer:sockaddr_local


Type HostEnt
   DEF h_name :POINTER
   DEF h_aliases :POINTER
   DEF h_addrtype:WORD
   DEF h_length:WORD
   DEF addrlist :POINTER
EndType

  DEF StructHostEnt:HostEnt


TYPE sockaddr_remote
  DEF   sin_family:word
  DEF   sin_port:word
  DEF   sin_addr:int
  DEF   sin_zero[8]:ISTRING
ENDTYPE

  DEF StructClient:sockaddr_remote


'********* Declare system variables and constants here ***********

  DEF tempsd,mHost:MEMORY
  ALLOCMEM tempsd,1,len(StartupData)
 
  setid "NOBLOCK", 1
  setid "FIONBIO" , 0x8004667E
  setid "WSVER2",         0x202
  setid "NO_ERROR",      0
  setid "AF_INET",      2
  setid "SOCK_STREAM",   1
  setid "SOCK_DGRAM",    2
  setid "IPPROTO_UDP",   17
  setid "IPPROTO_TCP",   6
  setid "INVALID_SOCKET",   1
  setid "SOCKET_ERROR",   1
  setid "INADDR_ANY",      0
  setid "MaxConnections",   1
  def tino:int
  def xpos,ypos,zpos:float
  DEF iResult,addrlen,s_socket,c_socket,AcceptSocket,ListenOut,noblock,Ready,WinSock,LenStructClient,host,addr,perror,vv:int
  DEF bufor:string
  DEF WelcomeMessage,localname,MyIP,flightdata:string
  def AddrPointer:pointer
  def noblocking:pointer
     
  AddrPointer = addr

DECLARE startup() :' Initialises ie: setup and configures the winsock2.DLL
DECLARE create() :' Creates the socket for both TX and RX
DECLARE bindit() :' Setup our created socket to make it a listening port
DECLARE sendit() :' Sends a packet of data via the TX socket
DECLARE recvit() :' Receives a packet of data (if available) from the RX port
DECLARE closedown() :' Cleaning up


DIALOG d1,0,0,223,199,0x80C80080,0,"Caption",dialoghandler
CONTROL d1,"E,127.0.0.1,51,33,106,26,0x50800000,1"
CONTROL d1,"E,20000,51,88,107,27,0x50800000,2"
CONTROL d1,"B,CONNECT,60,150,92,22,0x50000000,3"
CONTROL d1,"T,I.P. Address,67,9,74,20,0x5000010B,4"
CONTROL d1,"T,Port Number,65,66,81,20,0x5000010B,5"

result = DOMODAL d1


'open the window
WAITUNTIL d1 = 0

'
window w1,0,0,800,300,@SIZE,0,"PC-2-PC Version 3.0 by Valentin Angelovski, modified by Egil Ingebrigtsen",mainwindow

'start system tmer and run main program loop until main window is closed
starttimer w1,1000
WAITUNTIL w1 = 0

'IF perror<>0

'Put simple messagebox indicating any network errors (if any)
'IF MESSAGEBOX(w1,"Network Error","error",@MB_OK) = @IDRETRY
'ENDIF
'ENDIF

'Call network shutdown routine
closedown()
'All done!
END


'********* the message handler subroutine for the network config window **********

sub dialoghandler
select @CLASS


case @IDCONTROL
select @CONTROLID
case 3
               setaddress = GETCONTROLTEXT(d1, 1)
               setport = VAL(GETCONTROLTEXT(d1, 2))
closedialog d1,@IDOK
d1=0
endselect
case @IDINITDIALOG
CENTERWINDOW d1
endselect
return
'



'********** Main Program Loop begins here ***********

sub mainwindow
   IF @CLASS = @IDCLOSEWINDOW
       CLOSEWINDOW w1
   ENDIF
   IF @CLASS = @IDTIMER
vv=vv+1
if vv = 1
startup()
endif
if vv = 2
create()
endif
if vv = 3
bindit()
endif
if vv > 4
sendit()
endif
if vv > 5
recvit()
endif      
   ENDIF
RETURN
'

'********* Initialise Winsock2 DLL here **********

sub startup()
  print w1,"* Start Winsock2"
  iResult=WSAStartup( @WSVER2 , tempsd)
  if iResult<>@NO_ERROR
  print w1,"WSAStartup ERROR"
  CLOSEWINDOW w1
  endif
return
'

'********** Create TX and RX sockets here ************

sub create()
'CREATE TRANSMIT SOCKET
   print w1,"* Make Tx socket"
  c_socket=socket(@AF_INET, @SOCK_DGRAM,@IPPROTO_UDP )
  if c_socket=@INVALID_SOCKET
    print w1,"INVALID SOCKET"
    CLOSEWINDOW w1
endif
'CREATE RECEIVE SOCKET
  print w1,"* Make Rx socket "
  s_socket=socket(@AF_INET, @SOCK_DGRAM,@IPPROTO_UDP )
  if s_socket=@INVALID_SOCKET
  print w1,"INVALID SOCKET"
  CLOSEWINDOW w1
  endif
return
'


'********** bind and configure the RX socket here ************

sub bindit()
'BIND THE RECEIVING SOCKET AND MAKE IT A LISTENING PORT!
  Structserver.sin_family = @AF_INET
  Structserver.sin_addr = htonl(@INADDR_ANY)
  Structserver.sin_port = htons(setport)

  Structclient.sin_family = @AF_INET
  Structclient.sin_addr = inet_addr( setaddress )
  Structclient.sin_port = htons(setport)

  print w1,"* Binding sockets"
  perror = bind(s_socket,StructServer,len(StructServer))
  print s_socket
  if perror<>@NO_ERROR
  print w1,"INVALID SOCKET"
  CLOSEWINDOW w1
  endif
move w1,40,150
   print w1,"diagnostic purposes only:"
   noblock=1
   noblocking=noblock
   perror = ioctlsocket(s_socket,@FIONBIO, noblocking)
move w1,40,170
print w1,"server socket setup error =",perror
   perror = ioctlsocket(c_socket,@FIONBIO, noblocking)
move w1,40,190
print w1,"client socket setup error =",perror
   perror = WSAGetLastError()
move w1,40,210
print w1,"General setup error =",perror
return
'


'******** Data packet TX handled here **********
sub sendit()
   xpos=22/7
   ypos=23/7
   zpos=zpos+1
   flightdata="This is UDP Data Packet no."+STR$(zpos)
   perror=sendto(c_socket,flightdata,len(flightdata),0,Structclient,len(Structclient))
move w1,40,80
print "packet data length =",perror,"Bytes"
   move w1,40,40
print w1,"local message =",flightdata
  if perror=@INVALID_SOCKET
  print w1,"INVALID SOCKET"
  CLOSEWINDOW w1
  endif
return
'

'recvfrom(s:uint,buf:string,buflen:int,flags:int,addr:pointer, namelen2:int),int
'******* Data packet RX handled here ********
sub recvit()
perror = MY_rcvfrom(s_socket,bufor,2048,0,0,0)
perror = WSAGetLastError()
move w1,40,60
  print w1,"remote message =",bufor
move w1,300,80
print w1,"Last network error =",perror,"          "
return
'


' ******** closedown routine handled here **********
sub closedown()  
  closesocket(s_socket)
  closesocket(c_socket)
  WSACleanup()
  freemem tempsd
  freemem mHost
return
'

Support Amateur Radio  -  Have a ham  for dinner!

Egil

This morning I have been playing around with this UDP program, and though it appears to work fine in the example, the receive functions have some weaknesses.
The receive buffer is not flushed when receive data has been retreived, and sometimes the receiver routine blocks the program.

I have been searching on internet fo find a way to fix the problems, but in vain. Therefore I wonder if there is someone out there that can tellme how to use the flags in either the API receive or receivefrom functions ins such a way that they become non-blocking, and how the receive buffers can be flushed or reset, I would me very grateful.

I know I have read about this somewhere, but now when I need that information, I am not able to find it.

Regards,

Egil

Support Amateur Radio  -  Have a ham  for dinner!

sapero

Try WSAAsyncSelect, it will set the socket to nonblocking mode, and will send a custom message to your window when a network event occurs (ready to read/write, connected, accepted, disconnected, when you local IP list changed (wifi toggled))
declare "ws2_32.dll", WSAAsyncSelect(sock:int, hWnd:window, message:int, mask:int),int
'message: any number from the WM_APP or WM_USER range
'mask
setid "FD_READ",1
setid "FD_WRITE",2
setid "FD_OOB",4
setid "FD_ACCEPT",8
setid "FD_CONNECT",16
setid "FD_CLOSE",32
setid "FD_QOS",64
setid "FD_ROUTING_INTERFACE_CHANGE",256
setid "FD_ADDRESS_LIST_CHANGE",512
setid "FD_ALL_EVENTS",1023
setid "FD_NONE",0 ' use to cancel notifications

' client_socket = socket(...)
' WSAAsyncSelect(client_socket, d1, 0x401, @FD_ALL_EVENTS)
' server_socket = socket(...)
' WSAAsyncSelect(server_socket, d1, 0x402, @FD_ALL_EVENTS)

' sub handler
'  select @class
'   case 0x401 ' client_socket event
'     When one of the nominated network events occurs on the specified socket s,
'     the application window hWnd receives message wMsg. The wParam parameter
'     identifies the socket on which a network event has occurred. The low word of lParam (lParam=@CODE in CBASIC)
'     specifies the network event that has occurred. The high word of lParam contains
'     any error code. The error code be any error as defined in Winsock2.h.
'   case 0x402 ' server_socket event

Egil

Thanks Sapero!

I'll experiment with your code suggestions to see what happens.

Guess  this is one of the occations where IWB really excells in functionality compared to CB. I already have a working program coded in IWB, where the receiver is running in a thread.

But I have  decided to try making CB code work in a similar way, which at least for me is quite a challenge. I have been browsing the MSDN, and know that the answers always can be found there.
The problem is that MSDN is loaded with so much information. That makes it difficult for persons with limited Windows programming experience to select the most convinient solution.

Regards
Egil
Support Amateur Radio  -  Have a ham  for dinner!

Egil

February 14, 2011, 09:29:49 AM #4 Last Edit: February 15, 2011, 11:52:14 AM by Egil
Using sapero's suggestion, the receive function is no longer blocking.

But still need to study a little to find how to flush the receive buffer after data is read. Exept from the communication routines, I use almost the same setup in a serial communications program that works just fine.
Therefore I assume that I have to look into the communication buffers, to see if I can do it another way.

The code was tested out by starting the program on two different PC's. And I enclose a zipped version of the code I have so far, just in case others want to have a go....

Regards,
Egil

EDIT:
Have deleted the zip file. The latest version can be found below...
Support Amateur Radio  -  Have a ham  for dinner!

sapero

February 14, 2011, 11:53:45 AM #5 Last Edit: February 15, 2011, 10:29:53 AM by sapero
Egil, read all the data from your socket, until the function fails (with SOCKET_ERROR)
SUB UDP_receive()
def ret[256]:istring
def cch:int

' read all data from this socket
cch = recvfrom(s_socket,ret,255,0,0,0)
while (cch>0)
ret[cch]=""
RE_Append(win,7,ret)
cch = recvfrom(s_socket,ret,255,0,0,0) ' missing line
endwhile
RETURN


WSAAsyncSelect doc

Egil

February 15, 2011, 08:14:31 AM #6 Last Edit: February 15, 2011, 11:51:11 AM by Egil
Sapero:
Thanks for the code and the URL.
But when I tried your code (after renaming the variables) the program went into a continuos loop. After a good nights sleep, I made new tries today. First, I found many typos and wrong variable definitions. On MSDN I found that the recvfrom function returns number of bytes received. So what I did was first call the function with the defined istring buffer size to retrieve the error code (containing the actual number of bytes received), and then make a new call using the received error code as buffer size.
That solved my buffer problems. Here is the code:
SUB UDP-receive()

def ret[2048]:istring
def cch:int

' get number of bytes received
cch = recvfrom(s_socket,ret,2047,0,0,0)

' then retrieve that number of bytes
recvfrom(s_socket,ret,cch,0,0,0)

' Append new text to receive window
RE_Append(win,7,ret)

RETURN


Also done some "houshold routines" on the code, but I guess that there much still much "householding" to do. But now I have a working program. The final code enclosed in zip file below.


Regards,
Egil

EDIT:
Have deleted the zip file. The latest version can be found below...
Support Amateur Radio  -  Have a ham  for dinner!

sapero

February 15, 2011, 10:34:18 AM #7 Last Edit: February 15, 2011, 10:47:11 AM by sapero
I am sorry for this, just forgot to duplicate the line with recfrom before endwhile (code updated).
Recfrom does not terminate received data with NULL, you need to do it manually, using the ret[cch]="" trick, or ret[cch]=chr$(0) (assigning zero is not allowed).

The number of received bytes cannot be retrieved with recvfrom, because recvfrom actually reads the data removing it from the buffer, however, the amount that can actually be read in a single call to the recvfrom function is limited to the data size written in the send or sendto function calld. You need to call ioctlsocket(socket, FIONREAD, pointer_to_int) to determine the amount of data pending in the network's input buffer.

In your code: ' get number of bytes received
cch = recvfrom(s_socket,ret,2047,0,0,0)

You read up to 2047 bytes from the queue and ignore the data, calling recvfrom again, probably overwriting your data with new bytes from the queue.

Egil

No problem sapero!

The URL you supplied was more important, at least to me. ;)

Quote from: sapero on February 15, 2011, 10:34:18 AMThe number of received bytes cannot be retrieved with recvfrom, because recvfrom actually reads the data removing it from the buffer, however, the amount that can actually be read in a single call to the recvfrom function is limited to the data size written in the send or sendto function calld. You need to call ioctlsocket(socket, FIONREAD, pointer_to_int) to determine the amount of data pending in the network's input buffer.

The posted code does just the same job. The first call retrieves number of bytes, and the second call retrieves the bytes received. I found on another forum that recvfrom returns the number of bytes received last if all is OK, and an error code indicating last socket error on next call.  Do you think it will become a problem when the buffer is never flushed??  Anyway I will try your suggestion.

Plan now is to make a collection of routines that ends up with approximately the same functions as in udp.inc found elsewhere on the forum. I have for some time now wanted a small collection of general UDP functions for CB, and now I am using this small terminal program as a "case" for testing out such functions.  I have already done a sub retrieving my computers hostname, as this was the easy one... hehe. Then remains functions for retrieving IP adresses from hostmnames, and one for retreiving remote hostname from IP.
Support Amateur Radio  -  Have a ham  for dinner!

Egil

sapero:
You had edited your post when I was answering... hehe.
Now the receiver code has been changed to your example. And the code has a new functions so the computer identifies itself in the sent messages when broadcasting on a LAN. This last function was requested by my "proffesional testing team"... my grandson and two of his friends. They have all brought their laptops and are now chatting on my wireless LAN.  :D :D
I enclose the latest version as a zip file.

Regards,
Egil
Support Amateur Radio  -  Have a ham  for dinner!

sapero

February 15, 2011, 12:01:11 PM #10 Last Edit: February 15, 2011, 12:07:45 PM by sapero
QuoteThe first call retrieves number of bytes
It reads the data and returns the number of bytes copied to your buffer,
Quoteand the second call retrieves the bytes received
Yes, but from the next packet (if any)

If your program lags a while, and someone sends two packets to you, the second call to recvfrom will overwrite the data read in the first call to recvfrom, so you need to "use" the data returned from each call to recvfrom:
cch = recvfrom(s_socket,ret,2047,0,0,0) ' the first data packet (or a part of it)
' todo: if (cch<1) then done
' todo: display cch bytes from buffer ret

cch = recvfrom(s_socket,ret,2047,0,0,0) ' the remaining data from the first packet (if any) + the second packet (if any)
' todo: if (cch<1) then done
' todo: display cch bytes from buffer ret
...


When cch is set to -1 (SOCKET_ERROR) WSAGetLastError() probably will return WSAEWOULDBLOCK (10035) indicating that the RX buffer is empty.
If recvfrom returns 0, this means that the connection has been closed.

Egil

Thanks sapero!
The code is already corrected.

Regards,

Egil
Support Amateur Radio  -  Have a ham  for dinner!