本文是《Mina 实现自定义协议的通信》服务器端的解释和扩充。
服务器端代码在不使用第三方开源或者商业的代码库前提下,通过C语言自定义实现了WEB服务器端的数据接收和通信。可以简单的理解为TOMCAT的简单版本。
通过在服务器端SOCKET的监听绑定,监听了服务器的REQUEST,见代码如下:
struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htons(INADDR_ANY); server_addr.sin_port = htons(SEVERPORT); int server_socket = socket(PF_INET, SOCK_STREAM, 0); if (server_socket < 0){ printf("create socket failed!\n"); exit(1); }/* int opt = 1; setsocketopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));*/ if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr))){ printf("server bind port: %d failed!\n", SEVERPORT); exit(1); } if (listen(server_socket, LENGTH_OF_LISTEN_QUEUE)){ printf("server listen failed!\n"); exit(1); }然后通过一个简单的while(1)死循环不断的访问监听服务器端的端口,代码如下:
while(1){ int i; struct sockaddr_in client_addr; socklen_t length = sizeof(client_addr); int new_server_socket = accept(server_socket, (struct sockaddr*)&client_addr, &length); if (new_server_socket < 0){ printf("server accept failed!\n"); break; } char buffer[BUFFER_SIZE]; bzero(buffer, BUFFER_SIZE); length = recv(new_server_socket, buffer, BUFFER_SIZE, 0); if (length < 0){ printf("server receive data failed!\n"); } printf("----package begin----"); printf("client INFO:%s ", inet_ntoa(client_addr.sin_addr)); time_t t = time(NULL); struct tm *local = localtime(&t); printf("hour:%d minute:%d second:%d\n", local->tm_hour, local->tm_min, local->tm_sec);/* for (i = 0; i < BUFFER_SIZE; i++){ printf("%d ", (unsigned char)buffer[i]); }*/ printf("----package end----\n");// handler_request(buffer, new_server_socket); struct comdata cd; int k; for(k = 0; k < BUFFER_SIZE; k++) cd.buffer[k] = buffer[k]; cd.socket_server = new_server_socket; pool_add_worker(handler_request, &cd);// close(new_server_socket); }
其中当数据包被监听到的时候,就会调用handler_request()函数进行数据包的解析和操作反馈。为了能够试程序的效率运行的更好,对于并发的处理能够更好,作者使用了线程池的方法来处理对于数据包处理函数的调用。代码可以见如下:
pool_add_worker(handler_request, &cd);其中线程池的初始化代码如下:
pool_init(POOL_MAX_NUMBER);线程池的详细代码和解释放在下一篇文章中,或者直接参照源码。
继续回到正题。对于handler_request()函数的详细代码如下:
void *handler_request(void *arg){ char *request = (*(struct comdata *)arg).buffer; int socket_server = (*(struct comdata *)arg).socket_server;// splice_file_request(request, socket_server); char command[BUFFER_SIZE]; char arguments[BUFFER_SIZE]; if (sscanf(request, "%s%s", command, arguments) != 2){ splice_file_request(request, socket_server); close(socket_server); return NULL; } printf("handler_cmd: %s\n", command); printf("handler_path: %s\n", arguments); if (strcmp(command, "GET") == 0 || strcmp(command, "POST") == 0) right_request(request, command, arguments, socket_server); else wrong_request(socket_server); close(socket_server); return NULL;}先看一段简单的HTTP访问数据包如下:
----package begin----client INFO:127.0.0.1 hour:11 minute:3 second:39GET /favicon.ico HTTP/1.1Host: 127.0.0.1:8082Connection: keep-aliveAccept: */*User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.4 (KHTML, like Gecko) Ubuntu/12.10 Chromium/22.0.1229.94 Chrome/22.0.1229.94 Safari/537.4Accept-Encoding: gzip,deflate,sdchAccept-Language: en-US,en;q=0.8Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3----package end----
这段数据包的request是想要GET 文件/favicon.ico。
一般HTTP的访问方式有GET POST 等。只需要简单的解析一下就可以作出对数据REQUEST的RESPONSE。其中对于GET和POST的response函数代码如下:
void right_request(char *request, char *command, char *arguments, int socket_server){ char *head = "HTTP/1.0 200 OK\r\n"; int len = strlen(head); if (sendall(socket_server, head, &len) == -1){ printf("sending failed!\n"); return; } char *content_type = "Content-type: text/plain\r\n"; len = strlen(content_type); if (sendall(socket_server, content_type, &len) == -1){ printf("sending failed!\n"); return; } char *end = "\r\n"; len = strlen(end); if (sendall(socket_server, end, &len) == -1){ printf("sending failed!\n"); return; } char info[BUFFER_SIZE]; strcat(info, "return: "); strcat(info, command); strcat(info, arguments); strcat(info, "\r\nhello world!\r\n"); len = strlen(info); if (sendall(socket_server, info, &len) == -1){ printf("sending failed!\n"); return; }}其访问的最后效果为 浏览器访问127.0.0.1:8082/index.html 结果返回为:
return: GET/index.htmlhello world!
同时支持自定义数据包的接收,其数据的包格式见文章《Mina 实现自定义协议的通信》,其数据格式简单图如下(java版本 ):
package com.a2.desktop.example5.mina.potocol;import org.apache.mina.core.buffer.IoBuffer;/** * 通信协议 * @author Chen.Hui * */public abstract class AbsMessage { /** * 协议格式: * * tag | header length | Filename | File length | offset | checksum | temps | data * */ /** 请求或访问类型 请求Tag:0x00 返回Tag:0x01 共 8 bit */ public abstract byte getTag(); /** 头文件长度 共 2^16 可表示 65535 */ public abstract short getHeaderlen(); /** 根据UUID生成文件唯一标识,共 8*36=288 bit */ public abstract byte[] getFilename();//需要設計一個算法 /** 获取文件长度 2^32=4GB 共 32 bit */ public abstract int getFileLen(); /** 获取文件的偏移量offset 共 32 bit */ public abstract int getOffset(); /** 获取文件的MD5校验码 共 32 bit */ public abstract byte[] getChecksum(); /** 预留字段 长度不超过 128 bit */ public abstract byte[] getTmp(); /**data 方式传输内容 不超过1024bit*/ public abstract IoBuffer getData();}其C端服务器数据包的解析代码如下:
void splice_file_request(char *request, int socket_server){ struct message_head tmp; char data[DATA_SIZE]; int i; bzero(&data, sizeof(data)); bzero(&tmp, sizeof(tmp)); tmp.tag = request[0]; tmp.head_length = (unsigned char)request[2]+(unsigned char)request[1]*256; memcpy(&tmp.file_name, &(request[3]), 36); tmp.file_name[36]='\0'; tmp.file_length = (unsigned char)request[39]*256*256*256+(unsigned char)request[40]*256*256+(unsigned char)request[41]*256+(unsigned char)request[42]; tmp.offset = (unsigned char)request[43]*256*256*256+(unsigned char)request[44]*256*256+(unsigned char)request[45]*256+(unsigned char)request[46]; memcpy(&tmp.checksum, &(request[47]), 4); memcpy(&tmp.tmp, &(request[51]), 4); memcpy(&data, &(request[tmp.head_length]), DATA_SIZE); for(i = 0; i < DATA_SIZE; i++){// printf("%2x ", (unsigned char)data[i]); if (i != 0 && i % 20 == 0) printf("\n"); printf("%2x ", (unsigned char)data[i]); } printf("\n--head--\n"); printf("tag:%d head_length:%d file_name:%s file_length:%d offset:%d checksum:%s\n", tmp.tag, tmp.head_length, tmp.file_name, tmp.file_length, tmp.offset, tmp.checksum); printf("--head--\n");}
作者只是实现到data数据的获取和显示,然后作者会将数据进行整合和拼接。然后会处理虚拟文件的管理,以及分布式文件的处理,服务器的负载均衡等问题。
Comming soon.....
详细代码见附件。
http://dl.vmall.com/c0h39lv1hn