WebSocket-сервер лайт-версия

от автора

Эта статья пригодится тем, кому захочется написать свой WebSocket-сервер на СИ.

Не знал какую придумать КДПВ…

Здравствуйте.

Увлекаясь постройкой «умного дома» мне захотелось попробовать заменить обычный web-сервер на websocket. Идея была реализована и протестирована, однако я пока что так и не понял, что удобнее (для меня), традиционный сервер или websocket.

Впрочем речь не об этом, а о том, что в сети достаточно много примеров реализованных на различных js-фреймворках, PHP и немного на С++, мне же хотелось сделать это на чистом СИ. Найденные примеры были слишком избыточны (для простого обмена небольшим количеством данных) и требовали сторонних библиотек, поэтому я решил написать что-то своё и заодно разобраться в технологии websocket.

Заранее скажу что в сервере не реализована работа по "wss://…" (https), не поддерживается длина «тела сообщения» больше 125-ти символов и не поддерживается передача фрагментированных сообщений (поэтому лайт-версия).

Если Вы ещё не знакомы с тем, что такое WebSocket, то вот тут, тут и тут очень хорошо всё расписано, ну и конечно же RFC 6455. Я же покажу код простого WebSocket-сервера на СИ с пояснениями того, что и как там происходит.


Алгоритм таков: сервер работает как обычный web-сервер прослушивая какой-нибудь порт, например 80-ый, и обрабатывая стандартные запросы — файлы index.html, *.css, *.png, *.js, *.ttf. Когда сервер отдаст клиенту файл index.html содержащий специальный js-код, этот код, отработав, сделает запрос на соединение по протоколу "ws://…", сервер в свою очередь отреагирует на это «специальным» ответом и не будет закрывать соединение.
Таким образом Websocket соединение будет установлено.

Теперь нужно слегка отвлечься от сервера и разобраться с клиентом, то есть с браузерной стороной (впрочем клиентом может быть и Ваше приложение). К счастью, здесь уже всё сделано разработчиками браузеров и нам нужно только загрузить страничку (index.html) со «специальным» js-кодом…

index.html

<!DOCTYPE html> <html>     <head>         <meta charset="utf-8">         <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>         <style type="text/css">            .knopka {            height: 40px;            width: 120px;            text-align: center;            line-height: 2.1;            cursor: pointer;            border: 1px solid #999;            border-radius: 10px;            }            v{}         </style>          <script type="text/javascript">             $(function() {                 window.WebSocket = window.WebSocket || window.MozWebSocket;                 var websocket = new WebSocket('ws://192.168.5.197:80/ws');                  websocket.onopen = function () {                     $('h1').css('color', '#65c178'); /* green */                 };                  websocket.onclose = function (e) {                     console.log("WebSocket: ", e)                     $('h1').css('color', '#fe457e'); /* red */                 };                  websocket.onerror = function () {                     $('h1').css('color', '#fe457e'); /* red */                 };                  websocket.onmessage = function (message) {                     console.log(message.data);                     $('v').append($('<p>', { text: message.data }));                 };                                  $('.knp0').click(function(e) {                     e.preventDefault();                     websocket.send($('input').val());                     $('input').val('');                 });                  $('.knp1').click(function(e) {                     e.preventDefault();                     websocket.send("hi");                 });                  $('.knp2').click(function(e) {                     e.preventDefault();                     websocket.send("ping");                 });             });         </script>         </head>     <body>         <h1>WebSocket</h1>           <div class='knopka knp1'>Hi...</div><br>           <div class='knopka knp2'>Call PING</div><br>           <div class='knopka knp0'>Send</div> <input type="text" />           <v></v>     </body> </html> 

Выглядит она вот так:

Если надпись зелёная, то соединение установлено, а если красная, то нет.

Итак клиент получил index.html cо «специальной» строчкой…

var websocket = new WebSocket('ws://192.168.5.197:80/ws');

Это и есть запрос на соединение по протоколу websocket. В скобках указывается протокол, адрес сервера (если порт 80-ый, то можно не писать), буквы после слеша могут быть любыми, а можно и вовсе без них, это просто «маячок» для сервера (см.ниже), говорящий ему, что нужно распарсить весь пакет и поискать там заголовок — Sec-WebSocket-Key: с рандомным ключём — 9+4iSUx4slLlng0Xcv2zFw== в кодировке Base64.

От разных браузеров запросы выглядят по разному, но в целом суть одна и та же, вот так от FireFox:

Так от Chromium:

Заголовки (браузер генерирует их сам) содержат:
Запрос на websocket-соединение.
Ключ для передачи его серверу.
Подпротоколы — они описаны по ссылкам данным в начале. Для данной статьи они не важны.
Прочая «дребедень».

Соединение

Далее я буду сопровождать текст вырезками из кода, а после будет код целиком…

Сервер, получив «маячок» (ws) распарсивает весь пакет, вычленяет из него ключ — 9+4iSUx4slLlng0Xcv2zFw== (ключ всегда постоянной длины) и кладёт его в массив resultstr (см. ниже).
Теперь к ключу нужно прибавить специальную строку (GUID) определённую в спецификации RFC 6455:

char GUIDKey[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; //36

Это строка никогда не меняется.

Склеиваем ключ полученый от клиена с GUIDKey:

strcat(resultstr, GUIDKey);

В итоге получаем вот такую строку:
9+4iSUx4slLlng0Xcv2zFw==258EAFA5-E914-47DA-95CA-C5AB0DC85B11

Блок отвечающий за эти действия:

    /////////////////////////////////// WS /////////////////////////////////////////////////     else if((strstr(str_from_buf, "GET /ws ")) != NULL)       {        warning_access_log(buffer);         if((p = strstr(buffer, "Sec-WebSocket-Key:")) != NULL)         {                                                             char resultstr[64] = {0,};           int i = 0, it = 0;           for(i = 19; it < 24; i++, it++)            {              resultstr[it] = p[i];            }            strcat(resultstr, GUIDKey); 

Далее склеенную строку передаём в функцию SHA1((unsigned char *)resultstr, strlen(resultstr), temp); (она определена в заголовке #include <openssl/sha.h>) и получаем обратно уже хеш-сумму этой строки…

////////////////////////////sha1///////////////////////////////////////           unsigned char temp[SHA_DIGEST_LENGTH] = {0,};           char buf[SHA_DIGEST_LENGTH*2] = {0,};            SHA1((unsigned char *)resultstr, strlen(resultstr), temp);            for(i=0; i < SHA_DIGEST_LENGTH; i++)             {              sprintf((char*)&(buf[i*2]), "%02x", temp[i]);            } 

Далее с помощью функции base64_encode(temp, key_out, sizeof(temp)); переводим хеш-сумму в кодировку Base64 и получаем вот такой ключ — s3pPLMBiTxaQ9kYGzzhZRbK+xOo= (он тоже всегда постоянной длины)

////////////////////////////Base64////////////////////////////////////            unsigned char key_out[64] = {0,};           base64_encode(temp, key_out, sizeof(temp));

Ну и наконец собираем и отправляем ответ клиенту…

sem_init(&sem, 0, 0); char resp[131] = {0,}; snprintf(resp, 130, "%s%s%s", response_ws, key_out, "\r\n\r\n"); if(send(client_fd, resp, sizeof(char) * strlen(resp), MSG_NOSIGNAL) == -1) warning_access_log("send response_ws."); 

… который выглядит вот так:

HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= 

Здесь мы говорим клиенту что переключились на протокол WS и отправляем ему наш ключ. Соединение не закрывается и передаётся в отдельный поток.

//////////////////////////// START WS /////////////////////////////////           if(pthread_create(&ws_thread, NULL, &ws_func, &client_fd) != 0) error_log("creating WS.");           pthread_detach(ws_thread);           sem_wait(&sem); 

Подведём итог первой части (соединения):
Браузер получает index.html со «специальным» js-кодом, отправляет серверу запрос на ws-соединение вместе с ключём-идентификатором.
Сервер «склеивает» этот ключ со специальной строкой, с помощью алгоритма SHA1 создаёт хеш-сумму склееной строки, переводит эту хеш-сумму в кодировку Base64 и отправляет клиенту вместе с заголовком.
Websocket соединение установлено.


Работа с Websocket

Тут начинается самое интересное. Обмен данными производится с помощью хитроумных фреймов, которые выглядят вот так:

     0                   1                   2                   3     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1    +-+-+-+-+-------+-+-------------+-------------------------------+    |F|R|R|R| опкод |М| Длина тела  |    Расширенная длина тела     |    |I|S|S|S|(4бита)|А|   (7бит)    |            (1 байт)           |    |N|V|V|V|       |С|             |(если длина тела==126 или 127) |    | |1|2|3|       |К|             |                               |    | | | | |       |А|             |                               |    +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +    |  Продолжение расширенной длины тела, если длина тела = 127    |    + - - - - - - - - - - - - - - - +-------------------------------+     + - - - - - - - - - - - - - - - +-------------------------------+    |                               |  Ключ маски, если МАСКА = 1   |    +-------------------------------+-------------------------------+     +-------------------------------+-------------------------------+    | Ключ маски (продолжение)      |       Данные фрейма ("тело")  |    +-------------------------------- - - - - - - - - - - - - - - - +     +-------------------------------- - - - - - - - - - - - - - - - +    :                     Данные продолжаются ...                   :    + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +    |                     Данные продолжаются ...                   |    +---------------------------------------------------------------+ 

Каждый горизонтальный блок — это 32 бита.

Представленный сервер умеет обмениваться PINGPONGами (нужны для проверки соединения) и работать с текстовыми сообщениями длина которых не превышает 125-ти байт (большего мне не требовалось).

Фрейм с текстовым сообщением полученный от клиента выглядит так…

Первый байт:

      0 1 2 3 4 5 6 7       +-+-+-+-+-------+      |F|R|R|R| opcode|      |I|S|S|S|  (4)  |      |N|V|V|V|       |      | |1|2|3|       |      +-+-+-+-+-------+ 

Второй байт:

      0 1 2 3 4 5 6 7       +---------------+      |M| Payload len |      |A|     (7)     |                 |S|             |        |K|             |                   +---------------+ 

Третий, четвёртый, пятый и шестой байты — это маска.
Следом идёт сообщение (ограниченное 125-ю байтами).

В спецификации говорится, что сообщение от браузера к серверу всегда имеет маску, а сообщение от сервера к браузеру должно быть без маски.

Вот что говорит браузер Chromium если ему отправить маскированное сообщение:

Однако если отправить маскированное сообщение браузеру FireFox, то ошибок не наблюдается.

Допустим браузер прислал сообщение «Hello geektimes», тогда на стороне сервера это будет выглядеть так:

Принято — 21 байт.
Opcode: 0x01 — говорит о том, что это текстовое сообщение.
Maska: 0x01 — маска есть.
Payload_len: 15 — длина сообщения 15 байт.

В программе при этом происходят следующие действия…

... #define WS_TEXT_FRAME 0x01 ...  void * ws_func(void *client_arg)   {     ...     while(1)     {       ...        if(rec_b > 0)  // если что-то получили, то ...                            {           char masking_key[4] = {0,}; // сюда положим маску          char opcode; // сюда тип фрейма          int payload_len; // сюда длину сообщения (тела), то есть без служебных байтов            opcode = inbuf[0] & 0x0F;               printf("FIN: 0x%02x\n", inbuf[0] & 0x01);             printf("RSV1: 0x%02x\n", inbuf[0] & 0x02);             printf("RSV2: 0x%02x\n", inbuf[0] & 0x03);             printf("RSV3: 0x%02x\n", inbuf[0] & 0x04);             printf("Opcode: 0x%02x\n", inbuf[0] & 0x0F);                                 payload_len = inbuf[1] & 0x7F;              printf("Maska: 0x%02x\n", inbuf[1] & 0x80 ? 1:0);             printf("Payload_len: %d\n", inbuf[1] & 0x7F);           masking_key[0] = inbuf[2];           masking_key[1] = inbuf[3];          masking_key[2] = inbuf[4];          masking_key[3] = inbuf[5];                          char payload[128] = {0,}; // сюда положим сообщение           ...           if(opcode == WS_TEXT_FRAME) // от клиента получен текст           {             int i = 6, pl = 0;             for(; pl < payload_len; i++, pl++)              {                payload[pl] = inbuf[i]^masking_key[pl % 4]; // применяем маску к полученному тексту              }                                   printf("\nReciv TEXT_FRAME from %d client, payload: %s\n", client_fd, payload);             ... 

Если закрыть страничку, то браузер пошлёт опкод закрытия (0x08):

... #define WS_CLOSING_FRAME 0x08 ...         if(opcode == WS_CLOSING_FRAME) // от клиента получен код закрытия соединения           {             memset(reciv_r, 0, 48);             snprintf(reciv_r, 47, "%s%d\n", "Ws_func recive opcod - 0x08, DIE clien - ",  client_fd);             warning_access_log(reciv_r); // пишем ссобытие в лог             if(close(client_fd) == -1) error_log("open file close client_fd ws 2."); // закрываем соединение с клиентом             pthread_exit(NULL); // убиваем поtok           } ... 

Если сервер отправит браузеру PING

...                printf("\nPING client - %d\n", client_fd);                 char ping[] = {0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f}; // Ping - не маскированный, тело содержит слово Hello, эта же слово вернётся с Понгом                                if(send(client_fd, ping, 7, 0) == -1)                 {                   warning_access_log("Error PING.");                    if(close(client_fd) == -1) error_log("open file close client_fd ws 3."); // закрываем соединение с клиентом                   pthread_exit(NULL);                  } ... 

… тогда браузер в ответ отправит PONG, тем самым сообщив серверу о своём присутствии. Вместе с PINGом можно отправить любую фразу, браузер вернёт её вместе с PONGом.

Не обязательно посылать вместе с PINGом какую-то фразу, можно послать «пустой» PING…

...                char ping[] = {0x89, 0x00};                 if(send(client_fd, ping, 2, 0) == -1)                 {                   warning_access_log("Error PING.");                    if(close(client_fd) == -1) error_log("open file close client_fd ws 3."); // закрываем соединение с клиентом                   pthread_exit(NULL);                  } ... 

Отправить PING может только сервер.

Чтобы закрыть соединение с клиентом, нужно отправить ему опкод — 0x88

...                char close_client[] = {0x88, 0};                                if(send(client_fd, close_client, 2, 0) == -1)                 {                   warning_access_log("Error CLOSE.");                    if(close(client_fd) == -1) error_log("open file close client_fd ws 4."); // закрываем соединение с клиентом                   pthread_exit(NULL);                  } ... 

Чтобы отправить браузеру какое-либо сообщение, нужно в первый байт фрейма записать опкод говорящий что это текстовое сообщение, а во второй маску 0 и количество байт из которых состоит само сообщение.
То есть фрейм будет выглядеть так:

               char hello[] = {0x81, 0x05, 'H', 'e', 'l', 'l', 'o'};                              if(send(client_fd, hello, 7, 0) == -1)                 {                   if(close(client_fd) == -1) error_log("send hello.");                    pthread_exit(NULL);                  } 

Ну и наконец код целиком…

Исходник

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h>  #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h>  #include <fcntl.h>     #include <time.h> #include <pthread.h> #include <semaphore.h> #include <signal.h> #include <sys/sendfile.h> #include <openssl/sha.h>  char response[] = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html; charset=UTF-8\r\n\r\n";  char response_404[] = "HTTP/1.1 404 Not Found\r\n" "Content-Type: text/html; charset=UTF-8\r\n\r\n";  char response_img[] = "HTTP/1.1 200 OK\r\n" "Content-Type: image/png; charset=UTF-8\r\n\r\n";    char response_xicon[] = "HTTP/1.1 200 OK\r\n" "Content-Type: image/x-icon; charset=UTF-8\r\n\r\n";  char response_css[] = "HTTP/1.1 200 OK\r\n" "Content-Type: text/css; charset=UTF-8\r\n\r\n";  char response_js[] = "HTTP/1.1 200 OK\r\n" "Content-Type: text/js; charset=UTF-8\r\n\r\n";  char response_ttf[] = "HTTP/1.1 200 OK\r\n" "Content-Type: font/ttf ; charset=UTF-8\r\n\r\n";  char response_text[] = "HTTP/1.1 200 OK\r\n" "Content-Type: text/text; charset=UTF-8\r\n\r\n";  char response_403[] = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html; charset=UTF-8\r\n\r\n" "<!DOCTYPE html><html><head><title>403</title>" "<style>body { background-color: #312f2f }" "h1 { font-size:4cm; text-align: center; color: #666;}</style></head>" "<body><h1>403</h1></body></html>\r\n";  char GUIDKey[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; //36  char response_ws[] = "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: "; //97  unsigned char charset[]={"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"};  // opcode - тип фрейма #define WS_TEXT_FRAME 0x01 #define WS_PING_FRAME 0x09 #define WS_PONG_FRAME 0x0A #define WS_CLOSING_FRAME 0x08 #define BUFSIZE 1024 #define FILESTR 32 #define ALLARRAY 64  char patch_to_dir[ALLARRAY] = {0,}; char fpfile[ALLARRAY] = {0,}; char buffer[BUFSIZE] = {0,}; int client_fd; int count_warning_log =0; struct stat stat_buf; sem_t sem;   int base64_encode(unsigned char sha_key_in[], unsigned char base64_key_out[], int len)  {    int idx, idx2, blks, left_over;     blks = (len / 3) * 3;    for(idx=0, idx2=0; idx < blks; idx += 3, idx2 += 4)      {       base64_key_out[idx2] = charset[sha_key_in[idx] >> 2];       base64_key_out[idx2+1] = charset[((sha_key_in[idx] & 0x03) << 4) + (sha_key_in[idx+1] >> 4)];       base64_key_out[idx2+2] = charset[((sha_key_in[idx+1] & 0x0f) << 2) + (sha_key_in[idx+2] >> 6)];       base64_key_out[idx2+3] = charset[sha_key_in[idx+2] & 0x3F];     }     left_over = len % 3;     if(left_over == 1)      {       base64_key_out[idx2] = charset[sha_key_in[idx] >> 2];       base64_key_out[idx2+1] = charset[(sha_key_in[idx] & 0x03) << 4];       base64_key_out[idx2+2] = '=';       base64_key_out[idx2+3] = '=';       idx2 += 4;     }     else if(left_over == 2)      {       base64_key_out[idx2] = charset[sha_key_in[idx] >> 2];       base64_key_out[idx2+1] = charset[((sha_key_in[idx] & 0x03) << 4) + (sha_key_in[idx+1] >> 4)];       base64_key_out[idx2+2] = charset[(sha_key_in[idx+1] & 0x0F) << 2];       base64_key_out[idx2+3] = '=';       idx2 += 4;     }     base64_key_out[idx2] = '\0';    return(idx2);  }   void error_log(char *my_error)   {     time_t t;    time(&t);    FILE *f;    f = fopen("/var/log/ErrorWsstd.log", "a");     if(f == NULL) printf("Error open /var/log/ErrorWsstd.log.\n");    fprintf(f, "%s", ctime( &t));    fprintf(f, "Error %s\n\n", my_error);    printf("Error %s Write to /var/log/ErrorWsstd.log.\n", my_error);    fclose(f);    exit(0);  }   void warning_access_log(char *war_ac)   {      count_warning_log++;    if(count_warning_log > 100)      {        system("gzip -f /var/log/Access_warning.log");        count_warning_log = 0;        time_t t;        time(&t);        FILE *f;        f = fopen("/var/log/Access_warning.log", "w");         fprintf(f, "%s", ctime( &t));        fprintf(f, "%s\n\n", war_ac);        printf("_______________________________________\nWrite to /var/log/Access_warning.log...\n%s\n", war_ac);        fclose(f);      }      else      {        time_t t;        time(&t);        FILE *f;        f = fopen("/var/log/Access_warning.log", "a");         fprintf(f, "%s", ctime( &t));        fprintf(f, "%s\n\n", war_ac);        printf("_______________________________________\nWrite to /var/log/Access_warning.log...\n%s\n", war_ac);        fclose(f);      }  }   void read_in_file(char *name_file)   {     off_t offset = 0;    memset(&stat_buf, 0, sizeof(stat_buf));        memset(fpfile, 0, ALLARRAY);    snprintf(fpfile, (int)strlen(patch_to_dir) + (int)strlen(name_file) + 1, "%s%s", patch_to_dir, name_file);    int file = open(fpfile, O_RDONLY);     if(file < 0)      {       if(close(client_fd) == -1) error_log("open file close client_fd.");       warning_access_log("Not File.");      }      else     {       if(fstat(file, &stat_buf) != 0) error_log("fstat.");       if(sendfile(client_fd, file, &offset, stat_buf.st_size) == -1) warning_access_log("sendfile.");        if(close(file) == -1) error_log("close file.");       if(close(client_fd) == -1) warning_access_log("in function read_in_file() - close client_fd.");       warning_access_log(buffer);       printf("Trans %s\n\n", name_file);     }  }   //////////////////////////////////////////////// ws_func /////////////////////////////////////////////////////////// void * ws_func(void *client_arg)   {     int client_fd = * (int *) client_arg;    sem_post(&sem);    warning_access_log("START_WS");    printf("\nClient ID - %d\n", client_fd);    char inbuf[132] = {0,};    char reciv_r[48] = {0,};     while(1)     {       memset(inbuf, 0, 132);        int rec_b = read(client_fd, inbuf, 131); // ожидаем данные от клиента и читаем их по приходу       memset(reciv_r, 0, 48);       snprintf(reciv_r, 39, "%s%d%s%d\n", "Ws_func recive ", rec_b, " bytes from clien ",  client_fd);       warning_access_log(reciv_r); // пишем ссобытие в лог        if(rec_b == 0 || rec_b == -1) // если клиент отвалился или что-то нехорошо, тогда...        {          memset(reciv_r, 0, 48);          snprintf(reciv_r, 47, "%s%d%s%d\n", "Ws_func read return - ", rec_b, ", DIE clien - ",  client_fd);          warning_access_log(reciv_r); // пишем ссобытие в лог          if(close(client_fd) == -1) error_log("open file close client_fd ws 1."); // закрываем соединение с клиентом          pthread_exit(NULL);        }         if(rec_b > 0)  // если что-то получили, то ...                            {           char masking_key[4] = {0,}; // сюда положим маску          char opcode; // сюда тип фрейма          int payload_len; // сюда длину сообщения (тела), то есть без служебных байтов            opcode = inbuf[0] & 0x0F;               printf("FIN: 0x%02x\n", inbuf[0] & 0x01);             printf("RSV1: 0x%02x\n", inbuf[0] & 0x02);             printf("RSV2: 0x%02x\n", inbuf[0] & 0x03);             printf("RSV3: 0x%02x\n", inbuf[0] & 0x04);             printf("Opcode: 0x%02x\n", inbuf[0] & 0x0F);                                 payload_len = inbuf[1] & 0x7F;              printf("Maska: 0x%02x\n", inbuf[1] & 0x80 ? 1:0);             printf("Payload_len: %d\n", inbuf[1] & 0x7F);           masking_key[0] = inbuf[2];          masking_key[1] = inbuf[3];          masking_key[2] = inbuf[4];          masking_key[3] = inbuf[5];                          char payload[128] = {0,}; // сюда положим сообщение           if(opcode == WS_CLOSING_FRAME) // от клиента получен код закрытия соединения           {             memset(reciv_r, 0, 48);             snprintf(reciv_r, 47, "%s%d\n", "Ws_func recive opcod - 0x08, DIE clien - ",  client_fd);             warning_access_log(reciv_r); // пишем ссобытие в лог             if(close(client_fd) == -1) error_log("open file close client_fd ws 2."); // закрываем соединение с клиентом             pthread_exit(NULL); // убиваем поtok           }            if(opcode == WS_PONG_FRAME) // от клиента получен PONG (маскированный)           {             int i = 6, pl = 0;             for(; pl < payload_len; i++, pl++)              {                payload[pl] = inbuf[i]^masking_key[pl % 4];               }                                   printf("\nRecive PONG and text \"%s\"\n", payload);           }            if(opcode == WS_TEXT_FRAME) // от клиента получен текст           {             int i = 6, pl = 0;             for(; pl < payload_len; i++, pl++)              {                payload[pl] = inbuf[i]^masking_key[pl % 4];               }                                   printf("\nReciv TEXT_FRAME from %d client, payload: %s\n", client_fd, payload);               if(payload[0] == 'p' && payload[1] == 'i' && payload[2] == 'n' && payload[3] == 'g') // от клиента получен текст "ping"                {                printf("\nPING client - %d\n", client_fd);                  char ping[] = {0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f}; // Ping - не маскированный, тело содержит слово Hello, эта же слово вернётся с Понгом                                if(send(client_fd, ping, 7, 0) == -1)                 {                   warning_access_log("Error PING.");                    if(close(client_fd) == -1) error_log("open file close client_fd ws 3."); // закрываем соединение с клиентом                   pthread_exit(NULL);                  }               }              if(payload[0] == 'c' && payload[1] == 'l' && payload[2] == 'o' && payload[3] == 's' && payload[4] == 'e') // от клиента получен текст "close"              {                printf("\nClose client - %d\n", client_fd);                  char close_client[] = {0x88, 0};                             if(send(client_fd, close_client, 2, 0) == -1)                 {                   warning_access_log("Error CLOSE.");                    if(close(client_fd) == -1) error_log("open file close client_fd ws 4."); // закрываем соединение с клиентом                   pthread_exit(NULL);                  }               }                if(payload[0] == 'h' && payload[1] == 'i') // от клиента получен текст "hi"              {                char messag[] = "Hi client - ";                int message_size = (int) strlen(messag);                char out_data[128] = {0,};                memcpy(out_data + 2, messag, message_size); // копируем сообщение в массив "out_data" начиная со второго байта (первые два байта для опкода и длины тела)                char nom_client[5] = {0,};                sprintf(nom_client, "%d", client_fd); // номер клиента                int nom_client_size = (int) strlen(nom_client);                memcpy(out_data + 2 + message_size, nom_client, nom_client_size); // копируем номер клиента в массив "out_data" следом за сообщением                 message_size += nom_client_size; // получаем общую длину тела сообщения                 out_data[0] = 0x81;                out_data[1] = (char)message_size;                 printf("\nSize out Msg: %d\n", message_size);                 if(send(client_fd, out_data, message_size + 2, 0) == -1)                  {                   warning_access_log("Error Hi.");                    if(close(client_fd) == -1) error_log("open file close client_fd ws 5."); // закрываем соединение с клиентом                    pthread_exit(NULL);                  }              }           }        }         }   }     int main(int argc, char *argv[])   {     if(argc != 3) error_log("not argumets.");         unsigned int PORTW = strtoul(argv[1], NULL, 0); // порт для web-сервера 80   strncpy(patch_to_dir, argv[2], 63); // путь к файлу index.html   warning_access_log("START");   pthread_t ws_thread;       /////////////////////////////////////////////////////////    WEB    ///////////////////////////////////////////////////////////////   int one = 1;   struct sockaddr_in svr_addr, cli_addr;   socklen_t sin_len = sizeof(cli_addr);     int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);   if (sock < 0) error_log("not socket.");     setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int));     svr_addr.sin_family = AF_INET;   svr_addr.sin_addr.s_addr = INADDR_ANY;   svr_addr.sin_port = htons(PORTW);    if(bind(sock, (struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1)     {      close(sock);      error_log("bind.");    }     if(listen(sock, 5) == -1)     {      close(sock);      error_log("listen.");    }     signal(SIGPIPE, SIG_IGN);    char str_from_buf[FILESTR] = {0,};   char result_file[FILESTR] = {0,};           for(;;)     {     client_fd = accept(sock, (struct sockaddr *) &cli_addr, &sin_len);       if(client_fd == -1) continue;      memset(buffer, 0, BUFSIZE);     memset(str_from_buf, 0, FILESTR);     memset(result_file, 0, FILESTR);     char *p = NULL;      if(read(client_fd, buffer, BUFSIZE - 1) == -1) warning_access_log("Error in main - read_client_fd.");      int i = 0;     for(; i < FILESTR; i++)      {        str_from_buf[i] = buffer[i];        if(str_from_buf[i] == '\n') break;        if(i > 31)         {           str_from_buf[i] = '\0';           break;           }      }       if((strstr(str_from_buf, "GET / ")) != NULL)       {        if(send(client_fd, response, (int)strlen(response), MSG_NOSIGNAL) == -1) warning_access_log("send response.");        read_in_file("index.html");      }       /////////////////////////////////// WS /////////////////////////////////////////////////     else if((strstr(str_from_buf, "GET /ws ")) != NULL)       {        warning_access_log(buffer);         if((p = strstr(buffer, "Sec-WebSocket-Key:")) != NULL)         {                                                             char resultstr[64] = {0,};           int i = 0, it = 0;           for(i = 19; it < 24; i++, it++)            {              resultstr[it] = p[i];            }            strcat(resultstr, GUIDKey);            ////////////////////////////sha1///////////////////////////////////////           unsigned char temp[SHA_DIGEST_LENGTH] = {0,};           char buf[SHA_DIGEST_LENGTH*2] = {0,};            SHA1((unsigned char *)resultstr, strlen(resultstr), temp);            for(i=0; i < SHA_DIGEST_LENGTH; i++)             {              sprintf((char*)&(buf[i*2]), "%02x", temp[i]);            }            ////////////////////////////Base64////////////////////////////////////            unsigned char key_out[64] = {0,};           base64_encode(temp, key_out, sizeof(temp));             sem_init(&sem, 0, 0);           char resp[131] = {0,};           snprintf(resp, 130, "%s%s%s", response_ws, key_out, "\r\n\r\n");           if(send(client_fd, resp, sizeof(char) * strlen(resp), MSG_NOSIGNAL) == -1) warning_access_log("send response_ws.");                //////////////////////////// START WS /////////////////////////////////           if(pthread_create(&ws_thread, NULL, &ws_func, &client_fd) != 0) error_log("creating WS.");           pthread_detach(ws_thread);           sem_wait(&sem);         }      }       else if((p = strstr(str_from_buf, ".png")) != NULL)       {        int index = p - str_from_buf;        int i = 0;        int otbor = 0;        for(; i < index + 3; i++)         {           result_file[i] = str_from_buf[i];                if(result_file[i] == '/')             {              otbor = i;            }         }         memset(result_file, 0, FILESTR);        strncpy(result_file, str_from_buf + otbor - 3, index -1); //  otbor + 1        if(send(client_fd, response_img, (int)strlen(response_img), MSG_NOSIGNAL) == -1) warning_access_log("Error send response_img.");        read_in_file(result_file);      }      else if((p = strstr(str_from_buf, ".css")) != NULL)       {        int index = p - str_from_buf;        int i = 0;        int otbor = 0;        for(; i < index + 3; i++)         {           result_file[i] = str_from_buf[i];                if(result_file[i] == '/')             {              otbor = i;            }         }         memset(result_file, 0, FILESTR);        strncpy(result_file, str_from_buf + otbor + 1, index -1);        if(send(client_fd, response_css, (int)strlen(response_css), MSG_NOSIGNAL) == -1) warning_access_log("Error send response_css.");        read_in_file(result_file);      }       else if((strstr(str_from_buf, "jquery.js")) != NULL)       {        if(send(client_fd, response_js, (int)strlen(response_js), MSG_NOSIGNAL) == -1) warning_access_log("Error send response_js.");        read_in_file("jquery.js");      }      else if((strstr(str_from_buf, "favicon.ico")) != NULL)       {        if(send(client_fd, response_xicon, (int)strlen(response_xicon), MSG_NOSIGNAL) == -1) warning_access_log("Error send favicon.ico.");        read_in_file("favicon.ico");      }      else       {        if(send(client_fd, response_403, sizeof(response_403), MSG_NOSIGNAL) == -1) warning_access_log("Error send response_403.");        if(close(client_fd) == -1) warning_access_log("Error close client_fd 403.");        warning_access_log(buffer);      }     }  } //END main  // gcc -Wall -Wextra -Werror websocket.c -o websocket -pthread -lcrypto // sudo ./websocket 80 /home/dima/c-websocket/ 

И бинарник , который запускается с двумя параметрами — порт (80) и путь к папке с программой и файлом index.html (в начале статьи).

sudo chmod +x ./websocket sudo ./websocket 80 /home/dima/c-websocket/ 

Желающие могут скомпилить прогу для роутера или ещё чего-нибудь.

П.С. Код не «причёсан», содержит много ненужной отладочной информации и всяких логов, так что не обессудьте.
ссылка на оригинал статьи https://geektimes.ru/post/283298/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *