Server UDP

Un server UDP functioneaza conform schemei urmatoare

1. WSAStartup() --> Initializarea
2. socket()     --> Obtinerea unui socket
3. Alegerea portului si interfetei de serviciu
4. bind()       --> Legarea serverului la un port
5. Testarea sosirii datelor
6. recvfrom()   --> Citirea mesajului
7. sendto()     --> Trimiterea raspunsului
8. goto 5              --> se revine la punctul 5.

Secventa de apeluri anterioara este conforma modelului de aici.

1. WSAStartup()

Daca solicitam versiunea 2.0 a DLL-ului care implementeaza socketurile putem apela functia WSAStartup() spre exemplu prin linia de cod

retval= WSAStartup(majorV+minorV*256,wsockinfo);

Aceasta functie are nevoie ca in zona de date sa fie facute urmatoarele declaratii:

int retval,minorV=0, majorV=2;
LPWSADATA wsockinfo;

unde despre LPWSADATA gasim in fisierul antet winsock.h urmatoarele:

#define WSADESCRIPTION_LEN 256
#define WSASYS_STATUS_LEN 128

typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
} WSADATA;

typedef WSADATA FAR *LPWSADATA;

Pentru structura WSADATA pointata de wsockinfo trebuie sa alocam spatiu de exemplu prin linia de cod:

wsockinfo =(LPWSADATA) malloc(sizeof(WSADATA));

Nota. Un apel corect il putem obtine si prin codul

#define majorV 2
#define minorV 0
WSADATA wsaData;
int retval; 
WORD vers=majorV+minorV*256;
...
retval = WSAStartup( vers, &wsaData );

Pentru un sistem care are DLL-ul pentru socketuri din 2000 in structura wsaData se va reintoarce informatia urmatoare

wVersion       <-- 2.0
wHighVersion   <-- 2.2
szDescription  <-- "Microsoft wsock32.dll, ver2.2, 32bit of Jun 7 2000, at 21:34:15."
szSystemStatus <-- "On Win95."
iMaxSockets    <-- 32767
iMaxUdpDg      <-- 65467

2. socket()

Apelul functiei socket trebuie facut de forma

sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

Ea necesita definirea in zona de date a codului

SOCKET sock;

3. Alegerea portului si interfetei de serviciu

Inainte de apelul functiei bind() care necesita sintaxa

int bind ( SOCKET s,
 const struct sockaddr FAR* name,
 int namelen
)

trebuie initializata structura de tipul sockadr care este descrisa in winsock.h prin

/*
* Structure used by kernel to store most
* addresses.
*/
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};

Pentru a putea folosi rutinele htons() si inet_addr() aceasta structura trebuie echivalata cu structura originala Berkeley

struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};

in care structura in_addr este descrisa in winsock.h prin

/*
* Internet address (old style... should be updated)
*/
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
#define s_addr S_un.S_addr
/* can be used for most tcp & ip code */
#define s_host S_un.S_un_b.s_b2
/* host on imp */
#define s_net S_un.S_un_b.s_b1
/* network */
#define s_imp S_un.S_un_w.s_w2
/* imp */
#define s_impno S_un.S_un_b.s_b4
/* imp # */
#define s_lh S_un.S_un_b.s_b3
/* logical host */
};

Echivalarea celor doua structuri se poate face, de exemplu, prin declaratia

union sockadd{
struct sockaddr_in s_i;
struct sockaddr s;
} sa;

Initializarea structurii sockaddr_in presupune definirea portului de serviciu si a interfetei de serviciu care se poate face de exemplu prin

char * ip="192.168.1.10";//definit in zona de date
...
sa.s_i.sin_family = PF_INET;
sa.s_i.sin_port = htons(5003);
sa.s_i.sin_addr.s_addr= inet_addr (ip);

in care inet_addr() are sintaxa

unsigned long inet_addr (
const char FAR * cp 
);

iar htons() sintaxa

u_short htons ( u_short hostport );

In exemplul de mai sus s-a ales portul 5003 si interfata cu adresa 192.168.1.10.

4. bind()

Apelul functiei bind care are sintaxa

int bind ( SOCKET s,
  const struct sockaddr FAR* name,
  int namelen
)

se poate face prin

retval=bind(sock, psa, sizeof(sa.s));

daca s-a definit in zona de date 

struct sockaddr FAR *psa= &sa.s;

sau direct prin

retval=bind(sock, &sa.s, sizeof(sa.s));

5. Testarea sosirii datelor

Serverul trebuie inainte de a citi date sa vada daca sunt date disponibile la socketul sock. Aceasta se face folosind functia select. Sintaxa functiei select este urmatoarea

int select (

int nfds, 
fd_set FAR * readfds, 
fd_set FAR * writefds, 
fd_set FAR * exceptfds, 
const struct timeval FAR * timeout 
);

Ea are nevoie de definirea a trei structuri de tip fd_set si a unei structuri de tip timeval. Despre structura fd_set in winsock.h gasim

/*
* Select uses arrays of SOCKETs. These macros manipulate such
* arrays. FD_SETSIZE may be defined by the user before including
* this file, but the default here should be >= 64.
*
* CAVEAT IMPLEMENTOR and USER: THESE MACROS AND TYPES MUST BE
* INCLUDED IN WINSOCK.H EXACTLY AS SHOWN HERE.
*/
#ifndef FD_SETSIZE
#define FD_SETSIZE 64
#endif /* FD_SETSIZE */

typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;

iar despre structura timeval gasim

/*
* Structure used in select() call, taken from the BSD file sys/time.h.
*/
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};

Prin urmare daca dorim sa testam numai un socket (si anume socketul sock) in zona de date trebuiesc facute declaratiile

#define FD_SETSIZE 1
struct fd_set rd,wr,er;
struct fd_set *prd=&rd,*pwr=&wr,*per=&er;
struct timeval timeout={0,0},*ptimeout=&timeout;

iar in zona de cod se poate introduce

rd.fd_count=1;
rd.fd_array[0]=sock;
wr.fd_count=1;
wr.fd_array[0]=sock;
er.fd_count=1;
er.fd_array[0]=sock;
retval=select (1,prd,pwr,per,ptimeout);

Daca lucrurile merg bine atunci retval este diferit de SOCKET_ERROR  si se poate afla daca s-au primit date prin testarea valorii lui rd.fd_count. Daca acesta este 1 atunci la socketul sock au venit date.

6. recvfrom()

Citirea datelor se face cu functia recvfrom care are sintaxa

int recvfrom (

SOCKET s, 
char FAR* buf, 
int len, 
int flags, 
struct sockaddr FAR* from, 
int FAR* fromlen 
);

daca in zona de date s-au facut definitiile

#define maxbuff 517
char buffin[maxbuff];
struct sockaddr sa_in;
int adrlun=sizeof(sockaddr);

se poate apela functia recvfrom prin

retval = recvfrom(sock, buffin, maxbuff-1,0, &sa_in, &adrlun );

Daca lucrurile au mers normal in bufferul buffin se afla datele primite si retval precizeaza numarul de octeti receptionati.

7. sendto()

Raspunsul la mesajul receptionat se face folosind functia sendto. Aceasta are sintaxa

int sendto (

SOCKET s, 
const char FAR * buf, 
int len, 
int flags, 
const struct sockaddr FAR * to, 
int tolen 
);

Astfel linia de cod

retval=sendto(sock,buffout,lung,0,&sa_in,sizeof(sa_in));

presupune existenta in zona de date a definitiilor

int lung;
char buffout[maxbuff];

In cele de mai sus se presupune ca datele de trimis sunt memorate in bufferul buffout si au lungimea egala cu valoarea variabilei lung. De asemenea se presupune ca adresa corespondentului este memorata in structura sa_in dupa apelul functiei recvfrom() la punctul 6.

Cornel Mironel Niculae, 2004-2005
13-Nov-2004