8.5. Sample UDP server

Time to put try our new found UDP knowledge out with a sample UDP server program. This program will receive datagrams containing a user-id and some text, and then will send that user a message using the SNDMSG command. Once the message has been sent, it will send back a datagram that acknowledges that the message was sent correctly.

So, the pseudocode for our server looks like this:

  1. Create a UDP socket

  2. Bind the socket to a port. Maybe port 4000, that's worked pretty well so far, yes?

  3. Wait for any datagrams to be received. (we can use select() to give us a timeout, so we can check for system shut down)

  4. convert the datagram to EBCDIC.

  5. substring out a userid & message

  6. Call the SNDMSG command to send the message

  7. Send back an acknowledgement packet

  8. GO back to step 3.

Note: Unlikely TCP, we CANNOT use a simple telnet client to test our server program. We'll have to write a client program to play with our server -- which we'll do in the next topic.

Here's our server:

    File SOCKTUT/QRPGLESRC, Member: UDPSERVER
         H DFTACTGRP(*NO) ACTGRP(*NEW)
         H BNDDIR('QC2LE') BNDDIR('SOCKTUT/SOCKUTIL')
    
         D/copy socktut/qrpglesrc,socket_h
         D/copy socktut/qrpglesrc,sockutil_h
         D/copy socktut/qrpglesrc,errno_h
    
         D Cmd             PR                  ExtPgm('QCMDEXC')
         D   command                    200A   const
         D   length                      15P 5 const
    
         D translate       PR                  ExtPgm('QDCXLATE')
         D   length                       5P 0 const
         D   data                     32766A   options(*varsize)
         D   table                       10A   const
    
         D die             PR
         D   peMsg                      256A   const
    
         D sock            S             10I 0
         D err             S             10I 0
         D bindto          S               *
         D len             S             10I 0
         D dtalen          S             10I 0
         D msgfrom         S               *
         D fromlen         S             10I 0
         D timeout         S               *
         D tolen           S             10I 0
         D readset         S                   like(fdset)
         D excpset         S                   like(fdset)
         D user            S             10A
         D msg             S            150A
         D buf             S            256A
         D rc              S             10I 0
    
         c                   eval      *inlr = *on
    
         C* Reserve space for the address that we receive a 
         C*  datagram from.
         c                   eval      fromlen = %size(sockaddr_in)
         c                   alloc     fromlen       msgfrom
    
         C* Create a timeval structure so we can check for shutdown
         C*   every 25 seconds:
         c                   eval      tolen = %size(timeval)
         c                   alloc     tolen         timeout
    
         c                   eval      p_timeval = timeout
         c                   eval      tv_sec = 25
         c                   eval      tv_usec = 0
    
         C* Create a socket to do our UDP stuff:
         c                   eval      sock = socket(AF_INET: SOCK_DGRAM:
         c                                         IPPROTO_IP)
         c                   if        sock < 0
         c                   callp     die('socket(): '+%str(strerror(errno)))
         c                   return
         c                   endif
    
         C* Create a sockaddr struct to tell the
         C*  bind() API which port to use.
         c                   eval      len = %size(sockaddr_in)
         c                   alloc     len           bindto
    
         c                   eval      p_sockaddr = bindto
         c                   eval      sin_family = AF_INET
         c                   eval      sin_addr = INADDR_ANY
         c                   eval      sin_port = 4000
         c                   eval      sin_zero = *ALLx'00'
    
         C* bind to the port.
         c                   if        bind(sock: bindto: len) < 0
         c                   eval      err = errno
         c                   callp     close(sock)
         c                   callp     die('bind(): '+%str(strerror(err)))
         c                   return
         c                   endif
    
         c                   dow        1 = 1
    
         C* Use select to determine when data is found
         c                   callp     fd_zero(readset)
         c                   callp     fd_zero(excpset)
         c                   callp     fd_set(sock: readset)
         c                   callp     fd_set(sock: excpset)
    
         c                   eval      rc = select(sock+1: %addr(readset):
         c                                *NULL: %addr(excpset): timeout)
    
         C* If shutdown is requested, end program.
         c                   shtdn                                        99
         c                   if        *in99 = *on
         c                   callp     close(sock)
         c                   return
         c                   endif
    
         C* If select timed out, go back to select()...
         c                   if        rc  = 0
         c                   iter
         c                   endif
    
         C* Receive a datagram:
         c                   eval      dtalen = recvfrom(sock: %addr(buf):
         c                                %size(buf): 0: msgfrom: fromlen)
    
         C* Check for errors
         c                   if        dtalen < 0
         c                   eval      err = errno
         c                   callp     close(sock)
         c                   callp     die('recvfrom(): '+%str(strerror(err)))
         c                   return
         c                   endif
    
         C* Skip any invalid messages
         c                   if        dtalen < 11
         c                   iter
         c                   endif
    
         C* Skip any messages from invalid addresses
         c                   if        fromlen <> %size(sockaddr_in)
         c                   eval      fromlen = %size(sockaddr_in)
         c                   iter
         c                   endif
    
         C* Translate to EBCDIC
         c                   callp     Translate(dtalen: buf: 'QTCPEBC')
    
         c* send message to user
         c                   eval      user = %subst(buf:1: 10)
         c                   eval      dtalen = dtalen - 10
         c                   eval      msg = %subst(buf:11:dtalen)
    
         c                   callp(e)  Cmd('SNDMSG MSG(''' + %trimr(msg) +
         c                                ''') TOUSR(' + %trim(user) + ')': 200)
    
         c* make an acknowledgement
         c                   if        %error
         c                   eval      buf = 'failed'
         c                   eval      dtalen = 6
         c                   else
         c                   eval      buf = 'success'
         c                   eval      dtalen = 7
         c                   endif
    
         c* convert acknowledgement to ASCII
         c                   callp     Translate(dtalen: buf: 'QTCPASC')
    
         c* send acknowledgement to ASCII
         c                   if        sendto(sock: %addr(buf): dtalen: 0:
         c                                   msgfrom: fromlen) < 0
         c                   eval      err = errno
         c                   callp     close(sock)
         c                   callp     die('sendto(): '+%str(strerror(err)))
         c                   return
         c                   endif
    
         c                   enddo
    
    
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
          *  This ends this program abnormally, and sends back an escape.
          *   message explaining the failure.
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         P die             B
         D die             PI
         D   peMsg                      256A   const
    
         D SndPgmMsg       PR                  ExtPgm('QMHSNDPM')
         D   MessageID                    7A   Const
         D   QualMsgF                    20A   Const
         D   MsgData                    256A   Const
         D   MsgDtaLen                   10I 0 Const
         D   MsgType                     10A   Const
         D   CallStkEnt                  10A   Const
         D   CallStkCnt                  10I 0 Const
         D   MessageKey                   4A
         D   ErrorCode                32766A   options(*varsize)
    
         D dsEC            DS
         D  dsECBytesP             1      4I 0 INZ(256)
         D  dsECBytesA             5      8I 0 INZ(0)
         D  dsECMsgID              9     15
         D  dsECReserv            16     16
         D  dsECMsgDta            17    256
    
         D wwMsgLen        S             10I 0
         D wwTheKey        S              4A
    
         c                   eval      wwMsgLen = %len(%trimr(peMsg))
         c                   if        wwMsgLen<1
         c                   return
         c                   endif
    
         c                   callp     SndPgmMsg('CPF9897': 'QCPFMSG   *LIBL':
         c                               peMsg: wwMsgLen: '*ESCAPE':
         c                               '*PGMBDY': 1: wwTheKey: dsEC)
    
         c                   return
         P                 E
    
          /define ERRNO_LOAD_PROCEDURE
          /copy socktut/qrpglesrc,errno_h