Synthèse Sockets - Philippe Soriano Home Page
Transcription
Synthèse Sockets - Philippe Soriano Home Page
Programmation des Sockets Synthèse Sockets V1.1 Philippe Soriano Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ Préambule Par souci de simplification et de compréhension du texte, le mot "socket" qui synthétiquement en anglais, désigne un dispositif de transfert d'entrée et de sortie sur un réseau informatique, n'a pas été traduit Cette documentation est la synthèse en français du tutoriel de Scott Klement 1 traitant de l’utilisation des" sockets" en programmation réseau dans le langage RPG IV. Pré requis Le lecteur doit déjà connaître la programmation en RPG IV et notamment, l’utilisation des prototypes, des sous- procédures, des programmes de service et des pointeurs. Introduction à TCP et aux Sockets TCP/IP Concepts et Terminologie Un "socket "ou unité virtuelle est un point d’entrée/sortie dans le réseau de communication. Vous transférez des données dans un socket et vous récupérez les données à partir d’un socket. Les sockets peuvent aussi être utilisées pour accéder avec d’autres protocoles réseau comme IPX/SPX et Appletalk. Les APIs socket standards ont été développées à l’origine pour le monde Unix, mais elles ont été par la suite portées sur l‘OS/400 (API "Unix- type") et Windows (Winsock). Vous trouverez l’ensemble des API sockets réseau à cet url : http://publib.boulder.ibm.com/iseries/v5r2/ic2924/info/apis/unix8b.htm Quand on dit TCP/IP, on parle de tous les réseaux basés sur le protocole Internet (IP). Un réseau TCP/IP est différent d’un simple réseau où les ordinateurs sont connectés directement les uns aux autres. Un réseau Internet est un ensemble de un ou plusieurs réseaux qui sont eux- mêmes interconnectés pour former un réseau virtuel. N’importe quel ordinateur peut échanger des données avec n’importe quel autre ordinateur en se référant à son adresse IP. Cette adresse est un nombre de 32 bits unique dans tout l’internet. Ce nombre est découpé en 4 tronçons de 8 bits chacun. Chaque tronçon est séparé par un point pour faciliter sa lecture (notation "dot »). Certaines parties de l’adresse IP déterminent le réseau sur lequel un ordinateur est branché et les parties restantes pour identifier l’ordinateur. Le masque de sous- réseau formé aussi d’un nombre de 32 bits permet de dis tinguer la partie adresse IP de la partie identification : chaque bit à 1 dans le masque indique que le 1 L'auteur du tutoriel Il est né en 1969. Il habite à Milwaukee, Wisconsin (USA). Pour une biographie plus complète, on se reportera à sa page web About Me . V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 2 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ bit correspondant appartient à l’adresse IP ; chaque bit à 0 indique que le bit correspondant appartient à l’identification de la machine. Adresse IP 192.168.66.21 11000000 10101000 01000010 00010101 Masque 255.255.255.0 11111111 11111111 11111111 00000000 Adresse réseau 192.168.66 11000000 10101000 01000010 Identification .21 00010101 Le système envoie les données sur le réseau IP par paquets (ou datagrammes) de longueur fixe composés d’un en- tête et des données. L’en- tête contient entre autres l’adresse du destinataire et celle de l’expéditeur, car il n’est pas sûr que chaque paquet émis arrive à bon port et, plus encore, si vous envoyez 5 paquets d’un coup par exemple, rien ne vous garantit qu’ils arriveront à destination exactement en même temps et dans le même ordre. C’est pour cela que TCP (Transmission Control Protocol) a été créé. Ce protocole vous garantit que tous les paquets envoyés arrivent bien à leur destinataire dans l’ordre où ils ont été émis. Il est bon de savoir que TCP "tourne "au- dessus de la couche IP. Cela veut dire que toutes les données que vous envoyez sur le réseau IP sont d’abord converties en paquets, envoyées ensuite sur le réseau, puis reformatées en données à l’arrivée. Les ports de communication et protocoles TCP est un protocole orienté connexion, ce qui implique que vous devez d’abord établir une connexion pour l’utiliser. Un programme doit jouer le rôle de serveur et un autre de client. Le serveur attend que les connexions établies par les clients soient faites avant de pouvoir échanger des données dans les deux sens et ceci jusqu’à ce que la connexion soit fermée. Les connexions TCP multiples peuvent se faire grâce aux n° de port de communication. Chaque paquet contient un n° de port origine et un n° de port destination pour pouvoir acheminer les données d’un ordinateur à l’autre. Le numéro de port est un entier 16 bits non signé, les bornes sont donc de 0 à 65535. Le port 0 n'est pas exploitable en tant que désignation de service valide, donc le segment réellement exploitable va de 1 à 65535. Un service (comprendre un programme au niveau applicatif) qui démarre son activité réseau et qui donc est considéré comme ayant un rôle de serveur, s'attribue le ou les numéros de port qui lui reviennent conformément à cette table. Nom du Port Protocole service V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Commentaire echo 7 tcp echo 7 udp ftp -data 20 tcp #File Transfer [Default Data] ftp -data 20 udp #File Transfer [Default Data] ftp 21 tcp #File Transfer [Control] ftp 21 udp #File Transfer [Control] Page 3 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ telnet 23 tcp telnet 23 udp smtp 25 tcp mail #Simple Mail Transfer smtp 25 udp mail #Simple Mail Transfer domain 53 tcp #Domain Name Server domain 53 udp #Domain Name Server http 80 tcp www www-http #World Wide Web HTTP http 80 udp www www-http #World Wide Web HTTP pop3 110 tcp #Post Office Protocol - Version 3 pop3 110 udp #Post Office Protocol - Version 3 sunrpc 111 tcp rpcbind #SUN Remote Procedure Call sunrpc 111 udp rpcbind #SUN Remote Procedure Call nntp 119 tcp usenet #Network News Transfer Protocol nntp 119 udp usenet #Network News Transfer Protocol Ce sont les ports les plus connus et il y en a quantité d'autres2. Les ports parfaitement définis sont dans la plage 0 à 1023. Les ports déposés sont ceux allant de 1024 à 49151. On peut utiliser les ports de cette tranche en s'assurant qu'ils ne sont pas déjà utilisés par ailleurs. Les ports Dynamiques et/ou Privés vont de 49152 à 65535. Même remarque que ci-dessus. Deux autres protocoles peuvent être utilisés sous IP : UDP ("User Datagram Protocol ") et ICMP ("Internet Control Message Protocol"). UDP est similaire à TCP, sauf que la donnée est envoyée dans un seul paquet à la fois. La différence essentielle entre le paquet UDP et le paquet IP est qu’UDP ajoute des n° de ports aux paquets, ce qui permet à plusieurs tâches présentes sur le système d’utiliser UDP simultanément. On emploie habituellement UDP lorsqu’on veut envoyer de tous petits paquets de données à la fois, éliminant ainsi le besoin de puiser dans les ressources systèmes supplémentaires nécessaires à TCP. Le protocole Internet utilise ICMP en interne pour échanger des messages d’erreur et/ou de diagnostics. Le programmeur n’en a pas la charge. On peut ajouter dans la table des services de l'AS/4003, un service spécifique dans le protocole que ce service utilise (TCP...) et lui affecter un port disponible avec les commandes WRKSRVTBLE (Work with Service Table Entries) ou ADDSRVTBLE (Add Service Table Entry). 2 Les n° de port sont attribués par l'IANA (Internet Assigned Numbers Authority) La table des services est livrée avec des assignations de ports standards. Les valeurs des fonctions communes supportées par TCP/IP sont disponibles sur le web dans le document RFC (Request for Comments ) 3 V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 4 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ Un même service peut être ajouté plusieurs fois dans la table à condition toutefois que chaque service soit identifié de façon unique par la combinaison serviceprotocole-port. Vue d’ensemble d’une session de communication TCP Analogie d’une connexion TCP avec un appel téléphonique. Quand vous voulez passer un appel téléphonique, vous recherchez d’abord le n° de téléphone de la société ou de la personne à appeler. Puis vous recherchez le cas échéant le n° de poste. Vous décrochez et composez le n°. Vous parlez ensuite avec votre correspondant. Ce dernier vous répond. Puis, quand l’entretien est terminé, chacun raccroche son combiné. Une connexion TCP est très similaire : vous recherchez d’abord l’adresse IP de l’ordinateur que vous souhaitez joindre, puis le n° de port. Vous créez ensuite un socket et connectez IP et socket. Le programme que vous souhaitez contacter accepte votre connexion et, à partir de là, vous pouvez envoyer et recevoir des données. En final, vous fermez les sockets. Connextion TCP Téléphone gethostbyname = recherche une adresse IP à partir d’un nom de domaine Recherche du n° de téléphone getservbyname = recherche d’un n° de port à partir d’un nom de service Recherche du n° de de poste socket = crée un socket Décroche le combiné connect = connecte à une adresse IP et à un port donnés Compose le n° de téléphone bind = oblige l’API socket à utiliser un port particulier ou une adresse particulière Pas d'équivalence avec le téléphone listen = indique à l’API socket que vous voulez recevoir des connexions Le téléphone du correspondant sonne accept = accepte une connexion entrante Le correspondant décroche send = envoie les données à un socket Parler Recv = reçoit les données d'un socket Ecouter close = ferme un socket Raccrocher le combiné Liste des procédures à appeler à partir des API sockets Session TCP typique serveur client getservbyname() socket() bind() listen() gethostbyname() getservbyname() socket() V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano accept() connect() send() & recv() send() & recv() close() close() Page 5 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ Les principales APIs "client" utilisées NDLT: On se limitera ici définir en RPG IV les principales APIs citées dans l’ouvrage de Scott Klement. Seule l'API getservbyname() sera documentée et le texte traduit in extenso sur cette page. On se reportera utilement à la documentation de Scott pour en consulter les détails et connaître les autres APIs utiles à la programmation réseau. (http://klement.dstorm.net/rpg/socktut/tutorial.html) API getservbyname() Détails de l’API " getservbyname () "et explications pour comprendre le manuel IBM sur les APIs de type Unix. La syntaxe d’appel de cette API est documentée à l’url http://publib.boulder.ibm.com/pubs/html/as400/v4r5/ic2924/info/apis/gsrvnm.htm Malheureusement, cette syntaxe, comme d’ailleurs celle de la plupart des API traitant des sockets, a été faite pour les gens qui utilisent le langage C de l’AS/400. Pour appeler cette API, nous devons spécifier les paramètres de la façon suivante : struct servent *getservbyname(char *service_name, char *protocol_name) Découpons la pour y voir plus clair : 1 2 3 4 struct servent * getservbyname( char *service_name, char *protocol_name) 1 Chaque procédure en C débute avec la valeur retour de la procédure. L’astérisque signifie qu’elle retourne un pointeur et "struct servent "veut dire que le point eur pointe sur une structure de type "service entry". 2 La procédure à appeler se nomme "getservbyname ». 3 Le premier paramètre est un pointeur (astérisque) qui pointe sur une variable caractère appelée "service_name ». 4 Le second paramètre est un autre pointeur qui pointe sur une autre variable caractère. Il s’appelle "protocol_name ». NB : Attention ! les noms en C sont sensibles à la casse. Nous devons donc indiquer le nom de la procédure externe "getservbyname" en minuscules Voici ce que ça donne en RPG IV: D getservbyname D service_name D protocol_name V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano PR * * * ExtProc(’getservbyname’) value options(*string) value options(*string) Page 6 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ Notez bien que tous les pointeurs passés aux procédures C le sont toujours par valeur (“value”). Le mot- clé "options(*string)" est une option ajoutée au RPG pour justement le rendre compatible avec les programmes C. Les chaînes de caractères en C se terminent par un caractère nul (x’00’). Préciser options(*string) fera que RPG ajoutera automatiquement ce caractère lors du call. Si vous descendez un peu plus pas sur la page web d’IBM, vous verrez la structure "servent" décrite ci- dessous : struct servent { char *s_name; 1 char **s_aliases; 3 int s_port; 2 char *s_proto 4 }; 1 Un pointeur vers une zone mémoire contenant une chaîne alphanumérique appelée "s_name". 2 Un pointeur sur un tableau qui est lui- même un tableau de pointeurs, chaque élément pointant vers une chaîne alphanumérique. 3 4 Un entier appelé "s_port". Un pointeur vers une zone alphanumérique qui s’appelle “s_proto”. Si nous voulons définir cette structure en RPG, nous ferons comme ceci: D D D D D D p_servent servent s_name s_aliases s_port s_proto S DS * based(p_servent) * * 10I 0 * Vous vous rendez sans doute compte que le plus difficile pour appeler ce genre d’API Unix est de la convertir du C au RPG. Pour en apprendre plus et vous entraîner, allez à l’url http://www.opensource400.org/callc.html Une fois que cette DS et le prototype sont écrits, vous pouvez appeler tout simplement la procedure getservbyname de la façon suivante : C eval p_servent = getservbyname(’http’: ’tcp’) Ensuite, vous contrôlez la valeur de “s_port" pour rechercher sur quel port est situé le service http. Le plus difficile est donc la conversion. Après, ça va (presque) tout seul. V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 7 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ Voici un programme simple qui accepte un nom de service en paramètre et qui affiche le n° de port affecté à ce service en retour. H D D D D D D D D D D D c c c c c c c c c c c DFTACTGRP(*NO) ACTGRP(*NEW) getservbyname PR * ExtProc('getservbyname') service_name * value options(*string) protocol_name * value options(*string) p_servent S * servent DS based(p_servent) s_name * s_aliases * s_port 10I 0 s_proto * service S 10A msg S 50A *entry plist parm service eval p_servent = getservbyname( %trim(service): 'tcp') if p_servent = *NULL eval msg = 'No such service found or not started!' else eval msg = 'Port = ' + %editc(s_port:'L') endif dsply msg eval *inlr = *on NDLT: Se reporter à la documentation originale de Scott Klement pour consulter l'évolution de ce programme ainsi que le détail des APIs utilisées (http://klement.dstorm.net/rpg/socktut/tutorial.html) V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 8 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ API socket() 3 paramètres entiers à passer par valeur à cette API qui retourne un autre entier, le n° de socket utilisé. D D D D socket addr_family type protocol PR D AF_INET D SOCK_STREAM D IPPROTO_IP C C C 10I 0 ExtProc(’socket’) 10I 0 value 10I 0 value 10I 0 value CONST(2) CONST(1) CONST(0) Pour faire de la programmation réseau dans le domaine internet (TCP/IP), indiquer les informations suivantes: - Paramètre "addr_family" = ’AF_INET’ - Paramètre "type" = ’SOCK_STREAM’ - Paramètre "protocol" = 'IPPROTO_IP' (facultatif) c eval s = socket(AF_INET:SOCK_STREAM:IPPROTO_IP) API connect() 3 paramètres à passer à cette API: un entier, un pointeur et un autre entier. Elle retourne un entier pour dire si la connexion est réalisée. D D D D connect sock_desc dest_addr addr_len PR D D D D p_sockaddr sockaddr sa_family sa_data S DS D D D D D sockaddr_in sin_family sin_port sin_addr sin_zero 10I 0 ExtProc(’connect’) 10I 0 value * value 10I 0 value * based(p_sockaddr) 5I 0 14A DS based(p_sockaddr) 5I 0 5U 0 10U 0 8A D p_connto S * D addrlen S 10I 0 C* Ask the operating system for some memory to store our socket address into: C eval addrlen = %size(sockaddr) C alloc addrlen p_connto C* Point the socket address structure at the newly allocated area of memory: C eval p_sockaddr = p_connto C* Populate the sockaddr_in structure V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 9 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ C* Note that IP is the ip address we previously looked up using the inet_addr C* and/or gethostbyname APIs and port is the port number that we looked up using C* the getservbyname API. C eval sin_family = AF_INET C eval sin_addr = IP C eval sin_port = PORT C eval sin_zero = *ALLx’00’ C* Finally, we can connect to a server: C if connect(s: p_connto: addrlen) < 0 C*** Connect failed, report error here C endif APIs send() et recv() Une fois la connexion établie, nous recevons et envoyons les données par les APIs send() et recv(). 4 paramètres pour les deux procédures: un entier, un pointeur, et deux autres entiers. En retour, un autre entier pour savoir si la fonction s'est bien déroulée. D send D sock_desc D buffer D buffer_len D flags PR 10I 0 ExtProc('send') 10I 0 value * value 10I 0 value 10I 0 value D recv D sock_desc D buffer D buffer_len D flags PR 10I 10I * 10I 10I D miscdata D rc S S 25A 10I 0 C eval 0 ExtProc('recv') 0 value value 0 value 0 value miscdata = 'The data to send goes here' C c C* C eval rc = send(s: %addr(miscdata): 25: 0) if rc < 25 for some reason we weren't able to send all 25 bytes! endif C c C* C eval rc = recv(s: %addr(miscdata): 25: 0) if rc < 1 Something is wrong, we didnt receive anything. endif API close() Pour déconnecter et réutiliser un socket. Un seul paramètre, un entier. Retourne aussi un entier. D close D sock_desc C V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano PR callp 10I 0 ExtProc('close') 10I 0 value close(s) Page 10 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ Le programme client complété et amélioré 38 pages plus loin. Il accepte deux paramètres, un nom de site et un nom de page Web sur ce site. En retour, le code HTML de cette page est déposé dans un fichier et affiché à l'écran en fin de traitement. H DFTACTGRP(*NO) ACTGRP(*NEW) H BNDDIR('QC2LE') BNDDIR('SOCKTUT/SOCKUTIL') D/copy socktut/qrpglesrc,socket_h D/copy socktut/qrpglesrc,errno_h D/copy socktut/qrpglesrc,sockutil_h ********************************************************* * Definitions needed to make IFS API calls. Note that * these should really be in a separate /copy file! ******************************************* ************** D O_WRONLY C 2 D O_CREAT C 8 D O_TRUNC C 64 D O_CODEPAGE C 8388608 D open PR 10I 0 ExtProc('open') D filename * value options(*string) D openflags 10I 0 value D mode 10U 0 value options(*nopass) D codepage 10U 0 value options(*nopass) D unlink PR 10I 0 ExtProc('unlink') D path * Value options(*string) D write PR 10I 0 ExtProc('write') D handle 10I 0 value D buffer * value D bytes 10U 0 value ********************************************************* * end of IFS API call definitions ********************************************************* D die D peMsg PR D cmd D command D length PR D D D D D D D D D D D D D D S S S S S s s s S S S S S S msg sock port addrlen ch host file addr p_Connto RC RecBuf RecLen err fd 256A const ExtPgm('QCMDEXC') 200A const 15P 5 const 50A 10I 5U 10I 1A 32A 32A 10U * 10I 50A 10I 10I 10I 0 0 0 0 0 0 0 0 C************************************************* C* The user will supply a hostname and file C* name as parameters to our program... C************************************************* c *entry plist c parm host c parm file c V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano eval *inlr = *on Page 11 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ C************************************************* C* what port is the http service located on? C************************************************* c eval p_servent = getservbyname('http':'tcp') c if p_servent = *NULL c callp die('Can''t find the HTTP service!') c return c endif c eval port = s_port C************************************************* C* Get the 32-bit network IP address for the host C* that was supplied by the user: C************************************************* c eval addr = inet_addr(%trim(host)) c if addr = INADDR_NONE c eval p_hostent = gethostbyname(%trim(host)) c if p_hostent = *NULL c callp die('Unable to find that host!') c return c endif c eval addr = h_addr c endif C************************************************* C* Create a socket C************************************************* c eval sock = socket(AF_INET: SOCK_STREAM: c IPPROTO_IP) c if sock < 0 c callp die('socket(): ' + %str(strerror(errno))) c return c endif C************************************************* C* Create a socket address structure that C* describes the host & port we wanted to C* connect to C************************************************* c eval addrlen = %size(sockaddr) c alloc addrlen p_connto c c c c c eval eval eval eval eval p_sockaddr sin_family sin_addr = sin_port = sin_zero = = p_connto = AF_INET addr port *ALLx'00' C************************************************* C* Connect to the requested host C************************************************* C if connect(sock: p_connto: addrlen) < 0 c eval err = errno c callp close(sock) c callp die('connect(): '+%str(strerror(err))) c return c endif C************************************************* C* Send a request for the file that we'd like C* the http server to send us. C* C* Then we send a blank line to tell it we're C* done sending requests, it can process them... V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 12 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ C************************************************* c callp WrLine(sock: 'GET http://' + c %trim(host) + %trim(file) + c ' HTTP/1.0') c callp WrLine(sock: ' ') C************************************************* C* Get back the server's response codes C* C* The HTTP server will send it's responses one C* by one, then send a blank line to separate C* the server responses from the actual data. C************************************************* c dou recbuf = *blanks C eval rc = rdline(sock: %addr(recbuf): c %size(recbuf): *On) c if rc < 0 c eval err = errno c callp close(sock) c callp die('rdline(): '+%str(strerror(err))) c return c endif c enddo C************************************************* C* Open a temporary stream file to put our C* web page data into: C************************************************* c eval fd = open('/http_tempfile.txt': c O_WRONLY+O_TRUNC+O_CREAT+O_CODEPAGE: c 511: 437) c if fd < 0 c eval err = errno c callp close(sock) c callp Die('open(): '+%str(strerror(err))) c return c endif C************************************************* C* Write returned data to the stream file: C************************************************* c dou rc < 1 c eval rc = recv(sock: %addr(recbuf): c %size(recbuf): 0) c if rc > 0 c callp write(fd: %addr(recbuf): rc) c endif c enddo C************************************************* C* We're done receiving, do the following: C* 1) close the stream file & socket. C* 2) display the stream file C* 3) unlink (delete) the stream file C* 4) end program C************************************************* c callp close(fd) c callp close(sock) c callp Cmd('DSPF STMF(''/http_tempfile.txt'')': c 200) c callp unlink('/http_tempfile.txt') c return *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 13 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ * 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 D MessageID D QualMsgF D MsgData D MsgDtaLen D MsgType D CallStkEnt D CallStkCnt D MessageKey D ErrorCode PR D dsEC D dsECBytesP D dsECBytesA D dsECMsgID D dsECReserv D dsECMsgDta DS D wwMsgLen D wwTheKey S S ExtPgm('QMHSNDPM') 7A Const 20A Const 256A Const 10I 0 Const 10A Const 10A Const 10I 0 Const 4A 32766A options(*varsize) 1 5 9 16 17 4I 0 INZ(256) 8I 0 INZ(0) 15 16 256 10I 0 4A c c c c eval if return endif wwMsgLen = %len(%trimr(peMsg)) wwMsgLen<1 c c c callp SndPgmMsg('CPF9897': 'QCPFMSG *LIBL': peMsg: wwMsgLen: '*ESCAPE': '*PGMBDY': 1: wwTheKey: dsEC) c P return E /define ERRNO_LOAD_PROCEDURE /copy socktut/qrpglesrc,errno_h V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 14 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ Création de programmes serveurs D'habitude, c'est le programme client qui initialise la connexion pour solliciter un serveur, puis termine cette connexion. La plupart du temps, un programme client devient actif quand un utilisateur le sollicite et est désactivé quand il a terminé de recevoir les données du serveur ou quand l'utilisateur le désactive. A l'opposé, un programme serveur reste habituellement actif tout le temps et attend qu'un programme client le sollicite. Quand le client s'est connecté, il attend que le client aie terminé sa requête. Quand celle- ci est satisfaite, il se remet en attente de la prochaine connexion. Peu de différences avec le client en fait dans les calls d'APIs car vous devez toujours créer un socket pour communiquer mais, au lieu de vous connecter par l'API connect, vous devez vous mettre à l'écoute par l'API listen et accepter par accept les connexions qui viennent. Quand un programme client émet un connect, il doit dire à quelle adresse et à quel port se connecter. Nous devons être sûrs que le serveur écoute bien sur l'adresse et le port auquel le client se connecte. C'est ce qu'on appelle un port de liage (binding port). Les programmes serveurs - doivent être capables de gérer de nombreux clients à la fois (à complexité d'écriture) - compte tenu qu'ils doivent être toujours disponibles, l'aspect sécurité doit être notablement accru (toujours à l'esprit lors de l'écriture) - ne sont pas généralement des applications interactives Modèle de base d'un programme serveur: API Traitement 1. getservbyname() trouver le n° port et le service 2. socket() créer un nouveau socket 3. bind() lier le socket au port 4. listen() se mettre à l'écoute via ce socket 5. accept() attendre qu'un client se connecte au port, puis crée un nouveau socket pour nouveau client 6. Traitement nouveau client 7. close() 8. Boucle sur 5 V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Fermer le socket de l'accept Page 15 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ API bind() Chaque paquet envoyé sur le réseau contient une adresse locale, une adresse distante, un port local et un port éloigné pour les échanges entre serveur et client. C'est tout ce dont IP a besoin pour acheminer le paquet. L'appel à l'API bind() assigne le n° de port local qui est port "source" pour les paquets sortants et port "destination" pour les paquets entrants. L'appel à l'API connect() assigne a contrario le n° de port "éloigné" qui est le port de destination pour les paquets sortants et le port source pour les paquets entrants. Toutefois, si vous n'appelez pas bind(), l'OS assignera automatiquement un port disponible pour vous. Dans le cas de notre programme client, on n'a pas appelé l'API bind() donc l'OS nous a assigné un port qui était disponible. Peu importait lequel c'était puisque nous étions le client et initialisions la connexion. Le serveur a eu connaissance du n° de port puisqu'il figurait dans chaque paquet IP qu'on lui a envoyé. Côté serveur en revanche, nous devons utiliser un n° de port connu, sinon nos clients ne nous trouveront pas. Par conséquent, un serveur doit inévitablement faire appel à l'API bind() D bind D socket D local_addr D addresslen PR 10I 0 ExtProc('bind') 10I 0 Value * Value 10I 0 Value D INADDR_ANY C D INADDR_BROADCAST... D C D INADDR_LOOPBACK... D C D bindto D addrlen c c S S CONST(0) CONST(4294967295) CONST(2130706433) * 10I 0 ** reserve memory for a sockaddr_in structure: eval addrlen = %size(sockaddr_in) alloc addrlen bindto ** set sockaddr_in structure so that we can receive connections ** on port number "port_number" on any address. c eval p_sockaddr = bindto c eval sin_family = AF_INET c eval sin_addr = INADDR_ANY c eval sin_port = port_number c eval sin_zero = *ALLx'00' ** bind the socket! c if bind(socket: bindto: addrlen) < 0 C* bind() failed. check errno c endif V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 16 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ API listen() Tout ce que fait cette API est de dire au système que nous voulons accepter les connexions entrantes. En d'autres termes, le serveur transforme notre socket en "socket serveur". Nous indiquons en outre au système combien de clients peuvent être placés en file d'attente. Cette file d'attente est gérée de la même façon qu'une OUTQ. S'il y dépassement du nombre maximum de clients indiqué, le système renvoie un message "Connexion refusée" au client. D listen D socket_desc D back_log PR 10I 0 ExtProc('listen') 10I 0 Value 10I 0 Value c if listen(socket: 100) < 0 c** listen() failed, check errno! c endif API accept() Cette API va chercher une nouvelle connexion dans la file d'attente et crée un nouveau socket pour dialoguer avec le client. Le socket original utilisé dans bind() et listen() n'est pas affecté et poursuivra son écoute des nouvelles connexions puis les mettra en file d'attente. D accept D sock_desc D address D address_len PR 10I 0 ExtProc('accept') 10I 0 Value * Value 10I 0 D connfrom D len S S * 10I 0 c c eval alloc len = %size(sockaddr_in) len connfrom c eval newsock = accept(sock: connfrom: len) c if newsock < 0 C* accept() failed. Check errno c endif c if len <> 16 C* illegal length for a TCP connection! c endif c c c V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano eval eval p_sockaddr = connfrom msg = 'Received a connection from ' + %str(inet_ntoa(sin_addr)) + '!' Page 17 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ Notre premier programme serveur Pseudocode qui explique l'idée de base qui est derrière cet exemple: 1. 2. 3. 4. 5. 6. Call socket() pour qu'un socket qui soit à l'écoute des connexions. Call bind() pour lier notre socket au port 4000. Call listen() pour indiquer que nous voulons accepter les connexions entrantes. Call accept() pour créer un nouveau socket contenant la nouvelle connexion. Rejet si l'adresse envoyée est invalide. Renvoie l'adresse IP du socket connecté pour démontrer comment utiliser le paramètre 'address' de l'API accept(). 7. Demande au client d'envoyer un nom 8. Dire 'hello <NOM>' 9. Dire 'goodbye <NOM>' 10. Déconnecter le client au bout d'une seconde 11. Retour en 4 pour accepter le client suivant. 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 D command D length PR D die D peMsg PR D D D D D D D D D S S S S S S S S S len bindto connfrom port lsock csock line err clientip ExtPgm('QCMDEXC') 200A const 15P 5 const 256A 10I * * 5U 10I 10I 80A 10I 17A const 0 0 0 0 0 c eval *inlr = *on c exsr MakeListener c c c c c dow exsr exsr callp enddo 1 = 1 AcceptConn TalkToClient close(csock) C*=============================================================== C* This subroutine sets up a socket to listen for connections V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 18 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ 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 c c if c callp c return c endif lsock = socket(AF_INET: SOCK_STREAM: IPPROTO_IP) lsock < 0 die('socket(): ' + %str(strerror(errno))) 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 c c c c c if eval callp callp return endif bind(lsock: bindto: len) < 0 err = errno close(lsock) die('bind(): ' + %str(strerror(err))) 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 V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 19 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ c c c c c eval callp callp return endif err = errno close(lsock) die('accept(): ' + %str(strerror(err))) 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 c eval C*-----------------------CSR endsr p_sockaddr = connfrom clientip = %str(inet_ntoa(sin_addr)) 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 c callp WrLine(csock: 'Please enter your name' + ' now!') c c c if leavesr endif RdLine(csock: %addr(line): 80: *On) < 0 c c callp callp WrLine(csock: 'Hello ' + %trim(line)) WrLine(csock: 'Goodbye ' + %trim(line)) c callp C*-----------------------CSR endsr Cmd('DLYJOB DLY(1)': 200) *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * 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 D MessageID D QualMsgF D MsgData D MsgDtaLen D MsgType V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano PR ExtPgm('QMHSNDPM') 7A Const 20A Const 256A Const 10I 0 Const 10A Const Page 20 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ D D D D CallStkEnt CallStkCnt MessageKey ErrorCode 10A Const 10I 0 Const 4A 32766A options(*varsize) D dsEC D dsECBytesP D dsECBytesA D dsECMsgID D dsECReserv D dsECMsgDta DS D wwMsgLen D wwTheKey S S 1 5 9 16 17 4I 0 INZ(256) 8I 0 INZ(0) 15 16 256 10I 0 4A c c c c eval if return endif wwMsgLen = %len(%trimr(peMsg)) wwMsgLen<1 c c c callp SndPgmMsg('CPF9897': 'QCPFMSG *LIBL': peMsg: wwMsgLen: '*ESCAPE': '*PGMBDY': 1: wwTheKey: dsEC) c P return E /define ERRNO_LOAD_PROCEDURE /copy socktut/qrpglesrc,errno_h V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 21 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ Un programme serveur multi-clients Sockets bloquants et non bloquants Ce programme utilisera les sockets non bloquants et l'API select() pour consulter à une table de clients connectés. Avec cette méthode, nous pouvons traiter plusieurs clients simultanément. Par défaut, les sockets TCP sont en mode "bloquant". Par exemple, lorsque vous faites un call recv() pour lire une chaîne de caractères, le contrôle n'est rendu à votre programme que lorsque est lu au moins un caractère provenant du site éloigné. Ce processus qui attend que les données arrivent est appelé processus "bloquant". C'est la même chose pour les APIs write(), connect(), etc. Lorsqu'elles s'exécutent, la connection est bloquée jusqu'à ce que l'opération soit terminée. Il est toutefois possible de mettre en place un descripteur qui va permettre de passer en mode non bloquant. Plus besoin alors d'attendre qu'une opération se termine. NDLT: J'ai repris particulièrement cet exemple de programme serveur car je pense que c'est celui qui est le plus susceptible de correspondre à vos objectifs(multi-clients) et parce qu' il me semble relativement facilement adaptable à votre projet. H DFTACTGRP(*NO) ACTGRP(*NEW) H BNDDIR('SOCKTUT/SOCKUTIL') BNDDIR('QC2LE') *** header files for calling service programs & APIs D/copy socktut/qrpglesrc,socket_h D/copy socktut/qrpglesrc,sockutil_h D/copy socktut/qrpglesrc,errno_h *** Prototypes for externally called programs: D Translate D peLength D peBuffer D peTable PR ExtPgm('QDCXLATE') 5P 0 const 32766A options(*varsize) 10A const *** Prototypes for local subprocedures: D die D peMsg PR D NewListener D pePort D peError PR 10I 0 5U 0 value 256A D NewClient D peServ PR 10I 0 10I 0 value D ReadClient D peClient PR 10I 0 10I 0 value V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano 256A const Page 22 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ D HandleClient D peClient PR 10I 0 10I 0 value D EndClient D peClient PR 10I 0 10I 0 value D GetLine D peClient D peLine PR 10I 0 10I 0 value 256A *** Configuration D MAXCLIENTS C CONST(100) *** Global Variables: D D D D D D D D D D Msg to tolen serv max rc C readset excpset endpgm S S S S S S S S S S 256A * 10I 10I 10I 10I 10I 0 0 0 0 0 1N like(fdset) like(fdset) inz(*off) *** Variables in the "client" data structure are kept *** seperate for each connected client socket. D Client D sock D wait D rdbuf D rdbuflen D state D line DS Occurs(MAXCLIENTS) 10I 0 1N 256A 10I 0 10I 0 256A c eval *inlr = *on c exsr Initialize C********************************************************* C* Main execution loop: C* C* 1) Make 2 descriptor sets. One to check which C* sockets have data to read, one to check which C* sockets have exceptional conditions pending. C* Each set will contain the listner socket, plus C* each connected client socket C* C* 2) Call select() to find out which descriptors need C* to be read from or have exceptions pending. C* We have a timeout value set here as well. It's C* set to 1 minute if all sockets are waiting for C* user input, or 1/10 second if the sockets need C* us to write data to them. (the 1/10 second is C* just to keep this program from gobbling too V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 23 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ C* much CPU time) C* C* 3) Check to see if a user told us to shut down, or C* if the job/subsystem/system has requested us to C* end the program. C* C* 4) If the listener socket ("server socket") has data C* to read, it means someone is trying to connect C* to us, so call the NewClient procedure. C* C* 5) Check each socket for incoming data and load into C* the appropriate read buffer. C* C* 6) Do the next "task" that each socket needs. C* (could be sending a line of text, or waiting C* for input, or disconnecting, etc) C********************************************************* c dow 1 = 1 c exsr MakeDescSets c c eval rc = select(max+1: %addr(readset): *NULL: %addr(excpset): to) c exsr ChkShutDown c c c c c c if if callp endif exsr endif rc > 0 FD_ISSET(serv: readset) NewClient(serv) c exsr c enddo CheckSockets DoClients C*=============================================================== C* Initialize some program vars & set up a server socket: C*=============================================================== CSR Initialize begsr C*-----------------------c do MAXCLIENTS C c C occur client c eval sock = -1 c callp EndClient(C) c enddo c c c eval alloc eval tolen = %size(timeval) tolen to p_timeval = to C********************************************************* C* Start listening to port 4000 C********************************************************* c eval serv = NewListener(4000: Msg) c if serv < 0 c callp die(Msg) V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 24 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ c endif C*-----------------------CSR endsr C*=============================================================== C* This sets up the "readset" and "excpset" descriptor sets C* for use with select(). It also calculates the appropriate C* timeout value, and the maximum descriptor number to check C*=============================================================== CSR MakeDescSets begsr C*-----------------------c callp FD_ZERO(readset) c callp FD_SET(serv: readset) C* By default, set C* of the client's C* to only 100,000 c c c c c c c c c c c c c c c c C a 60 second timeout... but if one or more is not in 'wait' mode, change that timeout microseconds (1/10 second) eval tv_sec = 60 eval tv_usec = 0 eval max = serv do occur if callp if eval eval endif if eval endif endif enddo MAXCLIENTS C client sock <> -1 FD_SET(sock: readset) not wait tv_sec = 0 tv_usec = 100000 sock > max max = sock C* We can just copy excpset to readset... no point in going thru C* all of that again :) c eval excpset = readset C*-----------------------CSR endsr C*=============================================================== C* Check for a 'shutdown' condition. If shutdown was requested C* tell all connected sockets, and then close them. C*=============================================================== CSR ChkShutDown begsr C*-----------------------c shtdn 99 c if *in99 = *on c eval endpgm = *On c endif * Note that the 'endpgm' flag can also be set by the * 'HandleClient' subprocedure, not just the code above... c c V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano if do endpgm = *on MAXCLIENTS C Page 25 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ c C occur c if c callp c c callp c endif c enddo c callp c callp c return c endif C*-----------------------CSR endsr client sock <> -1 WrLine(sock: 'Sorry! 'down now!') EndClient(C) We''re shutting ' + close(serv) Die('shut down requested...') C*=============================================================== C* This loads any data that has been sent by the various client C* sockets into their respective read buffers, and also checks C* for clients that may have disconnected: C*=============================================================== CSR CheckSockets begsr C*-----------------------c do MAXCLIENTS C c C occur client c if sock <> -1 c c c c c c if if callp callp endif endif FD_ISSET(sock: readset) ReadClient(C) < 0 EndClient(C) FD_CLR(sock: excpset) c c c if callp endif FD_ISSET(sock: excpset) EndClient(C) c endif c enddo C*-----------------------CSR endsr C*=============================================================== C* This finally gets down to "talking" to the client programs. C* It switches between each connected client, and then sends C* data or receives data as appropriate... C*=============================================================== CSR DoClients begsr C*-----------------------c do MAXCLIENTS C c C occur client c if sock <> -1 c callp HandleClient(C) c endif c enddo C*-----------------------V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 26 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ CSR endsr *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * Create a new TCP socket that's listening to a port * * parms: * pePort = port to listen to * peError = Error message (returned) * * returns: socket descriptor upon success, or -1 upon error *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P NewListener B D NewListener PI 10I 0 D pePort 5U 0 value D peError 256A D D D D D D D sock len bindto on linglen ling flags S S S S S S S C*** Create a socket c eval c c if c eval c return c endif 10I 10I * 10I 10I * 10I 0 0 0 inz(1) 0 0 sock = socket(AF_INET:SOCK_STREAM: IPPROTO_IP) sock < 0 peError = %str(strerror(errno)) -1 C*** Tell socket that we want to be able to re-use the server C*** port without waiting for the MSL timeout: c callp setsockopt(sock: 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 = 120 c callp setsockopt(sock: SOL_SOCKET: SO_LINGER: c ling: linglen) C*** free up resources used by linger structure c dealloc(E) ling C*** tell socket we don't want blocking... c eval flags = fcntl(sock: F_GETFL) c eval flags = flags + O_NONBLOCK c if fcntl(sock: F_SETFL: flags) < 0 c eval peError = %str(strerror(errno)) c return -1 c endif V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 27 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ C*** Create a sockaddr_in structure c eval len = %size(sockaddr_in) c alloc len bindto c eval p_sockaddr = bindto c c c c eval eval eval eval sin_family sin_addr = sin_port = sin_zero = = AF_INET INADDR_ANY pePort *ALLx'00' C*** Bind socket to port c if bind(sock: bindto: len) < 0 c eval peError = %str(strerror(errno)) c callp close(sock) c dealloc(E) bindto c return -1 c endif C*** Listen for a connection c if listen(sock: MAXCLIENTS) < 0 c eval peError = %str(strerror(errno)) c callp close(sock) c dealloc(E) bindto c return -1 c endif C*** Return newly set-up socket: c dealloc(E) c return sock P E bindto *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * This accepts a new client connection, and adds him to * the 'client' data structure. *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P NewClient B D NewClient PI 10I 0 D peServ 10I 0 value D D D D D D D D X S cl flags ling connfrom len Msg S S S S S S S S 10I 10I 10I 10I * * 10I 52A 0 0 0 0 0 C************************************************* C* See if there is an empty spot in the data C* structure. C************************************************* c eval cl = 0 c do MAXCLIENTS X c X occur Client c if sock = -1 c eval cl = X V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 28 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ c c c leave endif enddo C************************************************* C* Accept new connection C************************************************* c eval len = %size(sockaddr_in) c alloc len connfrom c c c c eval if return endif S = accept(peServ: connfrom: len) S < 0 -1 c dealloc(E) connfrom C************************************************* C* Turn off blocking & limit lingering C************************************************* c eval flags = fcntl(S: F_GETFL: 0) c eval flags = flags + O_NONBLOCK c if fcntl(S: F_SETFL: flags) < 0 c eval Msg = %str(strerror(errno)) c dsply Msg c return -1 c endif c c c c c c c c eval alloc eval eval eval callp len = %size(linger) len ling p_linger = ling l_onoff = 1 l_linger = 120 setsockopt(S: SOL_SOCKET: SO_LINGER: ling: len) dealloc(E) ling C************************************************* C* If we've already reached the maximum number C* of connections, let client know and then C* get rid of him C************************************************* c if cl = 0 c callp wrline(S: 'Maximum number of connect' + c 'tions has been reached!') c callp close(s) c return -1 c endif C************************************************* C* Add client into the structure C************************************************* c cl occur client c eval sock = S c return 0 P E *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 29 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ * If there is data to be read from a Client's socket, add it * to the client's buffer, here... *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P ReadClient B D ReadClient PI 10I 0 D peClient 10I 0 value D D D D left p_read err len c S S S S peClient 10I 0 * 10I 0 10I 0 occur client c c eval eval left = %size(rdbuf) - rdbuflen p_read = %addr(rdbuf) + rdbuflen c c c c c c c c c eval if eval if return else return endif endif len len err err 0 c c P eval return rdbuflen = rdbuflen + len len = < = = recv(sock: p_read: left: 0) 0 errno EWOULDBLOCK -1 E *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * This disconnects a client and cleans up his spot in the * client data structure. *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P EndClient B D EndClient PI 10I 0 D peClient 10I 0 value c peClient occur client c if sock >= 0 c callp close(sock) c endif c eval sock = -1 c eval wait = *off c eval rdbuf = *Blanks c eval rdbuflen = 0 c eval state = 0 c eval line = *blanks c return 0 P E *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * As we're switching between each different client, this * routine is called to handle whatever the next 'step' is * for a given client. *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P HandleClient B V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 30 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ D HandleClient D peClient c PI peClient occur 10I 0 10I 0 value client C'est dans cette procédure qu'il va falloir intervenir pour exécuter les traitements "maison" et retourner les résultats au client. c c c c c select when callp c c c c c c when eval if eval eval endif state = 1 wait = *on GetLine(peClient: line) > 0 wait = *off state = 2 c c c c c c c c when if eval eval else callp eval endif state = 2 %trim(line) = 'quit' endpgm = *on state = 4 c c c when callp eval state = 3 WrLine(sock: 'Goodbye ' + %trim(line)) state = 4 c c c when callp endsl state = 4 EndClient(peClient) return 0 c P eval state = 0 WrLine(sock: 'Please enter your name' + ' now!') state = 1 WrLine(sock: 'Hello ' + %trim(line)) state = 3 E *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * This removes one line of data from a client's read buffer *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P GetLine B D GetLine PI 10I 0 D peClient 10I 0 value D peLine 256A D pos S C*** Load correct client: c peClient occur c if c return c endif 10I 0 client rdbuflen < 1 0 C*** Look for an end-of-line character: c eval pos = %scan(x'0A': rdbuf) c if pos < 1 or pos > rdbuflen c return 0 V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 31 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ c endif C*** Add line to peLine variable, and remove from rdbuf: c eval peLine = %subst(rdbuf:1:pos-1) c c c c c if eval else eval endif pos < %size(rdbuf) rdbuf = %subst(rdBuf:pos+1) c eval rdbuflen = rdbuflen - pos rdbuf = *blanks C*** If CR character found, remove that too... c eval pos = pos - 1 c if %subst(peLine:pos:1) = x'0D' c eval peLine = %subst(peLine:1:pos-1) c eval pos = pos - 1 c endif C*** Convert to EBCDIC: c if c callp c endif pos > 0 Translate(pos: peLine: 'QTCPEBC') C*** return length of line: c return P E pos *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * 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 D MessageID D QualMsgF D MsgData D MsgDtaLen D MsgType D CallStkEnt D CallStkCnt D MessageKey D ErrorCode PR D dsEC D dsECBytesP D dsECBytesA D dsECMsgID D dsECReserv D dsECMsgDta DS D wwMsgLen D wwTheKey S S V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano ExtPgm('QMHSNDPM') 7A Const 20A Const 256A Const 10I 0 Const 10A Const 10A Const 10I 0 Const 4A 32766A options(*varsize) 1 5 9 16 17 4I 0 INZ(256) 8I 0 INZ(0) 15 16 256 10I 0 4A Page 32 Programmation des sockets Synthèse raccourcie du tutoriel de Scott Klement http://klement.dstorm.net/rpg/socktut/ c c c c eval if return endif wwMsgLen = %len(%trimr(peMsg)) wwMsgLen<1 c c c callp SndPgmMsg('CPF9897': 'QCPFMSG *LIBL': peMsg: wwMsgLen: '*ESCAPE': '*PGMBDY': 1: wwTheKey: dsEC) c P return E /define ERRNO_LOAD_PROCEDURE /copy socktut/qrpglesrc,errno_h V1.1 08/2003 Synthèse Sockets V1.1 Philippe Soriano Page 33