The vast majority of options in the setsockopt() API are only used rarely. However, when running a server, it's a very good idea to set two of them:
SO_REUSEADDR -- As we already discussed, if we want to use a specific port, but don't want to wait for the MSL timeout, we need to turn this option on in our server program.
SO_LINGER -- Whenever the send() API is used to send data, it gets placed into a buffer, and is actually sent when the program on the other end of the connection is ready to recv() it. If we've sent some data, then called close() to drop the connection, what should the system do with data that's still in its send buffer? By default, it tries to send it forever. However, network errors could cause this to waste some memory indefinitely! So, for long running programs like a server, we should tell it how long to linger on that data...
Therefore, we'll insert these new options into our example server program. They'll go into the "MakeListner" subroutine, right after we've called the socket() API.
C* Tell socket that we want to be able to re-use port 4000 C* without waiting for the MSL timeout: c callp setsockopt(lsock: SOL_SOCKET: c SO_REUSEADDR: %addr(on): %size(on)) C* create space for a linger structure c eval linglen = %size(linger) c alloc linglen ling c eval p_linger = ling C* Data shouldnt need to linger on the "listener" socket, but C* give it one second, just in case: c eval l_onoff = 1 c eval l_linger = 120 c callp setsockopt(lsock: SOL_SOCKET: SO_LINGER: c ling: linglen)
Now, remember, we have two sockets, the one we create in MakeListener, and the one that's returned by the accept() API in the AcceptConn subroutine. The SO_REUSEADDR option wouldn't make sense on the accept() socket (since we don't bind it) but we should set the SO_LINGER value!
So, insert this code, right after we call accept():
C* tell socket to only linger for 2 minutes, then discard: c eval l_onoff = 1 c eval l_linger = 120 c callp setsockopt(csock: SOL_SOCKET: SO_LINGER: c ling: linglen)
After adding these options, we can go ahead and compile & test our program again. Notice that now, we can re-run the program immediately, after it ends, we don't have to wait 2 minutes...
Just to dispel any confusion, here's a full listing of our server program up to this point:
H DFTACTGRP(*NO) ACTGRP(*NEW) H BNDDIR('SOCKTUT/SOCKUTIL') BNDDIR('QC2LE') D/copy socktut/qrpglesrc,socket_h D/copy socktut/qrpglesrc,errno_h D/copy socktut/qrpglesrc,sockutil_h D Cmd PR ExtPgm('QCMDEXC') D command 200A const D length 15P 5 const D die PR D peMsg 256A const D len S 10I 0 D bindto S * D connfrom S * D port S 5U 0 D lsock S 10I 0 D csock S 10I 0 D line S 80A D err S 10I 0 D clientip S 17A D ling S * D linglen S 10I 0 D on S 10I 0 inz(1) c eval *inlr = *on c exsr MakeListener c dow 1 = 1 c exsr AcceptConn c exsr TalkToClient c callp close(csock) c if line = 'quit' c callp close(lsock) c return c endif c enddo C*=============================================================== C* This subroutine sets up a socket to listen for connections C*=============================================================== CSR MakeListener begsr C*------------------------ C** Normally, you'd look the port up in the service table with C** the getservbyname command. However, since this is a 'test' C** protocol -- not an internet standard -- we'll just pick a C** port number. Port 4000 is often used for MUDs... should be C** free... c eval port = 4000 C* Allocate some space for some socket addresses c eval len = %size(sockaddr_in) c alloc len bindto c alloc len connfrom C* make a new socket c eval lsock = socket(AF_INET: SOCK_STREAM: c IPPROTO_IP) c if lsock < 0 c callp die('socket(): ' + %str(strerror(errno))) c return c endif C* Tell socket that we want to be able to re-use port 4000 C* without waiting for the MSL timeout: c callp setsockopt(lsock: SOL_SOCKET: c SO_REUSEADDR: %addr(on): %size(on)) C* create space for a linger structure c eval linglen = %size(linger) c alloc linglen ling c eval p_linger = ling C* tell socket to only linger for 2 minutes, then discard: c eval l_onoff = 1 c eval l_linger = 1 c callp setsockopt(lsock: SOL_SOCKET: SO_LINGER: c ling: linglen) C* bind the socket to port 4000, of any IP address c eval p_sockaddr = bindto c eval sin_family = AF_INET c eval sin_addr = INADDR_ANY c eval sin_port = port c eval sin_zero = *ALLx'00' c if bind(lsock: bindto: len) < 0 c eval err = errno c callp close(lsock) c callp die('bind(): ' + %str(strerror(err))) c return c endif C* Indicate that we want to listen for connections c if listen(lsock: 5) < 0 c eval err = errno c callp close(lsock) c callp die('listen(): ' + %str(strerror(err))) c return c endif C*------------------------ CSR endsr C*=============================================================== C* This subroutine accepts a new socket connection C*=============================================================== CSR AcceptConn begsr C*------------------------ c dou len = %size(sockaddr_in) C* Accept the next connection. c eval len = %size(sockaddr_in) c eval csock = accept(lsock: connfrom: len) c if csock < 0 c eval err = errno c callp close(lsock) c callp die('accept(): ' + %str(strerror(err))) c return c endif C* tell socket to only linger for 2 minutes, then discard: c eval l_onoff = 1 c eval l_linger = 120 c callp setsockopt(csock: SOL_SOCKET: SO_LINGER: c ling: linglen) C* If socket length is not 16, then the client isn't sending the C* same address family as we are using... that scares me, so C* we'll kick that guy off. c if len <> %size(sockaddr_in) c callp close(csock) c endif c enddo c eval p_sockaddr = connfrom c eval clientip = %str(inet_ntoa(sin_addr)) C*------------------------ CSR endsr C*=============================================================== C* This does a quick little conversation with the connecting c* client. That oughta teach em. C*=============================================================== CSR TalkToClient begsr C*------------------------ c callp WrLine(csock: 'Connection from ' + c %trim(clientip)) c callp WrLine(csock: 'Please enter your name' + c ' now!') c if RdLine(csock: %addr(line): 80: *On) < 0 c leavesr c endif c if line = 'quit' c leavesr c endif c callp WrLine(csock: 'Hello ' + %trim(line)) c callp WrLine(csock: 'Goodbye ' + %trim(line)) c callp Cmd('DLYJOB DLY(1)': 200) C*------------------------ CSR endsr *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * 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