× The internal search function is temporarily non-functional. The current search engine is no longer viable and we are researching alternatives.
As a stop gap measure, we are using Google's custom search engine service.
If you know of an easy to use, open source, search engine ... please contact support@midrange.com.




I need to write a socket program that will receive data from a probe when a
button is pressed and save the information in an AS400 db2 database.  I've
created a program that creates the socket, binds, listens then translates
the data.  The problem is that I don't receive consistant data from the
buffer.

Hmmm... from your description, you'd call socket(), setsockopt(), bind(), listen() and accept(). But, the code calls socket() and connect(). In other words, you're describing a server connection (you even use the terms "bind" and "listen" in your description) but your code looks like a client program, not a server!

Network communications always require the cooperation of two different programs. You've given us lots of information about one of those programs -- your RPG program -- but you've told us nothing about the other end, aside from the fact that it's a "probe."

What sort of data is it supposed to be sending? How is it supposed to send it?

Should I be using a UDP server to acheive the desired results?

Depends. Does the probe send you a UDP datagram? Or does it open a TCP connection? Unless I'm familiar with your probe, I'm not in a position to answer this question.

But, if the probe is sending UDP data, you'd get nothing at all if you listen with TCP. So the fact that you're getting something implies that it's not UDP.


Here is the current program; any help would be appreciated. I used one of Scott Klement's examples.

Unfortunately, this isn't one of my examples. Now I feel obliged to point out the things that you're doing that I wouldn't do, and don't recommend that you do.


  2300      D getservbyname   PR              *   ExtProc('getservbyname')
  2400      D  service_name                   *   value options(*string)
  2500      D  protocol_name                  *   value options(*string)
[SNIP]

I always put the prototypes, and related definitions, for the socket APIs in a copy book. I'd never put them directly in a program because:

a) It clutters up the code making it harder to read, and harder for the reader to focus on what the code's purpose is.

b) It makes it more difficult to re-use the definitions.

c) It creates a maintenance nightmare should a bug be found in one of the definitions because you now have many places to fix it instead of one.

d) The reader can't rely on the definitions being the same as they were in other programs he's read, since they're re-coded in each program. he has to re-read them. Instead of learning it once, he has to learn it over and over again.

I strongly recommend that you use a /COPY member, as I did in all of my examples.

 11600      D cr              s              1    inz(X'0D')
 11700      D esc             s              1    inz(X'27')

Since the values of CR and ESC shouldn't ever change, you shouldn't store them in variables. That's what constants are for.

12500 D WWCMDSTR C CONST('ALCOBJ
OBJ((MELT_0 *DTAARA  -                                 05/04/06
 12600      D                                     *EXCL)) WAIT(0)')
 12700      D WCMDLNGTH       S             15  5 INZ(80)
 12800      D wwcmdstr1       s             80a
   .
   .
 13400      C                   CALL      'QCMDEXC'
 13500      C                   PARM                    WWCMDSTR1
 13600      C                   PARM                    WCMDLNGTH

As a programmer reading your code, I'd like to see the command you're executing at the time you're executing it. Having to go back and look at the D-specs to see the command is awkward.

Also, the use of CALL/PARM has a lot of disadvantages over using a prototyped call. Prototypes are safer and allow the compiler to do more of the work for you.

I'd much rather see this in your code, because I know exactly what you're doing:

     C                   CALLP     QCMDEXC('ALCOBJ -
     C                                OBJ((MELT_0 *DTAARA *EXCL)) -
     C                                WAIT(0)': 80)

Or even better:

      /free
          qcmdexc('ALCOBJ OBJ((MELT_O *DTAARA *EXCL)) WAIT(0)': 80);

 13700      C                   callp     Open_Session
 13800      C                   callp     Talk
 13900      C                   callp     End_Session

This code provides no way for the program to handle errors that occur in the various subprocedures, and it uses global variables to talk between the subprocedures, which makes the procedures harder to re-use, harder to debug, and harder to maintain as time goes on. In short, it makes subprocedures as bad as subroutines! Well, almost as bad.

Instead, I'd write the code like this:

      C                   callp     sock = Open_Session('MELTING0': 2000)
      C                   if        sock <> -1
      C                   callp     Talk(sock)
      C                   callp     End_Session(sock)
      C                   endif

That way:

a) You can re-use the Open_Session subprocedure in future programs that need to connect to connect to a particular host on a particular port.

b) You can debug the Open_Session routine independently of the others, since all of the input and output are parameters, you can call it directly at the start of your program and debug it by itself until you're sure it's working correctly. Then you can re-use it in all of your future socket programs without any

c) If something goes wrong in Open_Session, you don't want to run the Talk() or End_Session() subprocedures because you won't have a session to talk over or end -- so this version of the code takes that into account.


 14800      C*  We're done, so close the socket.
 14900      C*   do a dsply with input to pause the display
 15000      C*   and then end the program
 15100      C*************************************************
 15200      P End_Session     b
 15300      D End_Session     pi
 15400      c                   callp     close(sock)
 15500      c                   eval      *inlr = *on
 15600      c                   return
 15700      P End_Session     e

Unfortunately, this code does NOT end the program. It does set on *INLR, so if the program reaches a particular point in the RPG cycle, it'll end, but this code doesn't actually end it.

I suppose it's possible that you didn't mean to end the program here -- but the comments say that you did, so I'm assuming that you did. Of course, they also say that you're doing a DSPLY (and you're not).

Just remember that this subprocedure will close the socket, but will not end the program.

 16800      C                   monitor
 16900      C                   eval      port = %dec(2000:5:0)
 17000      C                   on-error  *all
 17100      c                   eval      p_servent = getservbyname('MELTING0'  
                                       04/21/06
 17200      c                             :'tcp')
 17300      c                   if        p_servent = *NULL
 17400      c                   callp     End_Session
 17500      c                   return
 17600      C                   endif
 17800      c                   eval      port = s_port
 17900      C                   endmon

This code really confuses me. The way I'm reading it, here's what it does:

a) Check if 2000 is a number.

b) If 2000 isn't a number, look up the 'MELTING0' tcp service in the services table.

c) If 'MELTING0' isn't in the services table, close the socket (which hasn't been opened, yet!) and RETURN so that we can proceed to run the Talk() subprocedure.

d) If 'MELTING0' is in the services table, and 2000 isn't a valid number, then use the port that corresponds to 'MELTING0' as the port number.

Your code would make sense if "2000" was a variable rather than a hard-coded number, since it would first make sure it's numeric, and if it's not numeric it'd assume it was a services name and look it up in the services table.

Though, you still wouldn't want to call End_Session(), since the socket isn't open and there's no need to close it... but at least the rest of it would make sense if you used a variable :)

As it stands, since 2000 is always a number, you could replace this entire sectiopn of code with this:

     C                   eval      port = 2000

If your goal is to hard-code the number 2000 if the service isn't found in the services table, then you need to call getservbyname() first, and only assign the port number if that call fails. (I don't know if that was your intention or not, but it's what I usually do.)

 18100      C*************************************************
 18200      C* Get the 32-bit network IP address for the host
 18300      C*  that was supplied by the user:
 18400      C*************************************************
 18500      c                   eval      IP = inet_addr('MELTING0')

Again, this would make sense if you used a variable instead of hard-coding 'MELTING0'. Since the hard-coded value will never be an IP address, there's no point to passing it to inet_addr(). You could go straight to calling gethostbyname().

If this were a variable, however, you wouldn't know if it were an IP address or host name. That's why you call inet_Addr() first to see if it's an IP address. If it's not, you call gethostbyname() to try a DNS/Host table lookup.

 18600      c                   if        IP = INADDR_NONE
 18700      c                   eval      p_hostent = gethostbyname('MELTING0')
 18800      c                   if        p_hostent = *NULL
 18900      c                   eval      fedesc= 'Unable to find that host!'
 19000      c                   eval      fmdate = %date()
 19100      c                   eval      fmtime = %time()
 19200      c                   write     rfmelterr
 19300      C                   eval      *inlr = *on
 19400      c                   return
 19500      c                   endif
 19600      c                   eval      IP = h_addr
 19700      c                   endif

Again, please keep in mind that setting on *INLR at this point doesn't end the program. The code will proceed to call the Talk() subprocedure. That's why I'd have a return value from the subprocedure that you can check in the mainline. If this did a "return -1" when the DNS lookup failed, then the mainline could check for the -1, and skip the Talk() and End_Session() procs.

Also, the error logging that you're writing to "fmelterr" would be a great candidate for a subprocedure. Then you could simply code callp logerr('Unable to find that host!') and the logerr subprocedure could do the work of getting the date and time and writing it to the file. That way, it's one line of code each time you wnat to log something, making ti easy to use over-and-over again in your program.


 19900      C*************************************************
 20000      C* Create a socket
 20100      C*************************************************
 20200      c                   eval      sock = socket(AF_INET: SOCK_STREAM:   
                                       05/20/05
 20300      c                                           IPPROTO_IP)
 20400      c                   if        sock < 0
 20500      c                   eval      fedesc = %editc(sock:'X') + ' '+
 20600      c                             'Error calling socket()!'
 20700      c                   eval      fedate = %date()
 20800      c                   eval      fetime = %time()
 20900      c                   write     rfmelterr
 21000      c                   return
 21100      c                   endif

Again, this would be a nice place for "callp logerr". Also, the RETURN statement will cause the Talk() and End_Session() procedures to run since you aren't letting the caller know that an error occurred. Again, I recommend using return -1 or something similar.

 21300      C*************************************************
 21400      C* Create a socket address structure that
 21500      C*   describes the host & port we wanted to
 21600      C*   connect to
 21700      C*************************************************
 21800      c                   eval      addrlen = %size(sockaddr)
 21900      c                   alloc     addrlen       p_connto

 22000
 22100      c                   eval      p_sockaddr = p_connto
 22200      c                   eval      sin_family = AF_INET
 22300      c                   eval      sin_addr = IP
07/10/01
 22400      c                   eval      sin_port = port
 22500      c                   eval      sin_zero = *ALLx'00'
 22700      C*************************************************
 22800      C* Connect to the requested host
 22900      C*************************************************
 23000      C                   if        connect(sock: p_connto: addrlen)< 0

The ALLOC stuff is my own fault, I know that. I wish I hadn't used ALLOC in my examples in my old socket tutorial. However... if you do want to use ALLOC, you must also make sure you use DEALLOC to free up the memory you allocated.

My suggestion is that you do it a little differently than the socket tutorial, and do this:

     D connto          s                   like(sockaddr_in)
       .
       .
     c                   eval      connto = *ALLx'00'
     c                   eval      p_sockaddr = %addr(connto)
     c                   eval      sin_family = AF_INET
     c                   eval      sin_addr   = IP
     c                   eval      sin_port   = port

     C                   if        connect( sock
     C                                    : %addr(connto)
     C                                    : %size(connto)) < 0

The advantage is that there's no need to use ALLOC anymore to allocate memory. The compiler will automatically allocate the right amount of space to the connto variable, and you use that memory instead.

If you're lucky enough to have V5R1 or later, there's an even better alternative: the LIKEDS keyword. Using that, you can eliminate the pointer logic altogether:

     D connto          s                   likeDS(sockaddr_in)
       .
       .
     c                   eval      connto = *ALLx'00'
     c                   eval      connto.sin_family = AF_INET
     c                   eval      connto.sin_addr   = IP
     c                   eval      connto.sin_port   = port

     C                   if        connect( sock
     C                                    : %addr(connto)
     C                                    : %size(connto)) < 0

 23100      c                   eval      fedesc= 'unable to connect to server!'
 23200      c                   eval      fedate = %date()
 23300      c                   eval      fetime = %time()
 23400      c                   write     rfmelterr
 23500      c                   callp     close(sock)
 23600      c                   return
 23700      c                   endif

AGain... same comments about using a logerr() subprocedure and returning something to indicate to the caller that there was an error.

I would also check errno/strerror to get an error message. The connect() API is especially important to do that with since there's a wide variety of possible errors, and connections are usually the point at which errors are encountered. You want to log a meaningful error, not just that it failed, so that when this program is running in production, people will have some chance of troubleshooting problems with it!

I don't have any comments about your other subprocedures except what I've said already... In particular, I'm confused about the fact that you've said that you're doing a bind, listen,etc, but your code is doing a connect... and, again, if you're getting any data (well, valid data!) this way, it's not a UDP transaction.

If you really do need to act as a client and connect to the server, I suggest trying it from a telnet client on a PC to see what sort of data you should expect. Then do the same thing with your program and you should get the same data...


As an Amazon Associate we earn from qualifying purchases.

This thread ...

Replies:

Follow On AppleNews
Return to Archive home page | Return to MIDRANGE.COM home page

This mailing list archive is Copyright 1997-2024 by midrange.com and David Gibbs as a compilation work. Use of the archive is restricted to research of a business or technical nature. Any other uses are prohibited. Full details are available on our policy page. If you have questions about this, please contact [javascript protected email address].

Operating expenses for this site are earned using the Amazon Associate program and Google Adsense.