; No claim of copyright is made with regard to this code.
; This is example code for explanatory purposes, and is not intended
; for production use.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This program demonstrates a use of $KEY and $ZKEY in a basic socket
; I/O setup. It launches two jobs: a server process which opens a
; listening socket and a client process which makes five connections
; to the server.  The server sends a message to each connection
; socket.  Even-numbered client sockets read the message partially but
; do not send a response back to the server.  Odd-numbered client
; sockets receive the full message and respond to the server with the
; message "Ok.".  The server reads two characters (but the client
; sends three) and $ZKEY shows sockets with unread characters.
;
;
;                     +---------+ <-No response +----------+
;      +--------------+ handle1 +---------------+ client1  |
;      |              +---------+   Full msg -> +----------+
;      |
;      +--------------+---------+ <- "Ok."      +----------+
;      |              | handle2 +---------------+ client2  |
; +----+---+          +---------+Partial msg -> +----------+
; |        |
; | Server |          +---------+ <-No response +----------+
; |        +----------+ handle3 +---------------+ client3  |
; |        |          +---------+  Full msg ->  +----------+
; +----+---+
;      |              +---------+ <- "Ok."      +----------+
;      +--------------+ handle4 +---------------+ client4  |
;      |              +---------+ Partial msg-> +----------+
;      |
;      |              +---------+<- No response +----------+
;      +--------------+ handle5 +---------------+ client5  |
;                     +---------+  Full msg ->  +----------+
;
;+------------------------------++-------------------------+
;        server job                    client job
;
;
; The program displays the stderr and stdout of server and client jobs
; and produces a report such as:
;
; $ mumps -r sockexamplemulti3
; At APR 10, 2014 06:30:31 waiting for server job 5972 & client job 5974
; At APR 10, 2014 06:30:34 waiting for server job 5972 & client job 5974
; At APR 10, 2014 06:30:37 waiting for server job 5972 & client job 5974
; At APR 10, 2014 06:30:40 waiting for server job 5972 & client job 5974
; At APR 10, 2014 06:30:43 waiting for server job 5972 & client job 5974
; At APR 10, 2014 06:30:46 waiting for server job 5972 & client job 5974
; At APR 10, 2014 06:30:49 waiting for server job 5972 & client job 5974
;
; Server stderr:
;
; Server stdout:
;** tcpserver is getting ready to start.**
; APR 10, 2014 06:30:31 Open serverport 10000 successful, $key=LISTENING|server|10000
; APR 10, 2014 06:30:31 Connection established, socket=h13971258310, ip=::ffff:127.0.0.1
; APR 10, 2014 06:30:31 Message successfully written to client.
; APR 10, 2014 06:30:31 Connection established, socket=h13971258311, ip=::ffff:127.0.0.1
; APR 10, 2014 06:30:41 Message successfully written to client.
; APR 10, 2014 06:30:41 Connection established, socket=h13971258410, ip=::ffff:127.0.0.1
; APR 10, 2014 06:30:41 Message successfully written to client.
; APR 10, 2014 06:30:41 Connection established, socket=h13971258411, ip=::ffff:127.0.0.1
; APR 10, 2014 06:30:51 Message successfully written to client.
; APR 10, 2014 06:30:51 Connection established, socket=h13971258510, ip=::ffff:127.0.0.1
; APR 10, 2014 06:30:51 Message successfully written to client.
; listensocket("h13971258310","read")="Partial"
; listensocket("h13971258410","read")="Partial"
; listensocket("h13971258510","read")="Partial"
;
; Client stderr:
;
; Client stdout:
; connectionsock("client1","read")="Full"
; connectionsock("client2","read")="Partial"
; connectionsock("client3","read")="Full"
; connectionsock("client4","read")="Partial"
; connectionsock("client5","read")="Full"
;
;
; TCP reads default to a timeout of 7 seconds. Owing to the nature of
; TCP/IP communication, the order of packets is guaranteed but the
; timing is not.  Therefore, the program reports "Timeout" for all
; connection sockets whose reads time out.
;
sockexamplemulti3
	new client,err1,err2,line,out1,out2,server
	set err1=$text(+0)_"_"_$job_"_server.mje"
	set out1=$extract(err1,1,$length(err1)-1)_"o"
	set cmd="server(,,,):(ERROR="""_err1_""":OUTPUT="""_out1_""")"
	job @cmd
	set server=$zjob
	set err2=$text(+0)_"_"_$job_"_client.mje"
	set out2=$extract(err2,1,$length(err2)-1)_"o"
	set cmd="client(,,):(ERROR="""_err2_""":OUTPUT="""_out2_""")"
	job @cmd
	set client=$zjob
	for  quit:$zsigproc(server,0)&$zsigproc(client,0)  write "At ",$zdate($horolog,"MON DD, YYYY 24:60:SS")," waiting for server job ",server," & client job ",client,! hang 3
	do showfile(err1,"Server stderr:")
	do showfile(out1,"Server stdout:")
	do showfile(err2,"Client stderr:")
	do showfile(out2,"Client stdout:")
	quit

client(portno,connectnum,timeout)
	new connectionsock,delim,detach,host,i,report,sockdev,socksel,val,x,y,zkey
	set:'($data(portno)#10) portno=$piece($horolog,",",1)#63487+2048
	set:'($data(connectnum)#10) connectnum=5
	set:'($data(timeout)#10) timeout=7
	set delim=$char(10) ; line feed
	set detach=""
	set host="localhost"
	set sockdev="client"
	set val=""
	for i=1:1:connectnum do ; open multiple socket connections to the server
	. set socksel="client"_i
	. open sockdev:(connect=host_":"_portno_":TCP":delim=delim:attach=socksel:exception="do ioerr(0,"_portno_")":ioerror="t"):timeout:"socket"
	. do:'$test ioerr(1,portno)
	. kill x,y
	. use sockdev:(socket=socksel)
	. read x:timeout ; discard empty line from write !,msg,! by server
	. if i#2 do  ; argumentless DO protects $TEST from changes due to the timeout on the READ
	. . read y:timeout ; read of write !,msg,! by server - y has msg
	. . write "OK.",! ; respond to server with "OK."
	. else  do
	. . read y#3:timeout ; partial read of write !,msg,! by server - y has 3 characters of msg
	. set zkey=$zkey,connectionsock(socksel,"read")=$select($length(y):"Full",1:"Timeout")
	set totalpartialread=$length(zkey,"READ")-1 ; find the number of sockets which have partially read data
	for i=1:1:totalpartialread set connectionsock($piece($piece(zkey,"READ",i+1),"|",2),"read")="Partial"
	for i=1:1:connectnum close sockdev:(socket="client"_i)
	zwrite connectionsock
	quit

; log error and terminate process
ioerr(code,port,opt)
	new msg,stack
	set msg=$text(ioerrs+$get(code))
	use $principal ; since process will terminate, no need to save & restore $io
 	zshow "s":stack
	do log("At "_stack("S",2)_" "_$piece(msg,";",2)_" port "_$get(port)_$select($data(opt)#10:$piece(msg,";",3)_opt,1:""))
	zhalt code

; text of error messages
ioerrs	;unable to OPEN
	;connection failure; timeout=

; display a message on $principal
log(str)
	new previo
	set previo=$IO
	use $principal
	write $zdate($horolog,"MON DD, YYYY 24:60:SS")," ",str,!
	use previo
	quit

; entry point for the server code
server(portno,msg,listenqueue,timeout)
	new childsock,delim,i,ip,key,parentsock,listensock,start,sockdev,totalpartialread,x,x1,zkey
	set start=$horolog
	set:'($data(portno)#10) portno=$piece(start,",",1)#63487+2048
	set:'($data(msg)#10) msg=$zversion_" server "_$job_" started at "_$zdate(start,"MON DD, YYYY 24:60:SS")
	set:'($data(listenqueue)#10) listenqueue=5
	set:'($data(timeout)#10) timeout=7
	set delim=$char(10) ; line feed
	set sockdev="server"
	use $principal write "** tcpserver is getting ready to start.**",!
	open sockdev:(listen=portno_":tcp":attach="server":delimiter=delim:exception="do ioerr(0,"_portno_")":ioerror="t")::"socket"
	use sockdev
	write:listenqueue>1 /listen(listenqueue) ; if so specified, increase listen queue size from default of 1
	set key=$key
	do log("Open serverport "_portno_" successful, $key="_key)
	set parentsock=$piece(key,"|",2)
	set totalpartialread=0
	for i=1:1:5  do
	. write /wait(timeout)
	. set key=$key
	. if $piece(key,"|",1)="CONNECT" do
	. . set childsock=$piece(key,"|",2),ip=$piece(key,"|",3)
	. . do log("Connection established, socket="_childsock_", ip="_ip)
	. . use sockdev:(socket=childsock)
	. . write !,msg,! set finalresult="Message successfully written to client."
	. . read x#2:timeout ; simulate a partial read - client sends three characters back but server reads only two
	. . set zkey=$zkey do log(finalresult)
	. else  do ioerr(1,portno,timeout) ; connection failure; log error and terminate
	kill listensock
	set x1=zkey ; find the number of sockets which have partially read data
	set totalpartialread=$length(x1,"READ")-1
	for i=1:1:totalpartialread do  
	. set sockhandlepar=$piece($piece(zkey,";",i),"|",2)
	. set listensock(sockhandlepar,"read")="Partial"
	. use sockdev:(detach=sockhandlepar) ; detach the socket with partially read data
	. set jobprocessparam="handlepartialread:(output="""_sockhandlepar_".mjo"":input="_"""SOCKET:"_sockhandlepar_""""_")"
	. job @jobprocessparam ; fork a process to handle the detached socket
	use $principal
	zwrite:totalpartialread>1 listensock
	close sockdev
	quit

; read & output a file, then delete it
showfile(file,title)
	new previo
	set previo=$io
	write !,title,!
	open file for  use file read line quit:$zeof  use previo write line,!
	use previo close file:delete
	quit

; read the remaining data on the partially read socket
handlepartialread
	use $principal:delim=$char(10) ; delimiters are not passed to the job
	read x ; odd-numbered client sockets sent three characters ("Ok.") but server reads two ("Ok"). x has the remaining ".".
	write x,!
	quit
	

