protocol buf需要注意的地方

本文深入探讨了 Protocol Buffer 中字段的限定符(required、optional、repeated),消息升级原则,以及如何在 .proto 文件中定义常用选项来优化代码生成。主要内容包括字段类型的合理选择、标签号的使用技巧、消息格式升级注意事项和选项配置实例。
1、 相比于optional,repeated主要用于表示数组字段。
2、对于Protocol Buffer而言,标签值为1到15的字段在编码时可以得到优化,既标签值和类型信息仅占有一个byte,标签范围是16到2047的将占有两个bytes,而Protocol Buffer可以支持的字段数量则为2的29次方减一。有鉴于此,我们在设计消息结构时,可以尽可能考虑让repeated类型的字段标签位于1到15之间,这样便可以有效的节省编码后的字节数量。
3、 限定符(required/optional/repeated)的基本规则。
      1.) 在每个消息中必须至少留有一个required类型的字段。
      2). 每个消息中可以包含0个或多个optional类型的字段。
      3.) repeated表示的字段可以包含0个或多个数据。
      4). 如果打算在原有消息协议中添加新的字段,同时还要保证老版本的程序能够正常读取或写入,那么对于新添加的字段必须是optional或repeated。道理非常简单,老版本程序无法读取或写入新增的required限定符的字段 
4、Protocol Buffer消息升级原则。
      在实际的开发中会存在这样一种应用场景,既消息格式因为某些需求的变化而不得不进行必要的升级,但是有些使用原有消息格式的应用程序暂时又不能被立刻升级,这便要求我们在升级消息格式时要遵守一定的规则,从而可以保证基于新老消息格式的新老程序同时运行。规则如下:
      1). 不要修改已经存在字段的标签号。
      2). 任何新添加的字段必须是optional和repeated限定符,否则无法保证新老程序在互相传递消息时的消息兼容性。
      3). 在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用。
      4). int32、uint32、int64、uint64和bool等类型之间是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着如果想修改原有字段的类型时,为了保证兼容性,只能将其修改为与其原有类型兼容的类型,否则就将打破新老消息格式的兼容性。
      5). optional和repeated限定符也是相互兼容的。
5、Options。
      Protocol Buffer允许我们在.proto文件中定义一些常用的选项,这样可以指示Protocol Buffer编译器帮助我们生成更为匹配的目标语言代码。Protocol Buffer内置的选项被分为以下三个级别:
      1). 文件级别,这样的选项将影响当前文件中定义的所有消息和枚举。
      2). 消息级别,这样的选项仅影响某个消息及其包含的所有字段。
      3). 字段级别,这样的选项仅仅响应与其相关的字段。
      下面将给出一些常用的Protocol Buffer选项。
      1>. option java_package = "com.companyname.projectname";
      java_package是文件级别的选项,通过指定该选项可以让生成Java代码的包名为该选项值,如上例中的Java代码包名为com.companyname.projectname。与此同时,生成的Java文件也将会自动存放到指定输出目录下的com/companyname/projectname子目录中。如果没有指定该选项,Java的包名则为package关键字指定的名称。该选项对于生成C++代码毫无影响。
      2>. option java_outer_classname = "LYPhoneMessage";
      java_outer_classname是文件级别的选项,主要功能是显示的指定生成Java代码的外部类名称。如果没有指定该选项,Java代码的外部类名称为当前文件的文件名部分,同时还要将文件名转换为驼峰格式,如:my_project.proto,那么该文件的默认外部类名称将为MyProject。该选项对于生成C++代码毫无影响。
      注:主要是因为Java中要求同一个.java文件中只能包含一个Java外部类或外部接口,而C++则不存在此限制。因此在.proto文件中定义的消息均为指定外部类的内部类,这样才能将这些消息生成到同一个Java文件中。在实际的使用中,为了避免总是输入该外部类限定符,可以将该外部类静态引入到当前Java文件中,如:import static com.company.project.LYPhoneMessage.*
      3>. option optimize_for = LITE_RUNTIME;
      optimize_for是文件级别的选项,Protocol Buffer定义三种优化级别SPEED/CODE_SIZE/LITE_RUNTIME。缺省情况下是SPEED。
      SPEED: 表示生成的代码运行效率高,但是由此生成的代码编译后会占用更多的空间。
      CODE_SIZE: 和SPEED恰恰相反,代码运行效率较低,但是由此生成的代码编译后会占用更少的空间,通常用于资源有限的平台,如Mobile。
      LITE_RUNTIME: 生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常少。这是以牺牲Protocol Buffer提供的反射功能为代价的。因此我们在C++中链接Protocol Buffer库时仅需链接libprotobuf-lite,而非libprotobuf。在Java中仅需包含protobuf-java-2.4.1-lite.jar,而非protobuf-java-2.4.1.jar。
      注:对于LITE_MESSAGE选项而言,其生成的代码均将继承自MessageLite,而非Message。    
      4>. [pack = true]: 因为历史原因,对于数值型的repeated字段,如int32、int64等,在编码时并没有得到很好的优化,然而在新近版本的Protocol Buffer中,可通过添加[pack=true]的字段选项,以通知Protocol Buffer在为该类型的消息对象编码时更加高效。如:
      repeated int32 samples = 4 [packed=true]。
      注:该选项仅适用于2.3.0以上的Protocol Buffer。
      5>. [default = default_value]: optional类型的字段,如果在序列化时没有被设置,或者是老版本的消息中根本不存在该字段,那么在反序列化该类型的消息是,optional的字段将被赋予类型相关的缺省值,如bool被设置为false,int32被设置为0。Protocol Buffer也支持自定义的缺省值,如:
      optional int32 result_per_page = 3 [default = 10]。

 

 

 

源:http://www.cnblogs.com/stephen-liu74/archive/2013/01/02/2841485.html

/*! * ************************************************************************************************* * @copyright(c) 2008-2025 Shenzhen TP-LINK Technologies Co.Ltd. * * @file http.c * @brief * * @author huyushuai * @version 1.0.0 * @date 2025-08-08 * ************************************************************************************************* */ /**************************************************************************************************/ /* INCLUDE_FILES */ /**************************************************************************************************/ #include <stdio.h> #include <stdbool.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <ctype.h> #include <strings.h> #include <string.h> #include <sys/stat.h> #include <pthread.h> #include <sys/wait.h> #include <stdlib.h> #include <errno.h> #include <signal.h> #include <stdatomic.h> #include <sys/epoll.h> /**************************************************************************************************/ /* DEFINES */ /**************************************************************************************************/ #define ISspace(x) isspace((int)(x)) #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n" #define MAX_PROCESSES 100 #define DOCUMENT_ROOT "htdocs" /**************************************************************************************************/ /* LOCAL_PROTOTYPES */ /**************************************************************************************************/ void accept_request(int client_sock); void bad_request(int client); void cat(int, FILE *); void cannot_execute(int); void error_die(const char *); void execute_cgi(int, const char *, const char *, const char *); int get_line(int, char *, int); void headers(int, const char *); void not_found(int); void serve_file(int, const char *); int startup(u_short *); void unimplemented(int); void normalize_path(char *path); const char *get_content_type(const char *path); void handle_sigpipe(int sig); /**************************************************************************************************/ /* LOCAL_FUNCTIONS */ /**************************************************************************************************/ /*! * @brief 路径规范函数 * * @param[in] path 文件路径 */ void normalize_path(char *path) { char *p = path; char *q = path; while (*p) { if (strncmp(p, "/../", 4) == 0) { p += 3; while (q > path && *--q != '/') { ; } } else if (strncmp(p, "/./", 3) == 0) { p += 2; } else if (strncmp(p, "//", 2) == 0) { p += 1; } else { *q++ = *p++; } } *q = '\0'; } /*! * @brief 忽略SIGPIPE信号处理 * * @param[in] sig SIGPIPE信号 */ void handle_sigpipe(int sig) { } /*! * @brief * * @param[in] ctx Comment */ void accept_request(int client_sock) { int client = client_sock; char buf[1024]; char method[255]; char url[255]; char path[512]; struct stat st; int cgi = 0; char *query_string = NULL; int numchars = get_line(client, buf, sizeof(buf)); size_t i = 0; size_t j = 0; while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) { method[i] = buf[j]; i++; j++; } method[i] = '\0'; if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) { unimplemented(client); close(client); return; } if (strcasecmp(method, "POST") == 0) { cgi = 1; } i = 0; while (ISspace(buf[j]) && (j < sizeof(buf))) { j++; } while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) { url[i] = buf[j]; i++; j++; } url[i] = '\0'; if (strcasecmp(method, "GET") == 0) { query_string = url; while ((*query_string != '?') && (*query_string != '\0')) { query_string++; } if (*query_string == '?') { cgi = 1; *query_string = '\0'; query_string++; } } if (strlen(url) == 0 || strstr(url, "..") != NULL) { bad_request(client); close(client); return; } snprintf(path, sizeof(path), "%s%s", DOCUMENT_ROOT, url); normalize_path(path); if (strncmp(path, DOCUMENT_ROOT, strlen(DOCUMENT_ROOT)) != 0) { not_found(client); close(client); return; } if (path[strlen(path) - 1] == '/') { strncat(path, "index.html", sizeof(path) - strlen(path) - 1); } if (stat(path, &st) == -1) { while ((numchars > 0) && strcmp("\n", buf) != 0) { numchars = get_line(client, buf, sizeof(buf)); } not_found(client); } else { if (S_ISDIR(st.st_mode)) { strncat(path, "/index.html", sizeof(path) - strlen(path) - 1); stat(path, &st); } if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH)) { cgi = 1; } if (!cgi) { serve_file(client, path); } else { execute_cgi(client, path, method, query_string); } } /* 清理资源 */ close(client); } /*! * @brief 请求出错 * * @param[in] client 客户端 */ void bad_request(int client) { char buf[1024]; sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n"); send(client, buf, sizeof(buf), 0); sprintf(buf, "Content-type: text/html\r\n"); send(client, buf, sizeof(buf), 0); sprintf(buf, "\r\n"); send(client, buf, sizeof(buf), 0); sprintf(buf, "<P>Your browser sent a bad request, "); send(client, buf, sizeof(buf), 0); sprintf(buf, "such as a POST without a Content-Length.\r\n"); send(client, buf, sizeof(buf), 0); } /*! * @brief 将文件内容发送到网络客户端 * * @param[in] client 客户端套接字描述符 * @param[in] resource 文件指针(指向要发送的文件) */ void cat(int client, FILE *resource) { char buf[1024]; size_t bytes_read; while ((bytes_read = fread(buf, 1, sizeof(buf), resource)) > 0) { if (send(client, buf, bytes_read, 0) < bytes_read) { perror("send"); break; } } } /*! * @brief 无法执行CGI * * @param[in] client 客户端 */ void cannot_execute(int client) { char buf[1024]; sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-type: text/html\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<P>Error prohibited CGI execution.\r\n"); send(client, buf, strlen(buf), 0); } /*! * @brief 打印错误消息 * * @param[in] sc 错误消息 */ void error_die(const char *sc) { perror(sc); exit(1); } /*! * @brief 执行CGI脚本 * * @param[in] client 客户端 * @param[in] path 资源路径 * @param[in] method 请求方法 * @param[in] query_string 请求内容 */ void execute_cgi(int client, const char *path, const char *method, const char *query_string) { char buf[1024]; int cgi_output[2]; int cgi_input[2]; pid_t pid; int status; int i = 0; char c; int numchars = 1; int content_length = -1; buf[0] = 'A'; buf[1] = '\0'; if (strcasecmp(method, "GET") == 0) { while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ { numchars = get_line(client, buf, sizeof(buf)); } } else /* POST */ { numchars = get_line(client, buf, sizeof(buf)); while ((numchars > 0) && strcmp("\n", buf)) { buf[15] = '\0'; if (strcasecmp(buf, "Content-Length:") == 0) { content_length = atoi(&(buf[16])); } numchars = get_line(client, buf, sizeof(buf)); } if (content_length == -1) { bad_request(client); return; } } sprintf(buf, "HTTP/1.0 200 OK\r\n"); send(client, buf, strlen(buf), 0); /* 建立output管道 */ if (pipe(cgi_output) < 0) { cannot_execute(client); return; } /* 建立input管道 */ if (pipe(cgi_input) < 0) { cannot_execute(client); return; } if ((pid = fork()) < 0) { cannot_execute(client); return; } if (pid == 0) /* child: CGI script */ { char meth_env[255]; char query_env[255]; char length_env[255]; dup2(cgi_output[1], 1); dup2(cgi_input[0], 0); close(cgi_output[0]); close(cgi_input[1]); sprintf(meth_env, "REQUEST_METHOD=%s", method); putenv(meth_env); if (strcasecmp(method, "GET") == 0) { sprintf(query_env, "QUERY_STRING=%s", query_string); putenv(query_env); } else { /* POST */ sprintf(length_env, "CONTENT_LENGTH=%d", content_length); putenv(length_env); } execl(path, path, NULL); exit(0); } else { /* parent */ close(cgi_output[1]); close(cgi_input[0]); if (strcasecmp(method, "POST") == 0) { for (i = 0; i < content_length; i++) { recv(client, &c, 1, 0); ssize_t bytes_written = write(cgi_input[1], &c, 1); if (bytes_written <= 0) { perror("Failed to write to CGI"); close(cgi_input[1]); close(cgi_input[0]); break; } } } while (read(cgi_output[0], &c, 1) > 0) { send(client, &c, 1, 0); } close(cgi_output[0]); close(cgi_input[1]); waitpid(pid, &status, 0); } } /*! * @brief 从socket中获取一行数据 * * @param[in] sock socket * @param[in] buf 用于保存数据的缓冲区 * @param[in] size 缓冲区的大小 * @return int 存储的字节数 */ int get_line(int sock, char *buf, int size) { int i = 0; char c = '\0'; int n; while ((i < size - 1) && (c != '\n')) { n = recv(sock, &c, 1, 0); if (n > 0) { if (c == '\r') { n = recv(sock, &c, 1, MSG_PEEK); if ((n > 0) && (c == '\n')) { recv(sock, &c, 1, 0); } else { c = '\n'; } } buf[i] = c; i++; } else { c = '\n'; } } buf[i] = '\0'; return (i); } /*! * @brief 根据文件路径的扩展名返回对应的 MIME 类型 * * @param[in] path 路径字符串 * @return const char* */ const char *get_content_type(const char *path) { const char *ext = strrchr(path, '.'); if (ext) { if (strcasecmp(ext, ".css") == 0) { return "text/css"; } if (strcasecmp(ext, ".js") == 0) { return "application/javascript"; } if (strcasecmp(ext, ".png") == 0) { return "image/png"; } if (strcasecmp(ext, ".jpg") == 0 || strcasecmp(ext, ".jpeg") == 0) { return "image/jpeg"; } if (strcasecmp(ext, ".gif") == 0) { return "image/gif"; } if (strcasecmp(ext, ".ico") == 0) { return "image/x-icon"; } if (strcasecmp(ext, ".svg") == 0) { return "image/svg+xml"; } if (strcasecmp(ext, ".woff") == 0) { return "font/woff"; } if (strcasecmp(ext, ".woff2") == 0) { return "font/woff2"; } } return "text/html"; } /*! * @brief 发送HTTP响应头 * * @param[in] client 客户端 * @param[in] filename 文件名 */ void headers(int client, const char *filename) { char buf[1024]; const char *content_type = get_content_type(filename); sprintf(buf, "HTTP/1.0 200 OK\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, SERVER_STRING); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type: %s\r\n", content_type); send(client, buf, strlen(buf), 0); sprintf(buf, "\r\n"); send(client, buf, strlen(buf), 0); } /*! * @brief 404 * * @param[in] client 客户端 */ void not_found(int client) { char buf[1024]; sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, SERVER_STRING); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type: text/html\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<BODY><P>The server could not fulfill\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "your request because the resource specified\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "is unavailable or nonexistent.\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "</BODY></HTML>\r\n"); send(client, buf, strlen(buf), 0); } /*! * @brief 如果不是CGI文件,直接读取文件返回给请求的http客户端 * * @param[in] client 客户端 * @param[in] filename 文件名 */ void serve_file(int client, const char *filename) { FILE *resource = NULL; int numchars = 1; char buf[1024]; buf[0] = 'A'; buf[1] = '\0'; while ((numchars > 0) && strcmp("\n", buf)) { numchars = get_line(client, buf, sizeof(buf)); } resource = fopen(filename, "rb"); if (resource == NULL) { not_found(client); } else { headers(client, filename); cat(client, resource); } fclose(resource); } /*! * @brief 启动web连接 * * @param[in] port 端口 * @return int */ int startup(u_short *port) { int httpd = 0; struct sockaddr_in name; httpd = socket(PF_INET, SOCK_STREAM, 0); if (httpd == -1) { error_die("socket"); } memset(&name, 0, sizeof(name)); name.sin_family = AF_INET; name.sin_port = htons(*port); name.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) { error_die("bind"); } if (*port == 0) { socklen_t namelen = sizeof(name); if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) { error_die("getsockname"); } *port = ntohs(name.sin_port); } if (listen(httpd, 5) < 0) { error_die("listen"); } return (httpd); } /*! * @brief 没有实现请求 * * @param[in] client 客户端 */ void unimplemented(int client) { char buf[1024]; sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, SERVER_STRING); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type: text/html\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "</TITLE></HEAD>\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "</BODY></HTML>\r\n"); send(client, buf, strlen(buf), 0); } int main(void) { int server_sock = -1; u_short port = 80; struct sockaddr_in client_name; socklen_t client_name_len = sizeof(client_name); signal(SIGPIPE, handle_sigpipe); server_sock = startup(&port); printf("HTTP server running on port %d\n", port); int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1 failed"); exit(EXIT_FAILURE); } struct epoll_event event; event.events = EPOLLIN; event.data.fd = server_sock; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_sock, &event) == -1) { perror("epoll_ctl failed"); exit(EXIT_FAILURE); } struct epoll_event events[MAX_PROCESSES]; while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_PROCESSES, -1); if (nfds == -1) { perror("epoll_wait failed"); continue; } for (int i = 0; i < nfds; i++) { if (events[i].data.fd == server_sock) { int client_sock = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len); if (client_sock == -1) { perror("accept failed"); continue; } struct epoll_event client_event; client_event.events = EPOLLIN | EPOLLET; client_event.data.fd = client_sock; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_sock, &client_event) == -1) { perror("epoll_ctl add client failed"); close(client_sock); } } else { accept_request(events[i].data.fd); } } } close(server_sock); close(epoll_fd); return 0; } 阅读上面代码,提炼出多进程实现web_server的核心部分,(关键点在于和多线程,io多路复用的对比实现,要列出可能不同的地方
08-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值