聶永的博客

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

          c_socket.io_server筆記之處理靜態文件

          緣由

          在編 寫純C語言版socket.io服務器 時,選擇了libev作為網絡基礎層代碼,可以離epoll模型遠一些,再說還可以避免單獨使用Epoll,寫出不易維護的多層嵌套代碼,聽說,有時Epoll出現一些“偽信號”小問題,沒有那么空閑精力,繞過之,選擇成熟度非常高的libev好了。
          有關libev的文章,中文資料不多,英文資料也不多。這里推薦三篇:
          - libev 設計分析
          - libev ev_io源碼分析
          - 官方文檔http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod,更為全面一些,閱讀時可以獲得較總體認知。

          這里把在編寫c_socket.io_server程中使用libev的一些地方做些筆記,記錄下來,也方便以后查閱。

          預備知識

          所有代碼的編寫、編譯、測試和運行等,都在Ubuntu下進行,另外實例嚴重依賴libev和http-parser HTTP解析庫。
          其它依賴,可以從https://github.com/yongboy/csocket.ioserver處下載。

          處理靜態文件

          這里設計一個靜態文件WEB服務器,非常簡單,僅僅滿足socket.io服務器最基本的需求,因此別苛求太多。但比網上很多大把類似文章多了一點寫入管道時緩沖區已滿問題的處理。
          這里簡單說一下處理靜態文件的思路。

          計算靜態文件路徑以及擴展名和內容類型

          char file_path[200]; 
          sprintf(file_path, "%s%s", static_folder, url_str);

          獲取文件內容和以及優先輸出響應頭部

              char file_path[200];
              sprintf(file_path, "%s%s", static_folder, url_str);

              int file = open(file_path, O_RDONLY);
              struct stat info;
              if (fstat(file, &info) == -1) {
                  fprintf(stderr, "the file %s is NULL\n", file_path);
                  write(client->fd, RESPONSE_404, strlen(RESPONSE_404));

                  close(file);
                  free_res(loop, client);

                  return 0;
              }

              char file_ext[50];
              get_extension(file_path, file_ext);
              char content_type[50];
              get_content_type(file_ext, content_type);

              int file_len = info.st_size;
              char head_msg[200] = "";
              sprintf(head_msg, RESPONSE_TEMPLATE, content_type, file_len);
              write(client->fd, head_msg, strlen(head_msg));

          很顯然,這里引入fcntl.h頭部文件,調用fstat初始化stat結構,可判斷文件是否存在,以及文件大小等。

          讀取文件內容到緩沖區,循環寫入

          int read_count;
          int buf_size = 8 * 1024;//8096;
          char buffer[buf_size + 1];
          while ((read_count = read(file, buffer, buf_size)) > 0) {
          int bytes_left = read_count;
          char *ptr = buffer;
          int need_break = 0;
          while (bytes_left > 0) {
          ssize_t write_len = write(client->fd, ptr, bytes_left);

          if (write_len == -1) {
          fprintf(stderr, "write failed(errno = %d): %s\n", errno, strerror(errno));
          switch (errno) {
          case EAGAIN:
          case EINTR:
          case EINPROGRESS:
          fprintf(stderr, "now sleep 0.2s\n");
          ev_sleep(0.2);
          break;
          default:
          need_break = 1;
          break;
          }
          } else if (write_len == 0) {
          need_break = 1;
          fprintf(stderr, "write_len is zero, and break now\n");
          break;
          } else if (write_len < bytes_left) {
          bytes_left -= write_len;
          ptr += write_len;
          fprintf(stderr, "write client with something wrong wtih bytes_left = %d & write_len = %d and write the left data !\n", (int)bytes_left, (int)write_len);
          } else {
          break;
          }
          }

          if (need_break) {
          break;
          }
          }

          close(file);

          需要注意,構造的一個大約8K+1的緩沖區buffer,不是每次都可以正常完整輸出到socket對端,write輸出不完整,會返回-1,系統返回errno值,在errno = EAGAIN,EINTR,EINPROGRESS時,需要再次將緩沖區中尚未寫入的剩下數據再次寫入到請求端。這樣可以避免常見的半包、包不完整問題。

          關閉socket描述符,當前請求結束

          free_res(loop, client);
          

          請求完成,一定要記得關閉socket描述符,釋放相應資源等。 這樣一個較為完整的HTTP請求,靜態文件就處理完畢了。

          編譯運行

          先編譯:

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

          運行之:

          ./static_server ../static

          命令輸入錯誤,如輸入靜態路徑為空,會報錯的,哈哈:

          Error: invald path parmeter

          Usage: ./static_server

          Example:
          ./staticserver ../static
          ./static
          server /home/yongboy/yourstaticfolder

          Enjoy it~

          測試一下吧

          curl -i http://192.168.190.150:8000/index.html

          在瀏覽器內,測試一下,支持圖片樣式等,完好顯示。

          需要注意,要傳入靜態文件目錄路徑,相對的路徑,或絕對的路徑,都是可以接受的。 最后,附上完整代碼:

          #include <netinet/in.h>
          #include <stdlib.h>
          #include <stdio.h>
          #include <string.h>
          #include <fcntl.h>
          #include <errno.h>
          #include <err.h>
          #include "../include/ev.h"
          #include "../include/http-parser/http_parser.h"
          #define SERVER_PORT 8000
          #define REQUEST_BUFFER_SIZE 2048
          #define RESPONSE_404 \
          "HTTP/1.1 404 Not Found\r\n"
          #define RESPONSE_TEMPLATE \
          "HTTP/1.1 200 OK\r\n" \
          "Connection: keep-alive\r\n" \
          "Content-Type: %s\r\n" \
          "Content-Length: %d\r\n" \
          "\r\n"
          typedef struct {
          int fd;
          ev_io ev_read;
          } client_t;
          struct _ext_to_content_type {
          char *extnsn[6];
          char *contentname;
          };
          http_parser_settings settings;
          struct ev_loop *loop;
          static char *static_folder = NULL;
          static void free_res(struct ev_loop *loop, client_t *client);
          static struct _ext_to_content_type ext_to_content[] = {
          { { "html", "htm", NULL }, "text/html" },
          { { "js", NULL } , "application/x-javascript" },
          { { "css", NULL } , "text/css" },
          { { "jpeg", "jpg", "jpe", NULL }, "image/jpeg" },
          { { "png", NULL } , "image/png" },
          { { "gif", NULL } , "image/gif" },
          { { "swf", NULL } , "application/x-shockwave-flash"},
          { { "txt", "c", "h", "asm", "cpp", NULL }, "text/plain" },
          { { "pdf", NULL }, "application/pdf" }
          };
          static char *get_content_type(const char *extension, char *content_type) {
          int i, j;
          for (i = 0; i < sizeof(ext_to_content) / sizeof(ext_to_content[0]); i++) {
          for (j = 0; ext_to_content[i].extnsn[j] != NULL; j++) {
          if (strcmp(extension, ext_to_content[i].extnsn[j]) == 0) {
          sprintf(content_type, "%s", ext_to_content[i].contentname);
          return content_type;
          }
          }
          }
          sprintf(content_type, "%s", "application/octet-stream");
          return content_type;
          }
          static char *substr(const char *str, unsigned start, unsigned end, char *file_ext) {
          unsigned n = end - start;
          char stbuf[256];
          strncpy(stbuf, str + start, n);
          stbuf[n] = 0;
          strcpy(file_ext, stbuf);
          return file_ext;
          }
          static char *get_extension(const char *fileName, char *file_ext) {
          char *ptr, c = '.';
          ptr = strrchr(fileName, c);
          int pos = ptr - fileName;
          substr(fileName, pos + 1, strlen(fileName), file_ext);
          return file_ext;
          }
          static 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 read_cb(struct ev_loop *loop, ev_io *w, int revents) {
          client_t *client = w->data;
          int len = 0;
          char rbuff[REQUEST_BUFFER_SIZE];
          if (revents & EV_READ) {
          len = read(w->fd, &rbuff, REQUEST_BUFFER_SIZE);
          }
          if (EV_ERROR & revents) {
          printf("error event in read\n");
          free_res(loop, client);
          return ;
          }
          if (len < 0) {
          printf("read error\n");
          ev_io_stop(EV_A_ w);
          free_res(loop, client);
          return;
          }
          if (len == 0) {
          printf("client disconnected.\n");
          ev_io_stop(EV_A_ w);
          free_res(loop, client);
          return;
          }
          rbuff[len] = '\0';
          http_parser parser;
          parser.data = client;
          http_parser_init(&parser, HTTP_REQUEST);
          size_t parsed = http_parser_execute(&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) {
          printf("the client_fd is NULL !\n");
          return;
          }
          if (setnonblock(w->fd) < 0)
          err(1, "failed to set client socket to non-blocking\n");
          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\n");
          client->ev_read.data = client;
          ev_io_init(&client->ev_read, read_cb, client->fd, EV_READ);
          ev_io_start(loop, &client->ev_read);
          }
          static int handle_static(client_t *client, const char *url_str) {
          if (client == NULL) {
          fprintf(stderr, "the client is NULL !");
          return -1;
          }
          char file_path[200];
          sprintf(file_path, "%s%s", static_folder, url_str);
          int file = open(file_path, O_RDONLY);
          struct stat info;
          if (fstat(file, &info) == -1) {
          fprintf(stderr, "the file %s is NULL\n", file_path);
          write(client->fd, RESPONSE_404, strlen(RESPONSE_404));
          close(file);
          free_res(loop, client);
          return 0;
          }
          char file_ext[50];
          get_extension(file_path, file_ext);
          char content_type[50];
          get_content_type(file_ext, content_type);
          int file_len = info.st_size;
          char head_msg[200] = "";
          sprintf(head_msg, RESPONSE_TEMPLATE, content_type, file_len);
          write(client->fd, head_msg, strlen(head_msg));
          int read_count;
          int buf_size = 8 * 1024;//8096;
          char buffer[buf_size + 1];
          while ((read_count = read(file, buffer, buf_size)) > 0) {
          int bytes_left = read_count;
          char *ptr = buffer;
          int need_break = 0;
          while (bytes_left > 0) {
          ssize_t write_len = write(client->fd, ptr, bytes_left);
          if (write_len == -1) {
          fprintf(stderr, "write failed(errno = %d): %s\n", errno, strerror(errno));
          switch (errno) {
          case EAGAIN:
          case EINTR:
          case EINPROGRESS:
          fprintf(stderr, "now sleep 0.2s\n");
          ev_sleep(0.2);
          break;
          default:
          need_break = 1;
          break;
          }
          } else if (write_len == 0) {
          need_break = 1;
          fprintf(stderr, "write_len is zero, and break now\n");
          break;
          } else if (write_len < bytes_left) {
          bytes_left -= write_len;
          ptr += write_len;
          fprintf(stderr, "write client with something wrong wtih bytes_left = %d & write_len = %d and write the left data !\n", (int)bytes_left, (int)write_len);
          } else {
          break;
          }
          }
          if (need_break) {
          break;
          }
          }
          close(file);
          free_res(loop, client);
          return 0;
          }
          static int on_url_cb(http_parser *parser, const char *at, size_t length) {
          char url_path[length];
          sprintf(url_path, "%.*s", (int)length, at);
          client_t *client = parser->data;
          return handle_static(client, url_path);
          }
          int main(int argc, char const *argv[]) {
          if(argc == 1){
          fprintf(stderr, "Error: invald path parmeter\n\n");
          printf("Usage:\n ./static_server <your static path>\n\n Example:\n ./static_server ../static\n ./static_server /home/yongboy/your_static_folder\n\nEnjoy it~\n");
          return -1;
          }
          static_folder = (char *)argv[1];
          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\n");
          if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on)) == -1)
          err(1, "setsockopt failed\n");
          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\n");
          if (listen(listen_fd, 5) < 0)
          err(1, "listen failed\n");
          if (setnonblock(listen_fd) < 0)
          err(1, "failed to set server socket to non-blocking\n");
          ev_io ev_accept;
          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 !!!!!!\n");
          return;
          }
          ev_io_stop(loop, &client->ev_read);
          close(client->fd);
          free(client);
          client = NULL;
          }
          view raw static_server.c hosted with ❤ by GitHub

          posted on 2013-03-25 16:46 nieyong 閱讀(2161) 評論(0)  編輯  收藏 所屬分類: socket.io

          公告

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

          新浪微博,歡迎關注:

          導航

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

          統計

          常用鏈接

          留言簿(58)

          隨筆分類(130)

          隨筆檔案(151)

          個人收藏

          最新隨筆

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 黄龙县| 砚山县| 醴陵市| 苍山县| 定西市| 山东| 正安县| 忻城县| 秦皇岛市| 广河县| 肥城市| 吉木萨尔县| 延庆县| 新田县| 玉龙| 阳城县| 义乌市| 东源县| 改则县| 临猗县| 双辽市| 滁州市| 铜陵市| 开化县| 松原市| 蓝田县| 武隆县| 永川市| 洞口县| 枣庄市| 龙井市| 鄂托克前旗| 诸暨市| 连江县| 平山县| 湘阴县| 乐都县| 双城市| 唐河县| 会宁县| 武安市|