聶永的博客

          記錄工作/學習的點點滴滴。

          c_socket.io_server筆記之長輪詢超時(timeout)處理

          不吐不快

          當你習慣了現有WEB服務器,諸如nginx、apache,JAVA應用服務器Tomcat等,你就不能不注意HTTP請求的響應超時時間,需要小心,尤其是反向代理時。當你可以自由控制請求timeout超時時,那是怎樣一個快意。
          在libev中使用timeout,沒有像java那樣封裝的完善,一切都很原始,但確實鋒利多了。

          長輪詢

          一般長輪詢需要定義超時時間,一旦超時,服務器端會主動斷開連接。無論是xhr形式的長輪詢,還是jsonp長輪詢,在服務器端處理沒有多大差別,輸出數據有異。

          輸出頭部

          一般優先輸出頭部,告訴瀏覽器,需要保持長連接,當然,這需要瀏覽器支持http 1.1協議,并且明確的注明當前連接為一直保持著:keep-alive:

          char heaer_str[200= "";
          strcat(heaer_str, 
          "HTTP/1.1 200 OK\r\n");
          strcat(heaer_str, 
          "Content-Type: text/plain; charset=UTF-8\r\n");
          strcat(heaer_str, 
          "Connection: keep-alive\r\n");
          strcat(heaer_str, 
          "\r\n");
          write_msg(client, heaer_str);

          定時器啟動,等待

          連接什么時候關閉,需要在代碼中手動控制,除非瀏覽器端在發出請求等待響應期間出現異常,無故斷開了連接。設服務器端設定好連接持續時間為30秒,那么就應該啟動一個定時器,除非所使用的語言層面提供了內置支持。

          client->timeout.data = client;
          ev_timer_init(
          &client->timeout, timeout_cb, 30.00); //30s
          ev_timer_start(loop, &client->timeout);

          定時器start之后,觸發的函數timeout_cb:

           1     static void timeout_cb(EV_P_ struct ev_timer *timer, int revents) {
           2         if (EV_ERROR & revents) {
           3             fprintf(stderr, "error event in timer_beat\n");
           4             return ;
           5         }
           6     
           7         if (timer == NULL) {
           8             fprintf(stderr, "the timer is NULL now !\n");
           9             return;
          10         }
          11     
          12         client_t *client = timer->data;
          13     
          14         if (client == NULL) {
          15             fprintf(stderr, "Timeout the client is NULL !\n");
          16             return;
          17         }
          18     
          19         write_msg(client, HTML_RESPONSE_ECHO);
          20         free_res(loop, client);
          21     }

          可以看到,定時器觸發之后,本例中將輸出一串預先定義好的文字,然后關閉連接。
          如何關閉觸發器,則很簡單:


              ev_timer *timer = &client->timeout;
              
          if (timer != NULL && (timer->data != NULL)) {
                  ev_timer_stop(loop, timer);
              }

          編譯運行

          編譯一下:

          gcc longpolling.c -o longpolling ../include/libev.a ../include/http-parser/http_parser.o -lm

          運行它:

          ./long_polling

          然后測試:

          curl -i http://192.168.190.150:9000/long_polling

          可以先看到頭部:

          HTTP/1.1 200 OK Content-Type: text/plain; charset=UTF-8 Connection: keep-alive

          等到30秒后輸出具體的文字內容:

          The timeout(30s) had passed, you are welcome ~!

          小結

          所演示的長輪詢,沒有什么難度,HTTP 1.1頭部輸出,定時器啟動,然后等待輸出。
          libev內含的timer組件簡單易用,控制方便,但不算是最佳實踐,官方文檔給出了若干種最佳實踐方式。具體可參閱:
          http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#codeevtimercoderelativeandopti

          完整代碼

          #include <arpa/inet.h>
          #include <stdlib.h>
          #include <stdio.h>
          #include <string.h>
          #include <fcntl.h>
          #include <err.h>
          #include "../include/ev.h"
          #include "../include/http-parser/http_parser.h"
          #define HTML_RESPONSE_ECHO "The timeout(30s) had passed, you are welcome ~!\r\n"
          #define SERVER_PORT 9000
          typedef struct {
          int fd;
          ev_timer timeout;
          ev_io ev_read;
          http_parser parser;
          } client_t;
          http_parser_settings settings;
          struct ev_loop *loop;
          ev_io ev_accept;
          static void free_res(struct ev_loop *loop, client_t *client);
          int setnonblock(int fd) {
          int flags = fcntl(fd, F_GETFL);
          if (flags < 0)
          return flags;
          flags |= O_NONBLOCK;
          if (fcntl(fd, F_SETFL, flags) < 0)
          return -1;
          return 0;
          }
          static void write_msg(client_t *client, char *msg) {
          if (client == NULL) {
          fprintf(stderr, "the client is NULL !\n");
          return;
          }
          write(client->fd, msg, strlen(msg));
          }
          static void timeout_cb(EV_P_ struct ev_timer *timer, int revents) {
          if (EV_ERROR & revents) {
          fprintf(stderr, "error event in timer_beat\n");
          return ;
          }
          if (timer == NULL) {
          fprintf(stderr, "the timer is NULL now !\n");
          return;
          }
          client_t *client = timer->data;
          if (client == NULL) {
          fprintf(stderr, "Timeout the client is NULL !\n");
          return;
          }
          write_msg(client, HTML_RESPONSE_ECHO);
          free_res(loop, client);
          }
          static void read_cb(struct ev_loop *loop, ev_io *w, int revents) {
          client_t *client = w->data;
          int len = 0;
          char rbuff[1024];
          if (revents & EV_READ) {
          len = read(client->fd, &rbuff, 1024);
          }
          if (EV_ERROR & revents) {
          fprintf(stderr, "error event in read\n");
          free_res(loop, client);
          return ;
          }
          if (len < 0) {
          fprintf(stderr, "read error\n");
          ev_io_stop(EV_A_ w);
          free_res(loop, client);
          return;
          }
          if (len == 0) {
          fprintf(stderr, "client disconnected.\n");
          ev_io_stop(EV_A_ w);
          free_res(loop, client);
          return;
          }
          rbuff[len] = '\0';
          http_parser_init(&client->parser, HTTP_REQUEST);
          size_t parsed = http_parser_execute(&client->parser, &settings, rbuff, len);
          if (parsed < len) {
          fprintf(stderr, "parse error\n");
          ev_io_stop(EV_A_ w);
          free_res(loop, client);
          }
          }
          static void accept_cb(struct ev_loop *loop, ev_io *w, int revents) {
          struct sockaddr_in client_addr;
          socklen_t client_len = sizeof(client_addr);
          int client_fd = accept(w->fd, (struct sockaddr *) &client_addr, &client_len);
          if (client_fd == -1) {
          fprintf(stderr, "the client_fd is NULL !\n");
          return;
          }
          client_t *client = malloc(sizeof(client_t));
          client->fd = client_fd;
          if (setnonblock(client->fd) < 0)
          err(1, "failed to set client socket to non-blocking");
          client->ev_read.data = client;
          client->timeout.data = NULL;
          client->parser.data = client;
          ev_io_init(&client->ev_read, read_cb, client->fd, EV_READ);
          ev_io_start(loop, &client->ev_read);
          }
          int on_url_cb(http_parser *parser, const char *at, size_t length) {
          client_t *client = parser->data;
          char heaer_str[200] = "";
          strcat(heaer_str, "HTTP/1.1 200 OK\r\n");
          strcat(heaer_str, "Content-Type: text/plain; charset=UTF-8\r\n");
          strcat(heaer_str, "Connection: keep-alive\r\n");
          strcat(heaer_str, "\r\n");
          write_msg(client, heaer_str);
          client->timeout.data = client;
          ev_timer_init(&client->timeout, timeout_cb, 30.0, 0); //30s
          ev_timer_start(loop, &client->timeout);
          return 0;
          }
          int main(int argc, char const *argv[]) {
          loop = ev_default_loop(0);
          settings.on_url = on_url_cb;
          struct sockaddr_in listen_addr;
          int reuseaddr_on = 1;
          int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
          if (listen_fd < 0)
          err(1, "listen failed");
          if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on)) == -1)
          err(1, "setsockopt failed");
          memset(&listen_addr, 0, sizeof(listen_addr));
          listen_addr.sin_family = AF_INET;
          listen_addr.sin_addr.s_addr = INADDR_ANY;
          listen_addr.sin_port = htons(SERVER_PORT);
          if (bind(listen_fd, (struct sockaddr *) &listen_addr, sizeof(listen_addr)) < 0)
          err(1, "bind failed");
          if (listen(listen_fd, 5) < 0)
          err(1, "listen failed");
          if (setnonblock(listen_fd) < 0)
          err(1, "failed to set server socket to non-blocking");
          ev_io_init(&ev_accept, accept_cb, listen_fd, EV_READ);
          ev_io_start(loop, &ev_accept);
          ev_loop(loop, 0);
          return 0;
          }
          static void free_res(struct ev_loop *loop, client_t *client) {
          if (client == NULL) {
          fprintf(stderr, "the client is NULL !!!!!!");
          return;
          }
          ev_io_stop(loop, &client->ev_read);
          ev_timer *timer = &client->timeout;
          if (timer != NULL && (timer->data != NULL)) {
          ev_timer_stop(loop, timer);
          }
          close(client->fd);
          free(client);
          }
          view raw long_polling.c hosted with ❤ by GitHub

          posted on 2013-03-27 08:57 nieyong 閱讀(4142) 評論(0)  編輯  收藏 所屬分類: socket.io

          公告

          所有文章皆為原創,若轉載請標明出處,謝謝~

          新浪微博,歡迎關注:

          導航

          <2013年3月>
          242526272812
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          統計

          常用鏈接

          留言簿(58)

          隨筆分類(130)

          隨筆檔案(151)

          個人收藏

          最新隨筆

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 长沙市| 连州市| 和田市| 长岛县| 穆棱市| 明水县| 惠来县| 广西| 吉水县| 丹东市| 临泉县| 清新县| 屯留县| 磐安县| 平乐县| 绥江县| 工布江达县| 无为县| 奉贤区| 交口县| 迁安市| 民乐县| 石河子市| 龙陵县| 岳阳市| 安徽省| 陕西省| 鄢陵县| 高平市| 腾冲县| 松原市| 和平县| 锦州市| 汝州市| 阿荣旗| 沈阳市| 紫金县| 儋州市| 城固县| 彰化市| 车致|