петък, 25 ноември 2011 г.

Sockets


Sockets
При модела клиент-сървер сърверът предлага дадена услуга. При инициализация на услугата сърверът създава socket. Socket e софтуерен мрежови интерфейс, който се използва за междумашинна комуникация в мрежовата среда и се определя от IP адреса и от номера на порта, който е число между 0 и 65535 (0-1024 –системни портове).

#include


struct sockaddr {
            unsigned short sa_family; // socket address family
            char sa_data[14]; // 14 байта е максималният размер за всичките
                        //   различни sockaddr структури
}

struct sockaddr_in {
            short sin_family; // адресна фамилия – AF_INET
            unsigned short sin_port; // номер на порт
            struct in_addr sin_addr; // Интернет адрес
            char sin_zero[8]; // padding, за да се изравни размера на структурата     //  sockaddr_int с този на sockaddr
}

struct in_addr {
            unsigned long s_addr;
}

sockaddr е структура, описваща един сокет. sockaddr_in е вид sockaddr, специфична за Интернет сокетите, и може да бъде cast-вана към sockaddr.

#include
in_addr_t inet_addr(const char *cp);

Функцията inet_addr преобразува даден интернет адрес от dot notation в integer. Преобразуваният адрес е в network byte order (байтовете са подредени отляво надясно).


#include
#include
int socket(int domain, int type, int protocol);

Функцията socket инициализира сокет и връща дескриптор на този новосъздаден сокет. Параметърът domain определя адресната фамилия и може да бъде AF_INET (нас това ни интересува, за Интернет адреси), AF_INET6, AF_UNIX, AF_LOCAL, AF_ISO и т.н. Параметърът type определя семантиката на предаваните данни и може да бъде SOCK_STREAM (за TCP/IP), SOCK_DGRAM (за UDP/IP), SOCK_SEQ_PACKET и др. Параметърът protocol определя протокола, който да се използва; за TCP/IP трябва да е 0.

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

Функцията bind наименува сокета (с дескриптор sockfd), т.е. му задава локален адрес my_addr (указател към структура sockaddr). Параметърът addrlen показва размера на структурата my_addr. При успех връща 0, а при грешка връща -1.

int listen(int sockfd, int backlog);

Чрез функцията listen се посочва, че даден сокет (с дескриптор sockfd) трябва да започне да слуша за конекции. Работи само със сокети от тип SOCK_STREAM и SOCK_SEQ_PACKET (connection-based socket types). Параметърът backlog указва максималната дължина на опашката от чакащи конекции. При успех функцията връща 0, а при грешка връща -1.

int accept(int sockfd, struct sockaddr *addr, int addrlen);

Функцията accept също работи само със сокети, установяващи конекция, т.е. със SOCK_STREAM и SOCK_SEQ_PACKET. Чрез нея сървърът приема чакащи на опашката конекции. sockfd трябва да е сокет, създаден със socket, свързан с bind и слушащ за конекции с listen. Взима се първата заявка за конекция от опашката, създава се нов свързан сокет и се връща файлов дескриптор на този сокет. Новият сокет не е в слушащо състояние. sockfd не се повлиява и остава непроменен. Функцията връща файлов дескриптор на новия сокет при успех и -1 при грешка.

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

За да се свърже един клиент с даден сървър, трябва да изпълни функцията connect (свързва клиентския сокет с дескриптор sockfd с адреса serv_addr). Функцията връща 0 при успех и -1 при грешка.



1. Прост клиент/ сървър
·         Протокол lnc: клиента изпраща цяло число. Сървърът връща на клиента това число, увеличено с едно. Клиента разпечатва увеличеното число.

Прост клиент/сървър
Пример за комуникация между клиент и сървър – клиентът изпраща цяло число; сървърът връща на клиента това число, увеличено с едно; клиентът разпечатва увеличеното число:

/*****************/
/* Simple server */г
/*****************/

/*  Make the necessary includes and set up the variables.  */

#include
#include
#include
#include
#include
#include
#include

int main()
{
    int server_sockfd, client_sockfd;
    int server_len, client_len;
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;
    int n;
    char buff[1024];
    int chislo;
    char dest[2];

/*  Create an unnamed socket for the server.  */

    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

/*  Name the socket.  */

    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_address.sin_port = htons(9734);
    server_len = sizeof(server_address);
    bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

/*  Create a connection queue and wait for clients.  */

    listen(server_sockfd, 5);
    while(1) {
        printf("server waiting\n");

/*  Accept a connection.  */

        client_len = sizeof(client_address);
        client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);

/*  We can now read/write to client on client_sockfd.  */
        while (n = read(client_sockfd, buff, 1024) > 0) {
                  write(1, "server receives: ", 17);
                  write(1, buff, n);
                  write(1, "\n", 1);

                  dest[2];
                  strncpy(dest, buff, n + 1);
                  dest[n] = 0;
                  chislo = atoi(dest) + 1;
                  snprintf(dest, 2, "%d", chislo);
                 
                  write(client_sockfd, dest, 2);
                  close(client_sockfd);
              }

    }
    printf("server closes\n");
    return 0;
}


/*****************/
/* Simple client */
/*****************/

/*  Make the necessary includes and set up the variables.  */
#include
#include
#include
#include
#include
#include

int main()
{
    int sockfd;
    int client_len;
    int i;
    struct sockaddr_in client_address;
    int result;
    char ch = '5';
    int n;
    char buff[1024];

/*  Create a socket for the client.  */

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

/*  Name the socket, as agreed with the server.  */

    client_address.sin_family = AF_INET;
    client_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    client_address.sin_port = htons(9734);
    client_len = sizeof(client_address);

/*  Now connect our socket to the server's socket.  */

    result = connect(sockfd, (struct sockaddr *)&client_address, client_len);

    if(result == -1) {
        perror("oops: client");
        return 1;
    }

/*  We can now read/write via sockfd.  */

            printf("client sends: %c\n", ch);
            write(sockfd, &ch, 1);
           
            while(n = read(sockfd, buff, 1024) > 0) {
                        write(1, "client receives: ", 17);
                        write(1, buff, n);
                        write(1, "\n", 1);
            }

    close(sockfd);
    return 0;
}

Различните видове компютри използват различни конвенции за подредбата на байтовете в рамките на една дума. Някои слагат най-важния байт първо (т.нар. big-endian подредба), а други – последно (т.нар. little-endian подредба). За да могат да си комуникират машини с различна подредба на байтовете, Интернет протоколите определят конвенция за подредба на байтовете при предаване по мрежата – network byte order. При установяването на Интернет връзка чрез сокети, трябва полетата sin_port и sin_addr от структурата sockaddr_in да са в network byte order. Ако се задават целочислени стойности, трябва да се преобразуват в network byte order. Ако се използва някоя от функциите getservbyname, gethostbyname или inet_addr за получаване на номер на порт и адрес на хост, стойностите вече са в network byte order и няма нужда да се преобразуват допълнително. В противен случай обаче трябва да се използват функциите htons и ntohs за преобразуване на номера на порт, а htonl и ntohl – за преобразуване на IPv4 адреси. Тук h означава host, а nnetwork; sshort (16-битови числа, портове), llong (32-битови числа, Интернет адреси).

#include
#include
uint16_t htons(uint16_t hostshort);
uint16_t ntohs(uint16_t netshort);
uint32_t htonl(uint32_t hostlong);
uint32_t ntohl(uint32_t netlong);

#include
int gethostname(char *name, size_t namelen);

Чрез функцията gethostname в аргумента name се получава стандартното име на текущата система. namelen указва размера в байтове на масива, към който сочи name. Функцията връща 0 при успех, -1 при грешка.

Чрез структурата hostent се описва даден хост:

#include
struct hostent {
            char *h_name; // официалното име на хоста
            char **h_aliases; // zero-terminated списък от псевдо-
   // ними/алтернативни имена на хоста
int h_addrtype; // адресна фамилия – AF_INET
int h_length; // дължина на адреса в байтове
char **h_addr_list; // zero-terminated списък от адреси
// на хоста в network byte order
}
#define h_addr h_addr_list[0]; // I-ият адрес от h_addr_list
 // за backward compatibility
#include
#include
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const char *addr, int addr_len, int addr_type);

Функцията gethostbyname връща указател към структура от тип hostent за даденото име name. Това име name може да бъде или име на хост, или IPv4 адрес в dot notation, или IPv6 адрес в colon notation. Ако е IPv4 или IPv6 адрес, то не се извършва lookup.
Функцията gethostbyaddr връща структура hostent по зададен Интернет адрес. Въпреки че addr е char *, всъщност се иска указател към структура от тип in_addr. addr_len трябва да е sizeof(struct in_addr), т.е. дължината в байтове на този Интернет адрес, а addr_type трябва да е AF_INET.

Както е споменато по-горе, във файла /etc/services се извършва map-ване на портове и протоколи към имена на услуги. Всеки ред от този файл представлява запис за една услуга и има следната структура:
service_name port/protocol [aliases …]
service_name е името на услугата и е case sensitive, т.е. има значение дали буквите са малки или главни. port е десетично число, представляващо номер на порт, на който работи услугата. protocol е име на протокол (например tcp, udp), с който работи услугата, и трябва да съответства със запис от файла protocols. aliases е опционален списък от псевдоними, т.е. алтернативни имена на тази услуга, които също са case sensitive.
Файлът /etc/protocols пък представя наличните протоколи и всеки запис (т.е. всеки ред) от него има следния вид:
protocol number [aliases …]
protocol е името на протокола (ip, tcp, udp, …). number е официалното число, съответстващо на този протокол, такова, каквото се изписва в IP header-а. aliases е опционален списък от псевдоними на протокола.

Структурата servent представя запис за дадена услуга:

#include
struct servent {
            char *s_name; // официалното име на услугата
char **s_aliases; // zero-terminated списък от
   // алтернативни имена на услугата
int s_port; // номер на порт в network byte order
char *s_proto; // име на протокол
}

struct servent *getservent(void);
struct servent *getservbyname(const char *name,
const char *proto);
struct servent *getservbyport(int port, const char *proto);

Функцията getservent прочита следващия ред от файла /etc/services и връша структура servent, описваща тази услуга. Ако се налага, файлът /etc/services първо се отваря за четене.
Функцията getservbyname връща структура servent за някой ред от файла /etc/services, който съответства на указаното име на услуга name и протокол proto. Ако proto е null, то няма ограничение за протокола.
Функцията getservbyport връща структура servent за някоя услуга от файла /etc/services, която съответства на порта port в network byte order и на протокола proto. Отново, ако proto е null, то протоколът няма значение.
Ако има грешка или се достигне края на файла /etc/services, функциите getservent, getservbyname и getservbyport връщат null.

  1. Клиент на стандартната услуга daytime
·         Клиента получава от сървъра един символен низ с текущото време.(Използвайте името на услугата и името на сървъра.)
Клиент на стандартната услуга daytime
/******************/
/* Daytime client */
/******************/

/*  Start with the usual includes and declarations.  */

#include
#include
#include
#include
#include

int main(int argc, char *argv[])
{
    char *host;
    int sockfd;
    int len, result;
    struct sockaddr_in address;
    struct hostent *hostinfo;
    struct servent *servinfo;
    char buffer[128];

    if(argc == 1)
        host = "localhost";
    else
        host = argv[1];

/*  Find the host address and report an error if none is found.  */

    hostinfo = gethostbyname(host);
    if(!hostinfo) {
        fprintf(stderr, "no host: %s\n", host);
        exit(1);
    }

/*  Check that the daytime service exists on the host.  */

    servinfo = getservbyname("daytime", "tcp");
    if(!servinfo) {
        fprintf(stderr,"no daytime service\n");
        exit(1);
    }
    printf("daytime port is %d\n", ntohs(servinfo -> s_port));

/*  Create a socket.  */

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

/*  Construct the address for use with connect...  */

    address.sin_family = AF_INET;
    address.sin_port = servinfo -> s_port;
    address.sin_addr = *(struct in_addr *)*hostinfo -> h_addr_list;
    len = sizeof(address);

/*  ...then connect and get the information.  */

    result = connect(sockfd, (struct sockaddr *)&address, len);
    if(result == -1) {
        perror("oops: getdate");
        exit(1);
    }

    result = read(sockfd, buffer, sizeof(buffer));
    buffer[result] = '\0';
    printf("read %d bytes: %s", result, buffer);

    close(sockfd);
    exit(0);
}


2.   Клиент и съврър за отдалечено разпечатване на съдържанието на директория.
·        Клиента изпраща име на директория. Той получава от сървъра съдържанието на директорията и го извежда на стандартния си изход.
Клиент и сървър за отдалечено разпечатване на директория
Пример за комуникация между клиент и сървър, при която клиентът изпраща име на директория, получава от сървъра съдържанието на директорията и го извежда на стандартния си изход:

/***************/
/* "ls" server */
/***************/
/*  Make the necessary includes and set up the variables.  */

#include
#include
#include
#include
#include
#include

int main()
{
    int server_sockfd, client_sockfd;
    int server_len, client_len;
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;
    int n;
    char dir[1024];

/*  Create an unnamed socket for the server.  */

    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

/*  Name the socket.  */

    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_address.sin_port = htons(9999);
    server_len = sizeof(server_address);
    bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

/*  Create a connection queue and wait for clients.  */

    listen(server_sockfd, 5);
    while(1) {
        printf("server waiting\n");

/*  Accept a connection.  */

        client_len = sizeof(client_address);
        client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);

/*  We can now read/write to client on client_sockfd.  */

        while (n = read(client_sockfd, dir, 1024) > 0) {
                        write(1, "server receives: ", 17);
                        write(1, dir, 3);
                        write(1, "\n", 1);
           
                        close(1);
                        dup(client_sockfd);
                        close(client_sockfd);
           
                        execlp("ls", "ls", dir, "-l", 0);
            } 
            printf ("server closes\n");
    }
    return 0;
}

/***************/
/* "ls" client */
/***************/

/*  Make the necessary includes and set up the variables.  */
#include
#include
#include
#include
#include
#include

int main()
{
    int sockfd;
    int client_len;
    int i;
    struct sockaddr_in client_address;
    int result, n;
    char dir[3] = "bla";
    char buff[1024];

/*  Create a socket for the client.  */

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

/*  Name the socket, as agreed with the server.  */

    client_address.sin_family = AF_INET;
    client_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    client_address.sin_port = htons(9999);
    client_len = sizeof(client_address);

/*  Now connect our socket to the server's socket.  */

    result = connect(sockfd, (struct sockaddr *) &client_address, client_len);

    if(result == -1) {
        perror("oops: client");
        return 1;
    }

/*  We can now read/write via sockfd.  */
   
    printf("client sends: %s\n", dir);
   
    write(sockfd, dir, sizeof(dir));
   
    while(n = read(sockfd, buff, 1024) > 0) {
            printf("client receives:\n%s", buff);
    }
                                                           
    close(sockfd);
    return 0;
}


Няма коментари:

Публикуване на коментар