GT.M socket o.k. w/Telnet client not w/others

2005-10-07
2012-12-29
  • I'm writing a socket server on GT.M, and it receives and responds to Telnet
    clients just fine.  But, when I write a sockets based client and try to
    send a string, the GT.M server just sits there. I've been examining the
    TCP dumps and everything, but just can't figure it out.

    You can see this behavior with the SCKSRV2.m sample that Sanchez
    provides.

    Here's $ZA, $ZB, $ZC all look fine and indicate no errors.

    It it much more reliable to send the message length first and then just
    read a specific number of bytes on the second message?

    Richard

     
    • as a followup to my previous post, I should also mention that the server seems to get stuck when reading a delimiter.  Here's the code:

      OPEN %ZNDev:(ZLISTEN=%ZNPort_":TCP":DELIMITER=$CHAR(13,10,58,27,95,58,0):ATTACH="l
      istener"):%ZNTimeS:"SOCKET"

      This opens the port - notice the three delimiters.  The first - standard cr/lf.  The second, $CHAR(27,95) is read by the server as an indication to close the socket.  The third - a delimiter I had to add because my client likes to send a lot of zero bytes (argh!).

      Here's the code that picks up the second delimiter. It's very similar in format to SCKSRV2.m

      ; label readSock jumps to the READ command without reopening a socket. Assumes current socket should be used.
      ; set %ZNResTerm to the key (DELIMITER) of last read.
      %ZNResTerm=$KEY
      multiEr5 if %ZNCmd="seq" DO  goto readSock
      . WRITE "seq"_^MYGLOBALS("SEQ"),!
      . S ^MYGLOBALS("SEQ")=^MYGLOBALS("SEQ")+1
      . IF %ZNResTerm=$CHAR(27,95) DO  goto multiWW
      . . CLOSE %ZNDev:(SOCKET=%ZNSock)
      . . SET ^GLOBALS("SOCKSTATUS")="socket closed"
      . . ZSHOW "D"

      It looks like the code gets stuck at the read command, and when I kill socket on the client side (terminate the client), the label multiEr5 is called and GT.M sends the data back on the wire (to a client that is no longer active). 

      Go figure ....

      Am I not handling the delimiters correctly or what?

      Richard

       
    • Oh, and one more thing .... when I telnet to the server it works just fine (the server runs on port 9000):

      $ telnet <server host> 9000
      seq                        <--- my input
      seq 9                     <--- server response

      seq                        <-- my input
      seq 10                    <-- server response

      etc ......

       
    • Hi Richard,

      two questions:
      1) can you provide me with the complete code (you included the OPEN and the code that hanldes the "end-of-communication", but I don't see the USE commands and the READ commands

      2) Can you provide me with a sample string that the client sends?

      thanks,
      Frans S.C. Witte

       
    • I'll send you via e-mail the code file.  But, it's posted here as well. 

      We're going to release it as part of the OpenEMed  distribution (www.openemed.net), but I think it's a good example to package with your things.

      I have it working now I think for the most part.  Here's what I did:

      1.) removed $CHAR(0) as a delimiter - i just ignore reads that don't have a valid command in them.
      2.) changed the XBFSIZEand ZIBFSIZE to 1024 - although, I admit I'm not sure how much this helps because the documentation on these parameters is kind of general.
      3.) made sure that the USE command is only executed after a socket is closed by the server.  If the server doesn't receive the delimiter that tells it to close the connection ($CHAR(27,95)) then the next READ is done on the existing socket.
      4.) made sure the client handles the condition of a closed socket on the server end (this is important to do anyway.)

      ========= CODE FOLLOWS ==============
      OEMEDSRV ;schilling@cognitiongroup.biz OpenEMed Socket Device, Server
      ; NO ^ROUTINE ENTRY
      QUIT
      ;----------------------------------------------------------------------
      ; This deriverative work of SCKSRV2.m is copyright Cognition Group, Inc.
      ; Licensed under the BSD license.
      ; Copyright (c) 2005, Cognition Group, Inc.
      ; All rights reserved.
      ;
      ; Redistribution and use in source and binary forms, with or without
      ; modification, are permitted provided that the following conditions
      ; are met:
      ;  * Redistributions of source code must retain the above copyright
      ;    notice, this list of conditions and the following disclaimer.
      ;  * Redistributions in binary form must reproduce the above
      ;    copyright notice, this list of conditions and the following
      ;    disclaimer in the documentation and/or other materials provided
      ;    with the distribution.
      ;  * Neither the name of Cognition Group nor the names of its
      ;    contributors may be used to endorse or promote products derived
      ;    from this software without specific prior written permission.
      ;
      ; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      ; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      ; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
      ; FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
      ; COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
      ; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
      ; BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      ; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
      ; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      ; LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
      ; ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      ; POSSIBILITY OF SUCH DAMAGE.
      ;
      ; ==================================================================
      ; public procedure multi^OEMEDSRV(<port>,<timeout>)
      ;
      ; September 2005
      ;
      ; General TCP/IP server procedure to handle multiple client connections.
      ; This code was adapted from the Sanchez multi-client socket example,
      ; SCKSERV2.m.  In addition to simple set/get commands, this routine
      ; also responds to an order command, which executes the $ORDER() function
      ; on a global variable.
      ;
      ; Output from the multi function is placed in the following global:
      ;      global ^OPENEMED("OEMEDSRV",$job, .... where $job is the job number
      ;
      ; COMANDS HANDLED BY THIS SERVER
      ; This server is designed to be a server for OpenEMed clients.  The OpenEMed
      ; client is typically a PIDS server and/or a COAS server, both of which
      ; need to save/restore information in the database under the global
      ; variable ^OPENEMED.  Here are the commands that this server will respond
      ; to:
      ;     PIDS server:
      ;        seq: returns the next unique sequence number. The
      ;             sequence numbers are used for arbitrary purposes by
      ;            OpenEMed, but it is imperative for the proper
      ;            functioning of OpenEMed that each time this command
      ;            is received by the server, a new and unique number is
      ;            returned.
      ;        get <global>: returns the global value if it's defined.  An
      ;            error is returned if the global is not defined. 
      ;            Typically, OpenEMed will know what global name space
      ;             it expects to find values under, so even though in
      ;            theory OpenEMed can access any global in the data
      ;            base, it will typically only look for globals under
      ;            the ^OPENEMED namespace
      ;        set <global> : sets the global value. 
      ;            Typically, OpenEMed will know what global name space
      ;             it expects to find values under, so even though in
      ;            theory OpenEMed can access any global in the data
      ;            base, it will typically only look for globals under
      ;            the ^OPENEMED namespace
      ;        cr/lf : This is the character sequence $CHAR(13,10) in mumps,
      ;            and "\r\n" in Java strings.  This 'command' is
      ;            interpreted as a delimiter in this server, and is used
      ;             to separate multiple commands sent to the server at
      ;            once.
      ;        $CHAR(27,95) : This is the character sequence this server
      ;            will use to close the connection.  The last command
      ;            sent to this server from the client is terminated
      ;            with this byte sequence instead of cr/lf.  See
      ;             the OpenEMed code in
      ;            $TM_HOME/src/tools/gov/lanl/Database/GTMDatabaseMgr.java ;            for more information.
      ;           
      ;
      multi(%ZNPort,%ZNTimeS) new %Z0,%ZNCmd,%ZNData,%ZNDev,%ZNLevel,%ZNSock
      ; make sure we have a sequence number stored
      IF $DATA(^OPENEMED("SEQUENCE"))#10=0 S ^OPENEMED("SEQUENCE")=1
      ;
      ; set a new errortrap
      S %ZNTermDev=$IO
      new $ZTRAP set $ZTRAP="ZGOTO "_$ZLEVEL_":multiQ"
      WRITE "OpenEMed Server for GT.M. Copyright 2005 Cognition Group, Inc. BSD License",!
      WRITE "Server output is recorded in ^OPENEMED(""OEMEDSRV"","_$job_").",!
      kill ^OPENEMED("OEMEDSRV",$job)
      ;
      ; Construct a devicename.
      set %ZNDev="SCK$HI"
      ;
      ; Open the device
      OPEN %ZNDev:(ZLISTEN=%ZNPort_":TCP":DELIMITER=$C(13,10,58,27,95):ATTACH="listener":ZBFSIZE=1024:ZIBFSIZE=1024):%ZNTimeS:"SOCKET"
      ;OPEN %ZNDev:(ZLISTEN=%ZNPort_":TCP":DELIMITER=$C(13,10,58,27,95,58,0):ATTACH="listener"):%ZNTimeS:"SOCKET"
      ;OPEN %ZNDev:(ZLISTEN=%ZNPort_":TCP":ATTACH="listener"):%ZNTimeS:"SOCKET"
      ; OPEN %ZNDev:(ZLISTEN=%ZNPort_":TCP":DELIMITER=$C(13,10,58,0):ATTACH="listener"):%ZNTimeS:"SOCKET"
      else  set ^OPENEMED("OEMEDSRV",$job)="-1,NotOpen" quit
      ;
      ; specify a trap that does not terminate on indirection errors
      set $ZTRAP="GOTO multiX",%ZNLevel=$ZLEVEL
      set ^OPENEMED("OEMEDSRV",$job)=0,%Z0=0
      ;
      ; USE fills $KEY with "BOUND|socket_handle|portnumber"
      use %ZNDev set ^OPENEMED("OEMEDSRV",$job,0)=$KEY
      ;
      ; Start listening, sets $KEY to "LISTENING|socket_handle|portnumber"
      WRITE /LISTEN(4) set ^OPENEMED("OEMEDSRV",$job,1)=$KEY
      ;
      ; Wait for events, and handle them
      multiWW goto:^OPENEMED("OEMEDSRV",$job) multiC
      set %Z0=%Z0+1
      ;
      USE %ZNDev:(SOCKET="listener") WRITE /WAIT(%ZNTimeS)
      ;
      ; /WAIT() fills $KEY with empty string when no event occurred
      ; otherwise with either "CONNECT|socket_handle|portnumber"
      ; or "READ|socket_handle|portnumber"
      IF $KEY="" goto multiWW
      SET %ZNCmd=$PIECE($KEY,"|"),%ZNSock=$PIECE($KEY,"|",2)
      ;
      ; If this is a new connection, initiate some vars
      IF %ZNCmd="CONNECT" set %ZNSock(%ZNSock)=0 goto multiWW
      ;
      ; if this is a read-event, use that socket, and read the data
      ; for multi-socket servers, it is essential to include the
      ; EXCEPTION-deviceparameter, because the READ sometimes terminates
      ; with a $DEVICE!=0, and sometimes with a runtime error
      ; If you look at ^SCKSERV you can recognize the runtime error cases
      ; because the lowest level subscripts 1 - 3 are missing
      IF %ZNCmd'="READ" goto multiWW
      USE %ZNDev:(SOCKET=%ZNSock:EXCEPTION="GOTO multiXS")
      SET %Z1=0
      readSock READ %ZNData:%ZNTimeS
      IF $LENGTH(%ZNData)=0 goto readSock
      ; assert: the command is READ, and the data length read > 0. 
      ; we probably have good data now.
      SET ^OPENEMED("OEMEDSRV",$job,"totalreads")=%Z0,%Z1=%Z1+1
      SET ^OPENEMED("OEMEDSRV",$job,%Z1,"KEY")=$KEY
      SET ^OPENEMED("OEMEDSRV",$job,%Z1,"ZA")=$ZA
      SET ^OPENEMED("OEMEDSRV",$job,%Z1,"ZB")=$ZB
      SET ^OPENEMED("OEMEDSRV",$job,%Z1,"ZC")=$ZC
      ;if we don't find any commands, then just read the next line
      S %ZNCmdFound=0
      IF %ZNData["get" S %ZNCmdFound=1
      IF %ZNData["set" S %ZNCmdFound=1
      IF %ZNData["kil" S %ZNCmdFound=1
      IF %ZNData["order" S %ZNCmdFound=1
      IF %ZNData["seq" S %ZNCmdFound=1
      IF '%ZNCmdFound goto readSock
      IF $LENGTH(%ZNData)>80 S %ZNData=$EXTRACT($LENGTH(%ZNData)-80,$LENGTH(%ZNData))
      SET ^OPENEMED("OEMEDSRV",$job,%Z1,"ZNData")=%ZNData
      SET ^OPENEMED("OEMEDSRV",$job,%Z1,"DEVICE")=$DEVICE
      SET ^OPENEMED("OEMEDSRV",$job,%Z1,"KEY")=$KEY
      SET ^OPENEMED("OEMEDSRV",$job,%Z1,"LENGTH")=$LENGTH(%ZNData)
      ;
      ; If an error occurs during READ, close this socket, and record the
      ; device status. $DEVICE will be a non-zero valie if an error occurred.
      multiXS IF $DEVICE do  goto multiWW
      . CLOSE %ZNDev:(SOCKET=%ZNSock)
      . set ^OPENEMED("OEMEDSRV",$job,%Z1,"SOCKSTATUS")="socket closed = "_%ZNSock
      . ZSHOW "D":^OPENEMED("OEMEDSRV",$job,%Z1,"KEY")
      . kill %ZNSock(%ZNSock)
      ;
      ; If terminator is not associated with db operation, ignore
      ;IF $KEY'=$CHAR(13,10) WRITE "cme "_%ZNData_$C(27,95) goto multiWW
      ;IF $KEY=$CHAR(27,95) DO  goto multiWW
      ;. SET ^OPENEMED("OEMEDSRV",$job,1000)="end of commands reached"
      ;. WRITE "jumping to multiWW"_$CHAR(27,95)
      IF $KEY'=$CHAR(13,10),$KEY'=$CHAR(27,95) DO  goto multiWW
      . SET ^OPENEMED("OEMEDSRV",$job,%Z1,"TERMINATORERROR")="not delimiter, KEY="_$KEY
      . CLOSE %ZNDev:(SOCKET=%ZNSock)
      . ZSHOW "D"
      ;
      ; set the terminator that is sent back to the client.
      SET %ZNResTrm=$KEY
      set %ZNSock(%ZNSock)=%ZNSock(%ZNSock)+1
      set ^OPENEMED("OEMEDSRV",$job,%Z1,"SOCK")=%ZNSock(%ZNSock)
      set %ZNCmd=$piece(%ZNData," "),%ZNData=$piece(%ZNData," ",2,32767)
      set ^OPENEMED("OEMEDSRV",$job,%Z1,"COMMAND")=%ZNCmd
      set ^OPENEMED("OEMEDSRV",$job,%Z1,"DATA")=%ZNData
      ; Reply, WRITE ! appends FIRST delimiter ($CHAR(13,10), and flushes data
      multiEr1 if %ZNCmd="get" DO  goto readSock
      . WRITE "val ",@%ZNData,%ZNResTrm,!
      . IF %ZNResTrm=$CHAR(27,95) DO  goto multiWW
      . . CLOSE %ZNDev:(SOCKET=%ZNSock)
      . . set ^OPENEMED("OEMEDSRV",$job,%Z1,"SOCKSTATUS")="socket closed (get) = "_%ZNSock
      . . ZSHOW "D"
      ; may add to above IF statement later: . . kill %ZNSock(%ZNSock)
      multiEr2 if %ZNCmd="set" DO  goto readSock
      . set @%ZNData
      . WRITE "ok",%ZNResTrm,!
      . IF %ZNResTrm=$CHAR(27,95) DO  goto multiWW
      . . CLOSE %ZNDev:(SOCKET=%ZNSock)
      . . SET ^OPENEMED("OEMEDSRV",$job,%Z1,"SOCKSTATUS")="socket closed (set) = "_%ZNSock
      . . ZSHOW "D"
      multiEr3 if %ZNCmd="kil" DO  goto readSock
      . kill @%ZNData
      . WRITE "ok",%ZNResTrm,!
      . IF %ZNResTrm=$CHAR(27,95) DO  goto multiWW
      . . CLOSE %ZNDev:(SOCKET=%ZNSock)
      . . SET ^OPENEMED("OEMEDSRV",$job,%Z1,"SOCKSTATUS")="socket closed (kil) = "_%ZNSock
      . . ZSHOW "D"
      multiEr4 if %ZNCmd="order" DO  goto readSock
      . S x="" F  S x=$ORDER(@%ZNData@(x)) Q:x=""  W x,!
      . WRITE "eol",%ZNResTrm,!
      . IF %ZNResTrm=$CHAR(27,95) DO  goto multiWW
      . . CLOSE %ZNDev:(SOCKET=%ZNSock)
      . . SET ^OPENEMED("OEMEDSRV",$job,%Z1,"SOCKSTATUS")="socket closed (order) = "_%ZNSock
      . . ZSHOW "D"
      multiEr5 if %ZNCmd="seq" DO  goto readSock
      . S ^OPENEMED("OEMEDSRV",$job,%Z0,"seq")="called"
      . S %ZNRetVal=^OPENEMED("SEQUENCE")
      . WRITE "seq "_%ZNRetVal,%ZNResTrm,!
      . S ^OPENEMED("SEQUENCE")=^OPENEMED("SEQUENCE")+1
      . IF %ZNResTrm=$CHAR(27,95) DO  goto multiWW
      . . CLOSE %ZNDev:(SOCKET=%ZNSock)
      . . SET ^OPENEMED("OEMEDSRV",$job,%Z1,"SOCKSTATUS")="socket closed (seq) = "_%ZNSock
      . . ZSHOW "D"
      WRITE "dbe "_%ZNData,%ZNResTrm
      IF %ZNResTrm=$CHAR(27,95) DO  goto multiWW
      . CLOSE %ZNDev:(SOCKET=%ZNSock)
      . SET ^OPENEMED("OEMEDSRV",$job,%Z1,"SOCKSTATUS")="socket closed = "_%ZNSock
      . ZSHOW "D"
      goto readSock
      ;
      ; multiC - close device before quiting
      ;
      multiC CLOSE %ZNDev
      ;
      ; multiQ - just QUIT
      ;
      multiQ QUIT
      ;
      ; singleX - M runtime exception
      ;
      ; If not at one of the multiErN-lines, then "normal" error: close and quit
      ; Else error while executing request from client: reply and retry
      multiX if $ZSTATUS'["multiEr" do
      . set ^OPENEMED("OEMEDSRV",$job)="-9,"_$ZSTATUS
      . ZGOTO %ZNLevel:multiC
      set ^OPENEMED("OEMEDSRV",$job,%Z0,9)=$ZSTATUS
      WRITE "xcpt "_$ZSTATUS_$CHAR(27,95) ;,#
      goto readSock
      =========== END OF CODE =============

       
    • I got this code working with a few more changes.  I've sent the file to fscwitte, and I'll be distributing it with the OpenEMed distribution (sourceforge.net/projects/openmed)

      Richard