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

Documents pareils