|
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 = 2000If 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)) < 0The 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 mailing list archive is Copyright 1997-2025 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.