Our intial interest came about after helping one of our clients update some software that talks to a mainframe, the original code needed some attention and we needed a way to ensure what we had developed would send the correct information to the mainframe (it had to be converted to ASCII). This required a test server that we could send the data to and from without the mainframe being involved. the following code is the start of what we hope will become a very good base for sending and receiving data to and from the IBM i using ASCII. The initial code will simply take a character string and convert it from ASCII to EBCDIC before sending it to a message queue and then send a ASCII based response to the client.
Before we can run the Server code we will need a few environment objects which will allow the jobs to run in batch via a SBMJOB command. We will create all of the objects in the OSPGM library, first thing we will need is a subsystem description:
CRTSBSD SBSD(OSPGM/OSSBS) POOLS((1 *BASE))Next a Job Queue which is where all of the submitted jobs will run.
CRTJOBQ JOBQ(OSPGM/OSJOBQ) TEXT(‘Job Queue for OSSBS jobs’)We then add the job queue to the subsystem description, this is how the active jobs show up under the subsystem description when you run a WRKACTJOB command.
ADDJOBQE SBSD(OSPGM/OSSBS) JOBQ(OSPGM/OSJOBQ)Next we need to route the jobs so they can be started via the QCMD program, but before we can add the routing entry we need to create the class. All of this allows you to finely tune your jobs as they run, for us the defaults work just fine.
CRTCLS CLS(OSPGM/OSCLS) TEXT(‘OS Class’)As with most jobs you will want to create a Job Description which allows control over how the jobs starts and runs, again we just created a simple *JOBD which sets the job queue to be used plus sets the initial library list to our OSPGM library.
ADDRTGE SBSD(OSPGM/OSSBS) SEQNBR(1) CMPVAL(*ANY) PGM(QCMD) CLS(OSPGM/OSCLS)
CRTJOBD JOBD(OSPGM/OSSVR) JOBQ(OSPGM/OSJOBQ) TEXT(‘OS Server Job Description’) INLLIBL(OSPGM)As part of the program we will generate for the server side we will need to stop the running jobs, for this we will use a Data Queue where messages can be sent and acted upon by the Server program. We use a keyed Data Queue as we can identify the type of request (will be useful in the future) and filter the actions based on that request.
CRTDTAQ DTAQ(OSPGM/SVRCTLQ) MAXLEN(256) SEQ(*KEYED) KEYLEN(4) AUTORCL(*YES) TEXT(‘Server Control data queue’)You can go ahead and start the subsystem at this time.
STRSBS SBSD(OSPGM/OSSBS)We will be creating a couple of programs that run as the server plus a menu which will interface with the programs, the build of the Server and Worker program will use the functionality we have created in the Service programs so make sure you have those available before trying to create these programs. The menu we are going to create is a UIM menu, this will be added to a member called SVRMAIN in the QUIMSRC file. This is the first time we have used the QUIMSRC file so the following command will create it for you.
CRTSRCPF FILE(OSLIB/QUIMSRC) RCDLEN(150) MBR(SVRMENU) TEXT(‘Server Source OSPGM’)Now add the following code to the member.
.* .* Copyright (c) 2018 Chris Hird .* All rights reserved. .* .* Redistribution and use in source and binary forms, with or without .* modification, are permitted provided that the following conditions .* are met: .* 1. Redistributions of source code must retain the above copyright .* notice, this list of conditions and the following disclaimer. .* 2. 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. .* .* Disclaimer : .* This code is distributed in the hope that it will be useful, .* but WITHOUT ANY WARRANTY; without even the implied warranty of .* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. .* :PNLGRP HLPSHELF=list DFTMSGF=osmsgf SUBMSGF=osmsgf. .* :COPYR. (c) Copyright Chris Hird 2018 .* .* ----------------------------------------------------------------- .* UIM Z-variable to be used as the panel identifier .* ----------------------------------------------------------------- :VAR NAME=ZMENU. .* .* ----------------------------------------------------------------- .* Define keys for the menu .* ----------------------------------------------------------------- :KEYL NAME=menukeys HELP=keyl. :KEYI KEY=F1 HELP=helpf1 ACTION=HELP. :KEYI KEY=F3 HELP=exit ACTION='EXIT SET' VARUPD=NO. F3=Exit :KEYI KEY=F4 HELP=prompt ACTION=PROMPT. F4=Prompt :KEYI KEY=F9 HELP=retrieve ACTION=RETRIEVE. F9=Retrieve :KEYI KEY=F12 HELP=cancel ACTION='CANCEL SET' VARUPD=NO. F12=Cancel :KEYI KEY=F24 HELP=morekeys ACTION=MOREKEYS. F24=More keys :KEYI KEY=ENTER HELP=enter ACTION=ENTER. :KEYI KEY=HELP HELP=help ACTION=HELP. :KEYI KEY=HOME HELP=home ACTION=HOME. :KEYI KEY=PAGEDOWN HELP=pagedown ACTION=PAGEDOWN. :KEYI KEY=PAGEUP HELP=pageup ACTION=PAGEUP. :KEYI KEY=PRINT HELP=print ACTION=PRINT. :EKEYL. .* .* ----------------------------------------------------------------- .* Define Menu panel .* ----------------------------------------------------------------- :PANEL NAME=srvmenu HELP='menu/help' KEYL=menukeys ENTER='MSG CPD9817 QCPFMSG' PANELID=ZMENU TOPSEP=SYSNAM. Server Menu .* .* ------------------------------------- .* Define the menu area .* ------------------------------------- :MENU DEPTH='*' SCROLL=NO BOTSEP=SPACE. :TOPINST.Select one of the following: .* .* ------------------------------------- .* Specify the action to be taken for each option .* ------------------------------------- .* :MENUI OPTION=1 ACTION='CMD SBMJOB CMD(CALL PGM(OSPGM/TESTSVR)) JOB(MYSERVER) JOBD(OSPGM/OSSVR) JOBQ(*JOBD)' HELP='menu/option1'. Start Server .* :MENUI OPTION=2 ACTION='CMD CALL SVRSTOP' HELP='menu/option2'. End Server .* :MENUI OPTION=5 ACTION='CMD WRKACTJOB SBS(OSSBS)' HELP='menu/option5'. Display Server jobs .* :MENUI OPTION=6 ACTION='CMD DSPMSG OSMSGQ' HELP='menu/option6'. Display messages .* :MENUI OPTION=90 ACTION='CMD SIGNOFF' HELP='menu/option90'. Signoff .* :EMENU. .* .* ------------------------------------- .* Use a command line and allow commands and option numbers .* ------------------------------------- :CMDLINE SIZE=LONG. Selection or command .* :EPANEL. .* .* .* ----------------------------------------------------------------- .* Define help modules for the menu panel .* ----------------------------------------------------------------- :HELP NAME=keyl. Function Keys - Help :XH3.Function keys :EHELP. .* :HELP NAME=helpf1. :PARML. :PT.F1=Help :PD. Provides additional information about using the display or a specific field on the display. :EPARML. :EHELP. .* :HELP NAME=exit. :PARML. :PT.F3=Exit :PD. Ends the current task and returns to the display from which the task was started. :EPARML. :EHELP. .* :HELP NAME=prompt. :PARML. :PT.F4=Prompt :PD. Provides assistance in entering or selecting a command. :EPARML. :EHELP. .* :HELP NAME=retrieve. :PARML. :PT.F9=Retrieve :PD. Displays the last command you entered on the command line and any parameters you included. Pressing this key once, shows the last command you ran. Pressing this key twice, shows the command you ran before that and so on. :EPARML. :EHELP. .* :HELP NAME=cancel. :PARML. :PT.F12=Cancel :PD. Returns to the previous menu or display. :EPARML. :EHELP. .* :HELP NAME=morekeys. :PARML. :PT.F24=More keys :PD. Shows additional function keys. :EPARML. :EHELP. .* :HELP NAME=enter. :PARML. :PT.Enter :PD. Submits information on the display for processing. :EPARML. :EHELP. .* :HELP NAME=help. :PARML. :PT.Help :PD. Provides additional information about using the display. :EPARML. :EHELP. .* :HELP NAME=home. :PARML. :PT.Home :PD. Goes to the menu that was shown after you signed on the system. This menu is either the initial menu defined in your user profile or the menu you requested from the Sign-On display. :EPARML. :EHELP. .* :HELP NAME=pagedown. :PARML. :PT.Page Down (Roll Up) :PD. Moves forward to show additional information for this display. :EPARML. :EHELP. .* :HELP NAME=pageup. :PARML. :PT.Page Up (Roll Down) :PD. Moves backward to show additional information for this display. :EPARML. :EHELP. .* :HELP NAME=print. :PARML. :PT.Print :PD. Prints information currently shown on the display. :EPARML. :EHELP. .* :HELP NAME='menu/help'. SaveFile Replication Main Menu - Help :P. Provides access to all options related the SVF Replication Process. :XH3. How to Use a Menu :P. To select a menu option, type the option number and press Enter. :P. To run a command, type the command and press Enter. For assistance in selecting a command, press F4 (Prompt) without typing anything. For assistance in entering a command, type the command and press F4 (Prompt). To see a previous command you entered, press F9 (Retrieve). :P. To go to another menu, use the Go to Menu (GO) command. Type GO followed by the menu ID, then press the Enter key. The menu ID is shown in the upper left corner of the menu.For assistance in entering the GO command, type GO and press F4 (Prompt). If you do not know the entire menu name you can use a generic name. :EHELP. .* :HELP NAME='menu/option1'. Option 1 - Help :XH3.Option 1. Start Server :P. Select this option to start the Server ready to receive data. :EHELP. .* :HELP NAME='menu/option2'. Option 2 - Help :XH3.Option 2. End Server :P. Select this option to end the server and the worker jobs. :EHELP. .* :HELP NAME='menu/option5'. Option 5 - Help :XH3.Option 5. WRKACTJOB :P. Display the active jobs in OSSBS subsystem. :EHELP. .* :HELP NAME='menu/option6'. Option 6 - Help :XH3.Option 6. Display messages :P. DPisplay the messages logged to the message queue. :EHELP. .* :HELP NAME='menu/option90'. Option 90 - Help :XH3.Option 90. Signoff :P. Select this option to SIGNOFF the current session. :EHELP. .* .* ----------------------------------------------------------------- .* End of menu source .* ----------------------------------------------------------------- :EPNLGRP.As this post is about ‘C’ programming we are not going to expand too much into what the above code does and why, the important thing is that using UIM ensures that any output that is generated is complient with the standards. Should you wish to run this through the modernization tools provided in the market place it will result in very few conflicts if any when displaying the interface (depends on the quality of the conversion tool). This produces a menu that allows you to start and stop the Server programs plus view any messages sent to the OSPGM/OSMSGQ message queue. Take note that we are simply running a SBMJOB command for option 1, in the future we will change that to ensure the subsystem is running as part of the start up request.
The next program sends a stop request to the controlling data queue which the Server job will listen to. Again it will be improved in the future adding error management for the data queue request as Data Queue API’s do not have the Error Code parameter many of the other API’s do. More on that in a later post but for now here is the code you need, simply create a new member called SVRSTOP in the OSLIB/QCSRC file and add the following code.
/** * * Revision log.: * Date Author Revision * 2018 Chris H 1.0 * * Copyright (C) <2018> <Chris Hird> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * with this program; if not, write to the Free Software Foundation, * Inc.,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * You can contact the author via Email chrish@shieldadvanced.com */ #include <stdio.h> // stdio header #include <string.h> // string header #include <stdlib.h> // stdlib header #include <decimal.h> // decimal header #include <qsnddtaq.h> // send dataq header void main(int argc, char **argv) { decimal(3,0) KeyLength = 4.0d; // length of key char Key[4] = "0000"; // key used retrieval char QueueData[7] = "STOP "; // Message to quit char DQName[10] = "SVRCTLQ "; // data queue name char DQLib[10] = "OSPGM "; // data queue lib decimal(5,0) Data_Len = 7.0d; // data length QSNDDTAQ(DQName, DQLib, Data_Len, QueueData, KeyLength, &Key); exit(0); }First we need a Server program, this is where the initial start is performed. It will then spawn the worker jobs where the communication between the client and the IBM i will be performed. The stop message will be sent to the data queue this program is watching so when a stop message is received it will go through and send a kill signal to all of the worker jobs it spawned.
Add a new member QCSRC/TESTSVR and add the following code.
/** * * Revision log.: * Date Author Revision * 2018 Chris H 1.0 * * Copyright (C) <2018> <Chris Hird> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * with this program; if not, write to the Free Software Foundation, * Inc.,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * You can contact the author via Email chrish@shieldadvanced.com */ #include <H/COMMON> // common header #include <H/MSGFUNC> // message functions #include <qrcvdtaq.h> // Receive Data Q Msg #include <qclrdtaq.h> // Clear Data Q Msgs #include <sys/socket.h> // socket #include <netinet/in.h> // net addr #include <spawn.h> // spawn job #include <errno.h> // error number #include <signal.h> // Exception signals typedef _Packed struct pid_list_x{ int pid_num[50]; }pid_list_t; #define _QSIZE 256 #define _CPYRGHT "Copyright @ Shield Advanced Solutions Ltd 1997-2018" #pragma comment(copyright,_CPYRGHT) main(int argc, char *argv[]) { int on = 1; // on flag int result = 0; // result flag int stop = 0; // stop flag int i, pid,rc,j; // counters int listen_sd, accept_sd = 0; // socket descriptors int num_srv = 1; // number of servers int Server_Port = 12345; // server port int type = 0; // Switch key decimal(5,0) DataLength = 0.0d; // Number of bytes returned decimal(5,0) WaitTime = -1.0d; // wait for data <0 = forever decimal(3,0) SInfLength = 9.0d; // length of Sender inf decimal(3,0) KeyLength = 4.0d; // length of key char *spawn_argv[3]; // Spawn arg char *spawn_envp[1]; // Spawn Environment char *recptr; // pointer to data char buffer[80]; // Buffer char DQKey[4] = "0000"; // key data used for retvl char QueueData[_QSIZE]; // Data from Data queue char DQueue1[10] = "SVRCTLQ "; // Master Q char DQLib[10] = "OSPGM "; // Master Q Lib char msg_dta[100] = {'\0'}; // msg buffer char SpawnStr[50]; // spawn string char Key[5]; // Switch Key pid_list_t pid_list; // Process List struct pid_list_t ss_pid_list; // Process List struct Qmhq_Sender_Information_t SInfo; // Sender Inf struct struct inheritance inherit; // inheritance Struct struct sockaddr_in addr; // socket struct Os_EC_t Error_Code = {0}; // Error code data Error_Code.EC.Bytes_Provided = _ERR_REC; // set up the worker program spawn string strcpy(SpawnStr,"/QSYS.LIB/OSPGM.LIB/WORKER.PGM"); // Clear out any existing stop messages in control queue QCLRDTAQ(DQueue1, DQLib, "EQ", KeyLength, DQKey, &Error_Code); if(Error_Code.EC.Bytes_Available) { sprintf(msg_dta,"Unable to clear the data queue %.7s",Error_Code.EC.Exception_Id); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); } // Set up the listening sockets Non Secure listen_sd = socket(AF_INET, SOCK_STREAM, 0); if(listen_sd < 0) { sprintf(msg_dta," socket() failed %s",strerror(errno)); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); close(accept_sd); exit(-1); } setsockopt(listen_sd,SOL_SOCKET,SO_REUSEADDR,(char *)&on,sizeof(on)); setsockopt(listen_sd,SOL_SOCKET,SO_RCVBUF,CHAR_32K,sizeof(CHAR_32K)); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(Server_Port); rc = bind(listen_sd,(struct sockaddr *) &addr, sizeof(addr)); if(rc < 0) { sprintf(msg_dta," bind() failed %s",strerror(errno)); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); close(listen_sd); exit(-1); } rc = listen(listen_sd, 5); if(rc < 0) { sprintf(msg_dta," bind() failed %s",strerror(errno)); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); close(listen_sd); exit(-1); } memset(&inherit, 0, sizeof(inherit)); sprintf(buffer, "%d", listen_sd); spawn_argv[0] = ""; spawn_argv[1] = buffer; spawn_argv[2] = NULL; spawn_envp[0] = NULL; // load the listening jobs Non Secure for(i = 0; i < num_srv; i++) { pid = spawn(SpawnStr,listen_sd + 1, NULL, &inherit,spawn_argv, spawn_envp); if(pid < 0) { sprintf(msg_dta," spawn() failed %s",strerror(errno)); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); close(listen_sd); exit(-1); } pid_list.pid_num[i] = pid; } // Check for signal to end do { recptr = QueueData; QRCVDTAQ(DQueue1, DQLib, &DataLength, &QueueData, WaitTime, "LE", KeyLength, &DQKey, SInfLength, &SInfo); memset(Key,'\0',5); memcpy(Key,DQKey,4); type = atoi(Key); switch(type) { case 0 : { // End the process */ for(i = 0; i < num_srv; i++) { kill(pid_list.pid_num[i], SIGTERM); } stop = 1; break; } Default : { // do nothing break; } } }while(!stop); close(listen_sd); exit(0); }The above program sets up the socket which is to be listened on and sets a number of options for that socket before binding to it. The worker jobs are actually programs we are going to create next and will be called WORKER. The program sets up the call information plus any parameters that are to be sent to the programs, in this case the socket is an important parameter. We then loop through and load the worker jobs storing the pid for each job so we can send it a kill signal once we receive the relevant data queue entry. Now the program sits on the data queue looking for the stop message, when it is received it will loop through the pid’s for the jobs it spawned and send the kill signal to the.
Finally we have the code which is where all of the real work is done, this is where the actual conversation with the remote client will take place. Add a new member QCSRC/WORKER and add the following code.
/** * * Revision log.: * Date Author Revision * 2018 Chris H 1.0 * * Copyright (C) <2018> <Chris Hird> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * with this program; if not, write to the Free Software Foundation, * Inc.,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * You can contact the author via Email chrish@shieldadvanced.com */ #include <H/MSGFUNC> // message functions #include <H/COMMON> // common header #include <netdb.h> // Socket DB #include <sys/socket.h> // Sockets #include <netinet/in.h> // Socket net #include <arpa/inet.h> // ARPA structs #include <errno.h> // Error return header #include <netdb.h> // net DB header #include <sys/errno.h> // sys error header #include <arpa/inet.h> // inet header #include <signal.h> // signal funcs #include <except.h> // exception funcs #include <iconv.h> // conversion header #include <qtqiconv.h> // iconv header #define _QSIZE 256 #define _CPYRGHT "Copyright @ Shield Advanced Solutions Ltd 1997-2018" #pragma comment(copyright,_CPYRGHT) // Signal catcher void catcher(int sig) { // you can do some messaging etc in this function, it is called // when the kill signal is received from the TESTSVR program exit(0); } int main(int argc, char **argv) { int listen_sd, accept_sd = 0; // file descriptors int rc = 0; // return codes int ret = 0; // return vals int Stop = 0; // stop flag int i = 0; // counter int j = 0; // counter int offset = 0; // offset marker int total_bytes = 0; // counter int bytes_processed = 0; // counter size_t insz; // input len size_t outsz = _32K; // outbuf size char recv_buf[_32K]; // recv buffer char convbuf[_32K]; // conversion buffer char msg_dta[_MAX_MSG]; // message buffer char *out_ptr; // buffer ptr char *in_ptr; // buffer ptr struct sockaddr_in addr; // socket struct struct sigaction sigact; // Signal Action Struct QtqCode_T jobCode = {0,0,0,0,0,0}; // (Job) CCSID to struct QtqCode_T asciiCode = {819,0,0,0,0,0}; // (ASCII) CCSID from struct iconv_t a_e_ccsid; // convert table struct iconv_t e_a_ccsid; // convert table struct // Set up the signal handler sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; sigact.sa_handler = catcher; sigaction(SIGTERM, &sigact, NULL); // create the conversion tables // ASCII to EBCDIC a_e_ccsid = QtqIconvOpen(&jobCode,&asciiCode); if(a_e_ccsid.return_value == -1) { sprintf(msg_dta,"QtqIconvOpen Failed %s",strerror(errno)); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); exit(-1); } // EBCDIC to ASCII e_a_ccsid = QtqIconvOpen(&asciiCode,&jobCode); if(e_a_ccsid.return_value == -1) { iconv_close(a_e_ccsid); sprintf(msg_dta,"QtqIconvOpen Failed %s",strerror(errno)); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); exit(-1); } // connect to the socket passed as argv[1] listen_sd = atoi(argv[1]); do { accept_sd = accept(listen_sd, NULL, NULL); setsockopt(accept_sd,SOL_SOCKET,SO_RCVBUF,CHAR_32K,sizeof(CHAR_32K)); if(accept_sd < 0) { sprintf(msg_dta,"accept() failed",strerror(errno)); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); close(listen_sd); exit(-1); } memset(recv_buf,'\0',_32K); rc = recv(accept_sd, recv_buf, _32K, 0); sprintf(msg_dta,"Bytes received %d %s",rc,recv_buf); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); if(rc < 0) { sprintf(msg_dta,"recv() failed",strerror(errno)); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); close(listen_sd); close(accept_sd); exit(-1); } // convert the input memset(convbuf,'\0',_32K); in_ptr = recv_buf; out_ptr = convbuf; insz = rc; outsz = _32K; ret = (iconv(a_e_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz)); // send the data we just received to a message queue snd_msg("GEN0001",convbuf,strlen(convbuf)); // send a reply to the client sprintf(msg_dta,"Yep got that now closing"); in_ptr = msg_dta; out_ptr = convbuf; insz = strlen(msg_dta); outsz = _32K; ret = (iconv(e_a_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz)); rc = send(accept_sd,convbuf,strlen(msg_dta),0); close(accept_sd); } while(Stop == 0); }Before we go ahead and compile the programs lets take a look at what we are doing in this program. Once we have decalred all of the variables we need to set up the signal handler that will be caled whenever a signal is trapped by this program, remember we will stop the running processes by the Server Program sending a kill request on receipt of a specific data queue entry. The handler code (a function called catcher just before the main() statement) we have does not do a lot other than simply returning, we can add more to that later. The next few lines of code is where we will set up the conversion for the data this program will receive, we have already decided that this program will be sent requests in ASCII (ISO8859-1). A couple of things happen as part of the conversion set up, first we create the to and from code arrays (jobCode and acsiiCode) which define the CCSID’s that we need translating. we will pass these into the QtqIconvOpen API which will return a mapped table for doing the conversion, if it fails we exit as not much point in going any further at tha point. We end up with 2 tables, one to convert from ASCII to EBCDIC and one to convert the other way, we have named these accordingly.
The Server program will map the socket that is to be listed on, this program needs to set a listening state to that socket and then loop through a process of receive and respond against that socket. The worker program will issue an accept request which is actioned once the client has sent its request. Each request will be terminated by the worker program after it has sent its response, we could have the worker wait until a specific set of data is sent to signify that the client has finished the conversation but in this instance we are simply going to stop accepting any further data (close(accept_sd)).
When the data is received from the client we will convert it to EBCDIC and simply send it onto the message queue, we could do other things like check if its a command and run itr or maybe if required run it as an SQL query? In the future we intend to show how some of that can be done.
The following commands will create the required objects from the above code.
CRTMNU MENU(OSPGM/SVRMAIN) TYPE(*UIM) SRCFILE(OSLIB/QUIMSRC) SRCMBR(SVRMAIN)You should now have all of the objects required to start up and run the Server programs. You can use the SVRMAIN menu to start (option 1) the programs and show them running (option 5). If option 5 shows no running programs make sure you have started the subsystem.
CRTCMOD MODULE(OSLIB/SVRSTOP) SRCFILE(OSLIB/QCSRC) SRCMBR(SVRSTOP) OUTPUT(*PRINT) DBGVIEW(*SOURCE) TGTRLS(V7R1M0)
CRTPGM PGM(OSPGM/SVRSTOP) MODULE(OSLIB/SVRSTOP) BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)
CRTCMOD MODULE(OSLIB/TESTSVR) SRCFILE(OSLIB/QCSRC) SRCMBR(TESTSVR) OUTPUT(*PRINT) DBGVIEW(*SOURCE) TGTRLS(V7R1M0)
CRTPGM PGM(OSPGM/TESTSVR) MODULE(OSLIB/TESTSVR) BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)
Now to test the program actually works. We use Linux a lot so for us we can use the NetCat program (there is a Windows version but not sure how well maintained it is these days plus there are notes about it conflicting with the Anti Virus programs due to port scanning capabilities built in). We simply start a terminal session and issue the following commands:
nc sas4.shield.local 12345This will start the session between the client and the IBM i which is listening on port 12345. Next we will send the data which will be sent to the mesage queue on the IBM i
Hello from the Linux Box Hope you get this.On pressing enter you should see a response from the IBM i which states ‘Yep got that now closing’. Here is a screen shot of our terminal.
Linux webserver 4.13.16-2-pve #1 SMP PVE 4.13.16-47 (Mon, 9 Apr 2018 09:58:12 +0200) x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Thu Apr 26 14:20:24 2018 from 192.168.200.149 chrish@webserver:~$ nc sas4.shield.local 12345 Hello from the Linux Box Hope you get this. Yep got that now closingchrish@webserver:~$So when you look in the message queue on the IBM i you should now see a message like so.
Bytes received 44 çÁ%%?■ÃÊ?_■ÈÇÁ■<Ñ>ÍÌ■â?Ì■ç?øÁ■`?Í■ÅÁÈ■ÈÇÑË■■This is what we expect, the strange text is the actual ASCII characters we received which when you look at the Hex data correlates with the text string that was sent and the second message is after the conversion using the translation tables.
Hello from the Linux Box Hope you get this.■
Thats all it takes, we will continue to build on this in future posts so if you have any problems with the code to date get in touch and we will help figure out what is going wrong. In this post we have covered quite a bit, some of it may not make sense at first so please ensure you go through and understand what we do and why in the programs, this is going to be very important once we get into the future stages.
This post we have looked at.
- TCP/IP ports and using them to communicate
- Conversion for ASCII and EBCDIC
- Use of the spawn process to launch worker jobs
- Using signal handling to stop spawned jobs
- UIM menus
- Building a subsystem and setting it up to run jobs.
That’s all for todays post, any questions or concerns get in touch and we will try to help.
Chris..