最近看了《HTTP权威指南》和《UNP》有了写一个简单的web服务器的想法,正好这个学期没有什么课,所以就花了一个星期这样写了一个出来,鉴于本人水平有限,如果有什么设计或代码错误的,希望各位指出哈。

tinyhttp web服务器的架构为epoll + 多线程 + sendfile,  本来想用线程池代替的因为每来一个连接就new一个线程这样对于OS来说负担太大,并且线程一旦过多线程切换就会花费很大代价造成性能瓶颈,但是我打算之后单独写一个线程池代码示例的说,所以这个版本就使用多线程来代替线程池了。

tinyhttp暂时只支持GET和HEAD方法,支持的首部不多大概七八个吧,支持伪长连接(我觉得是伪的哈哈)。

话不多说,先上几张效果图吧:

这张是测试http请求和响应包的

这张是自己构造了一个包来收发的

这张是我把google首页的源代码拿来测试的

这张是我自己写了一个html代码使用火狐浏览器来与的我tinyhttp web服务其通信的测试

我的tinyhttp是可配置的,现阶段只支持domain和docroot配置项,domain就是你部署的网站域名,docroot想必学过网页的都知道是什么意思吧~

好了,现在就把完整的源代码给出,总共有1500+行:

 /*
*Author Zou Xiao hang
*Email 1210603696@qq.com
*File Name tinyhttp.h
*Date 2013/10/05
*/
#ifndef _TINY_HTTP_H_
#define _TINY_HTTP_H_ #include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/epoll.h>
#include <strings.h>
#include <string>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <utility>
#include <fstream>
#include <sstream>
#include <map>
#include <iostream>
#include <string.h>
#include <pthread.h>
#include <netinet/tcp.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/sendfile.h> #include "utility.h"
#include "parse.h" using namespace std; typedef struct _epollfd_connfd
{
int epollfd;
int connfd;
}_epollfd_connfd; /*********************************** 常数定义
*******************************************/
#define MAX_EVENTS 1024 //epoll最大监听事件数
#define MAX_BACKLOG 100 //监听队列最大数
/******************************************************************************
***********/ /*********************************** 保存配置文件相关值
*********************************/
string tyhp_domain("");
string tyhp_docroot("");
/******************************************************************************
***********/ /*********************************** MIME定义
*******************************************/
typedef struct mime_node
{
const char *type;
const char *value;
}mime_node; mime_node tyhp_mime[] =
{
{".html", "text/html"},
{".xml", "text/xml"},
{".xhtml", "application/xhtml+xml"},
{".txt", "text/plain"},
{".rtf", "application/rtf"},
{".pdf", "application/pdf"},
{".word", "application/msword"},
{".png", "image/png"},
{".gif", "image/gif"},
{".jpg", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".au", "audio/basic"},
{".mpeg", "video/mpeg"},
{".mpg", "video/mpeg"},
{".avi", "video/x-msvideo"},
{".gz", "application/x-gzip"},
{".tar", "application/x-tar"},
{NULL ,NULL}
};
/*
*函数作用:将MIME的type转换为相应的value
*函数参数:type
*函数返回值: NULL表示type在MIME中不存在,否则返回相应value的指针
*/
inline const char* tyhp_mime_type2value(const char *type)
{
for(int i = ; tyhp_mime[i].type != NULL; ++i)
{
if(strcmp(type, tyhp_mime[i].type) == )
return tyhp_mime[i].value;
}
return NULL;
}
/******************************************************************************
***********/ /*********************************** HTTP状态码
*******************************************/
#define TYHP_CONTINUE 100 //收到了请求的起始部分,客户端应该继续请求 #define TYHP_OK 200 //服务器已经成功处理请求
#define TYHP_ACCEPTED 202 //请求已接受,服务器尚未处理 #define TYHP_MOVED 301 //请求的URL已移走,响应应该包含Location URL
#define TYHP_FOUND 302 //请求的URL临时移走,响应应该包含Location URL
#define TYHP_SEEOTHER 303 //告诉客户端应该用另一个URL获取资源,响应应该包含
Location URL
#define TYHP_NOTMODIFIED 304 //资源未发生变化 #define TYHP_BADREQUEST 400 //客户端发送了一条异常请求
#define TYHP_FORBIDDEN 403 //服务器拒绝请求
#define TYHP_NOTFOUND 404 //URL未找到 #define TYHP_ERROR 500 //服务器出错
#define TYHP_NOIMPLEMENTED 501 //服务器不支持当前请求所需要的某个功能
#define TYHP_BADGATEWAY 502
//作为代理或网关使用的服务器遇到了来自响应链中上游的无效响应
#define TYHP_SRVUNAVAILABLE 503
//服务器目前无法提供请求服务,过一段时间后可以恢复 char tyhp_ok[] = "OK";
char tyhp_badrequest[] = "Bad Request";
char tyhp_forbidden[] = "Forbidden";
char tyhp_notfound[] = "Not Found";
char tyhp_noimplemented[] = "No Implemented"; /*
*函数作用:通过HTTP状态码返回友好语句
*函数参数:HTTP状态码
*函数返回值: 相应的语句
*/
char *tyhp_get_state_by_codes(int http_codes);
/******************************************************************************
************/ /*********************************** HTTP响应首部
*******************************************/
#define TYHP_ACCEPTRANGE_HEAD "Accpet-Range"
#define TYHP_AGE_HEAD "Age"
#define TYHP_ALLOW_HEAD "Allow"
#define TYHP_CONTENTBASE_HEAD "Content-Base"
#define TYHP_CONTENTLENGTH_HEAD "Content-Length"
#define TYHP_CONTENTLOCATION_HEAD "Content-Location"
#define TYHP_CONTENTRANGE_HEAD "Content-Range"
#define TYHP_CONTENTTYPE_HEAD "Content-Type"
#define TYHP_DATE_HEAD "Date"
#define TYHP_EXPIRES_HEAD "Expires"
#define TYHP_LAST_MODIFIED_HEAD "Last-Modified"
#define TYHP_LOCATION_HEAD "Location"
#define TYHP_PUBLIC_HEAD "Public"
#define TYHP_RANGE_HEAD "Range"
#define TYHP_SERVER_HEAD "Server"
/******************************************************************************
************/
/*********************************** HTTP请求首部
*******************************************/
//#define TYHP
/******************************************************************************
************/ #endif
 /*
*Author Zou Xiao hang
*Email 1210603696@qq.com
*File Name tinyhttp.cpp
*Date 2013/10/05
*/
#include "tinyhttp.h" #define ONEKILO 1024
#define ONEMEGA 1024*ONEKILO
#define ONEGIGA 1024*ONEMEGA /*********************************** 线程相关 *******************************************/
/*
*函数作用:处理客户端链接的线程例程
*函数参数:param为客户conn_sock
*函数返回值: 无
*/
void* tyhp_thread_func(void *param); //记录当前处理线程的数量
int32_t tyhp_thread_num = ;
pthread_mutex_t tyhp_thread_num_mutex = PTHREAD_MUTEX_INITIALIZER;
/*
*函数作用:tyhp_thread_num原子加1
*函数参数:无
*函数返回值: 无
*/
void tyhp_thread_num_add1();
/*
*函数作用:tyhp_thread_num原子减1
*函数参数:无
*函数返回值: 无
*/
void tyhp_thread_num_minus1();
/*
*函数作用:tyhp_thread_num原子读
*函数参数:无
*函数返回值: tyhp_thread_num当前值
*/
int32_t tyhp_thread_num_read();
/*****************************************************************************************/ /******************************** tyhp_http_header_t处理函数 *********************************/
/*
*函数作用:根据解析下来的tyhp_http_header_t来处理客户的请求
*函数参数: phttphdr指向要处理的tyhp_http_header_t
out保存了处理的结果,即http响应包
*函数返回值: HTTP状态码
*/
int tyhp_do_http_header(tyhp_http_header_t *phttphdr, string& out);
/*
*函数作用:通过HTTP状态码返回友好语句
*函数参数:HTTP状态码
*函数返回值: 相应的语句
*/
char *tyhp_get_state_by_codes(int http_codes);
/*****************************************************************************************/ /*********************************** web服务器程序入口函数 ***************************************/
int main(int argc, char const *argv[])
{
int listen_fd;
int conn_sock;
int nfds;
int epollfd;
uint16_t listen_port;
struct servent *pservent;
struct epoll_event ev;
struct epoll_event events[MAX_EVENTS];
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t addrlen;
pthread_attr_t pthread_attr_detach;
_epollfd_connfd epollfd_connfd;
pthread_t tid; if(argc != )
{
printf("Usage: %s <config_path>\n", argv[]);
exit(-);
}
//判断配置文件是否存在
if(- == tyhp_is_file_existed(argv[]))
{
perror("tyhp_is_file_existed");
exit(-);
}
//调用tyhp_parse_config解析配置文件
if(- == tyhp_parse_config(argv[]))
{
printf("tyhp_parse_config error\n");
exit(-);
} //创建监听套接字
listen_fd = tyhp_socket(AF_INET, SOCK_STREAM, );
//设置监听套接字为非阻塞模式
tyhp_set_nonblocking(listen_fd);
//对监听套接字设置SO_REUSEADDR选项
tyhp_set_reuse_addr(listen_fd);
//通过服务名和协议名获得相应的知名端口,其实可以直接设置端口为80,我们这样做是为了可扩展性
pservent = tyhp_getservbyname("http", "tcp");
//pservent->s_port已经是网络字节序了
listen_port = pservent->s_port; bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = (listen_port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //将服务器sockaddr_in与监听套接字绑定
tyhp_bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
//开始监听
tyhp_listen(listen_fd, MAX_BACKLOG); //创建epoll文件描述符
epollfd = tyhp_epoll_create(MAX_EVENTS); ev.events = EPOLLIN;//可读事件
ev.data.fd = listen_fd;
//将监听事件加入epoll中
tyhp_epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_fd, &ev); //设置线程属性为detach
pthread_attr_init(&pthread_attr_detach);
pthread_attr_setdetachstate(&pthread_attr_detach, PTHREAD_CREATE_DETACHED); for(;;)
{
//无限等待直到有描述符就绪
nfds = tyhp_epoll_wait(epollfd, events, MAX_EVENTS, -);
//若tyhp_epoll_wait被中断则重新调用该函数
if(nfds == - && errno == EINTR)
continue; for(int n = ; n != nfds; ++n)
{
//处理监听套接字触发的事件
if(events[n].data.fd == listen_fd)
{
conn_sock = tyhp_accept(listen_fd, (struct sockaddr*)&client_addr, &addrlen);
//设置新链接上的套接字为非阻塞模式
tyhp_set_nonblocking(conn_sock);
//设置读事件和ET模式
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
//将监听事件加入epoll中
tyhp_epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev);
}
else
{
epollfd_connfd.epollfd = epollfd;
epollfd_connfd.connfd = events[n].data.fd;
ev.data.fd = conn_sock;
//epoll不再监听这个客户端套接字
tyhp_epoll_ctl(epollfd, EPOLL_CTL_DEL, conn_sock, &ev);
//处理链接
pthread_create(&tid, &pthread_attr_detach, &tyhp_thread_func, (void*)&epollfd_connfd);
//tyhp_thread_func((void*)&epollfd_connfd);
//close(conn_sock);
}
}
}
//清理工作
pthread_attr_destroy(&pthread_attr_detach); //关闭监听套接字
close(listen_fd);
return ;
}
/*****************************************************************************************/ /*********************************** 线程相关 *******************************************/
/*
*函数作用:处理客户端链接的线程例程
*函数参数:
*函数返回值: NULL
*/
#define TIMEOUT 1000*60*4 //设置超时 milliseconds
void* tyhp_thread_func(void *param)
{
tyhp_thread_num_add1(); tyhp_http_header_t *phttphdr = tyhp_alloc_http_header(); _epollfd_connfd *ptr_epollfd_connfd = (_epollfd_connfd*)param;
//int epollfd = ptr_epollfd_connfd->epollfd;
//获取客户连接socket
int conn_sock = ptr_epollfd_connfd->connfd; struct epoll_event ev, events[];
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
//针对客户链接的新epollfd
int epollfd = tyhp_epoll_create();
tyhp_epoll_ctl(epollfd, EPOLL_CTL_ADD, ev.data.fd, &ev);
int nfds = ; pthread_t tid = pthread_self();
printf("NO.%u thread runs now !!!\n", (unsigned int)tid); //Nginx默认http请求包大小为1M,所以我也分配1M缓存来存http请求包
char *buff = (char*)tyhp_malloc(ONEMEGA);
bzero(buff, ONEMEGA); //关闭connfd的Nagle算法
tyhp_set_off_tcp_nagle(conn_sock);
//设置接收超时时间为60秒
tyhp_set_recv_timeo(conn_sock, , );
//设置发送超时时间为120秒
//tyhp_set_snd_timeo(connfd, 120, 0);
begin:
int32_t nread = , n = ;
for(;;)
{
if((n = read(conn_sock, buff+nread, ONEMEGA-)) > )
nread += n;
else if( == n)
break;
else if(- == n && errno == EINTR)
continue;
else if(- == n && errno == EAGAIN)
break;
else if(- == n && errno == EWOULDBLOCK)
{
perror("socket read timeout");
goto out;
}
else
{
perror("read http request error");
tyhp_free(buff);
break;
} } if( != nread)
{
string str_http_request(buff, buff + nread); //do_something(str_http_request);
if(!tyhp_parse_http_request(str_http_request, phttphdr))
{
perror("tyhp_parse_http_request: parse str_http_request failed");
goto out;
}
cout<<"解析出来的http请求包:"<<endl;
tyhp_print_http_header(phttphdr); string out;
int http_codes = tyhp_do_http_header(phttphdr, out); /****** debug *****/
cout<<"http响应包:"<<endl<<out<<endl; char *out_buf = (char *)tyhp_malloc(out.size());
if(out_buf == NULL)
goto out;
int i;
for(i = ; i != out.size(); ++i)
out_buf[i] = out[i];
out_buf[i] = '\0';
int nwrite = ; n = ;
if( http_codes == TYHP_BADREQUEST ||
http_codes == TYHP_NOIMPLEMENTED ||
http_codes == TYHP_NOTFOUND ||
(http_codes == TYHP_OK && phttphdr->method == "HEAD"))
{
while((n = write(conn_sock, out_buf + nwrite, i)) != )
{
if(n == - && errno == EINTR)
continue;
else
goto out;
nwrite += n;
}
}
if(http_codes == TYHP_OK)
{
if(phttphdr->method == "GET")
{
while((n = write(conn_sock, out_buf + nwrite, i)) != )
{
cout<<n<<endl;
if(n == - && errno == EINTR)
continue;
else
break;
nwrite += n;
}
string real_url = tyhp_make_real_url(phttphdr->url);
int fd = open(real_url.c_str(), O_RDONLY);
int file_size = tyhp_get_file_length(real_url.c_str());
cout<<"file size "<<file_size<<endl;
int nwrite = ;
cout<<"sendfile : "<<real_url.c_str()<<endl;
again:
if((sendfile(conn_sock, fd, (off_t*)&nwrite, file_size)) < )
perror("sendfile");
if(nwrite < file_size)
goto again;
cout<<"sendfile ok:"<<nwrite<<endl;
}
}
free(out_buf);
//超时4分钟
nfds = tyhp_epoll_wait(epollfd, events, , TIMEOUT);
if( == nfds)//timeout
goto out;
for(int i = ; i < nfds; ++i)
{
if(events[i].data.fd == conn_sock)
goto begin;
else
goto out;
}
} out:
tyhp_free_http_header(phttphdr);
close(conn_sock);
tyhp_thread_num_minus1();
printf("NO.%u thread ends now ~~~\n", (unsigned int)tid);
} /*
*函数作用:tyhp_thread_num原子加1
*函数参数:无
*函数返回值: 无
*/
void tyhp_thread_num_add1()
{
pthread_mutex_lock(&tyhp_thread_num_mutex);
++tyhp_thread_num;
pthread_mutex_unlock(&tyhp_thread_num_mutex);
}
/*
*函数作用:tyhp_thread_num原子减1
*函数参数:无
*函数返回值: 无
*/
void tyhp_thread_num_minus1()
{
pthread_mutex_lock(&tyhp_thread_num_mutex);
--tyhp_thread_num;
pthread_mutex_unlock(&tyhp_thread_num_mutex);
}
/*
*函数作用:tyhp_thread_num原子读
*函数参数:无
*函数返回值: tyhp_thread_num当前值
*/
int32_t tyhp_thread_num_read();
/*****************************************************************************************/ /******************************** tyhp_http_header_t处理函数 *********************************/
/*
*函数作用:根据解析下来的tyhp_http_header_t来处理客户的请求
*函数参数: phttphdr指向要处理的tyhp_http_header_t
out保存了处理的结果,即http响应包
*函数返回值: HTTP状态码 *目前支持的请求首部:
*目前支持的响应首部:Date,Content-Base,Content-Length,Content-Location
Last-Modified,Public,Server
*/
int tyhp_do_http_header(tyhp_http_header_t *phttphdr, string& out)
{
char status_line[] = {};
string crlf("\r\n");
string server("Server: tinyhttp\r\n");
string Public("Public: GET, HEAD\r\n");
string content_base = "Content-Base: " + tyhp_domain + crlf;
string date = "Date:" + tyhp_time_gmt() + crlf; string content_length("Content-Length: ");
string content_location("Content-Location: ");
string last_modified("Last-Modified: ");
//string body(""); if(phttphdr == NULL)
{
snprintf(status_line, sizeof(status_line), "HTTP/1.1 %d %s\r\n",
TYHP_BADREQUEST, tyhp_get_state_by_codes(TYHP_BADREQUEST));
out = status_line + crlf;
return TYHP_BADREQUEST;
} string method = phttphdr->method;
string real_url = tyhp_make_real_url(phttphdr->url);
string version = phttphdr->version;
if(method == "GET" || method == "HEAD")
{
if(tyhp_is_file_existed(real_url.c_str()) == -)
{
snprintf(status_line, sizeof(status_line), "HTTP/1.1 %d %s\r\n",
TYHP_NOTFOUND, tyhp_get_state_by_codes(TYHP_NOTFOUND));
out += (status_line + server + date + crlf);
return TYHP_NOTFOUND;
}
else
{
int len = tyhp_get_file_length(real_url.c_str());
snprintf(status_line, sizeof(status_line), "HTTP/1.1 %d %s\r\n",
TYHP_OK, tyhp_get_state_by_codes(TYHP_OK));
out += status_line;
snprintf(status_line, sizeof(status_line), "%d\r\n", len);
out += content_length + status_line;
out += server + content_base + date;
out += last_modified + tyhp_get_file_modified_time(real_url.c_str()) + crlf + crlf;
}
}
else if(method == "PUT")
{
snprintf(status_line, sizeof(status_line), "HTTP/1.1 %d %s\r\n",
TYHP_NOIMPLEMENTED, tyhp_get_state_by_codes(TYHP_NOIMPLEMENTED));
out += status_line + server + Public + date + crlf;
return TYHP_NOIMPLEMENTED;
}
else if(method == "DELETE")
{
snprintf(status_line, sizeof(status_line), "HTTP/1.1 %d %s\r\n",
TYHP_NOIMPLEMENTED, tyhp_get_state_by_codes(TYHP_NOIMPLEMENTED));
out += status_line + server + Public + date + crlf;
return TYHP_NOIMPLEMENTED;
}
else if(method == "POST")
{
snprintf(status_line, sizeof(status_line), "HTTP/1.1 %d %s\r\n",
TYHP_NOIMPLEMENTED, tyhp_get_state_by_codes(TYHP_NOIMPLEMENTED));
out += status_line + server + Public + date + crlf;
return TYHP_NOIMPLEMENTED;
}
else
{
snprintf(status_line, sizeof(status_line), "HTTP/1.1 %d %s\r\n",
TYHP_BADREQUEST, tyhp_get_state_by_codes(TYHP_BADREQUEST));
out = status_line + crlf;
return TYHP_BADREQUEST;
} return TYHP_OK;
}
/*
#define TYHP_CONTINUE 100 //收到了请求的起始部分,客户端应该继续请求 #define TYHP_OK 200 //服务器已经成功处理请求
#define TYHP_ACCEPTED 202 //请求已接受,服务器尚未处理 #define TYHP_MOVED 301 //请求的URL已移走,响应应该包含Location URL
#define TYHP_FOUND 302 //请求的URL临时移走,响应应该包含Location URL
#define TYHP_SEEOTHER 303 //告诉客户端应该用另一个URL获取资源,响应应该包含Location URL
#define TYHP_NOTMODIFIED 304 //资源未发生变化 #define TYHP_BADREQUEST 400 //客户端发送了一条异常请求
#define TYHP_FORBIDDEN 403 //服务器拒绝请求
#define TYHP_NOTFOUND 404 //URL未找到 #define TYHP_ERROR 500 //服务器出错
#define TYHP_NOIMPLEMENTED 501 //服务器不支持当前请求所需要的某个功能
#define TYHP_BADGATEWAY 502 //作为代理或网关使用的服务器遇到了来自响应链中上游的无效响应
#define TYHP_SRVUNAVAILABLE 503 //服务器目前无法提供请求服务,过一段时间后可以恢复 /*
*函数作用:通过HTTP状态码返回友好语句
*函数参数:HTTP状态码
*函数返回值: 相应的语句
*/
char *tyhp_get_state_by_codes(int http_codes)
{
switch (http_codes)
{
case TYHP_OK:
return tyhp_ok;
case TYHP_BADREQUEST:
return tyhp_badrequest;
case TYHP_FORBIDDEN:
return tyhp_forbidden;
case TYHP_NOTFOUND:
return tyhp_notfound;
case TYHP_NOIMPLEMENTED:
return tyhp_noimplemented;
default:
break;
} return NULL;
}
/*****************************************************************************************/
 /*
*Author Zou Xiao hang
*Email 1210603696@qq.com
*File Name parse.cpp
*Date 2013/10/05
*/
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <map>
#include <utility>
#include <sstream>
#include <ctype.h>
#include <iostream> using namespace std; typedef map<string, string> tyhp_header; //#define string::npos REQUEST_END
#define make_tyhp_header(key, value) \
make_pair((key), (value)) //保存从http request解析下来的值
typedef struct _tyhp_http_header_t
{
string method;
string url;
string version; tyhp_header header; string body;
}tyhp_http_header_t; /*
*函数作用:打印tyhp_http_header_t里的header
*函数参数:tyhp_header 的const 引用
*函数返回值: 无
*/
void tyhp_print_http_header_header(const tyhp_header& head);
/*
*函数作用:打印tyhp_http_header_t
*函数参数:tyhp_http_header_t指针
*函数返回值: 无
*/
void tyhp_print_http_header(tyhp_http_header_t *phttphdr); /*
*函数作用:分配内存给tyhp_http_header_t
*函数参数:无
*函数返回值: NULL表示分配失败,其他值表示成功
*/
tyhp_http_header_t *tyhp_alloc_http_header();
/*
*函数作用:回收分配给tyhp_http_header_t的内存
*函数参数:tyhp_http_header_t指针
*函数返回值: 无
*/
void tyhp_free_http_header(tyhp_http_header_t *phttphdr);
/*
*函数作用:解析http_request
*函数参数:http_request为待解析的值,phttphdr保存了解析下来的值
*函数返回值: true表示解析成功,false表示解析失败
*/
bool tyhp_parse_http_request(const string& http_request, tyhp_http_header_t *phttphdr);
/*
*函数作用:根据key的值在phttphdr所指向的tyhp_http_header_t中查找相对应的值
*函数参数:key为关键字,header
*函数返回值: -返回空值表示查找失败,否则返回相应的值
*/
string tyhp_get_value_from_http_header(const string& key, const tyhp_header& header);
 /*
*Author Zou Xiao hang
*Email 1210603696@qq.com
*File Name parse.h
*Date 2013/10/05
*/
#include "parse.h" /*
*函数作用:打印tyhp_http_header_t里的header
*函数参数:tyhp_header 的const 引用
*函数返回值: 无
*/
void tyhp_print_http_header_header(const tyhp_header& head)
{
if(!head.empty())
{
tyhp_header::const_iterator cit = head.begin();
while(cit != head.end())
{
cout<<cit->first<<":"<<cit->second<<endl;
++cit;
}
}
}
/*
*函数作用:打印tyhp_http_header_t
*函数参数:tyhp_http_header_t指针
*函数返回值: 无
*/
void tyhp_print_http_header(tyhp_http_header_t *phttphdr)
{
if(NULL == phttphdr)
{
perror("phttphdr == NULL");
return ;
} cout<<phttphdr->method<<" "<<phttphdr->url<<" "<<phttphdr->version<<endl;
tyhp_print_http_header_header(phttphdr->header);
cout<<endl<<phttphdr->body<<endl;
} /*
*函数作用:分配内存给tyhp_http_header_t
*函数参数:无
*函数返回值: NULL表示分配失败,其他值表示成功
*/
tyhp_http_header_t *tyhp_alloc_http_header()
{
tyhp_http_header_t *phttphdr = (tyhp_http_header_t *)new tyhp_http_header_t;
if(phttphdr == NULL)
{
perror("tyhp_alloc_http_header");
exit(-);
}
return phttphdr;
} /*
*函数作用:回收分配给tyhp_http_header_t的内存
*函数参数:tyhp_http_header_t指针
*函数返回值: 无
*/
void tyhp_free_http_header(tyhp_http_header_t *phttphdr)
{
if(phttphdr == NULL)
return ;
delete phttphdr;
} /*
*函数作用:解析http_request
*函数参数:http_request为待解析的值,phttphdr保存了解析下来的值
*函数返回值: true表示解析成功,false表示解析失败
*/
bool tyhp_parse_http_request(const string& http_request, tyhp_http_header_t *phttphdr)
{
if(http_request.empty())
{
perror("tyhp_parse_http_request: http_request is empty");
return false;
}
if(phttphdr == NULL)
{
perror("tyhp_parse_http_request: phttphdr is NULL");
return false;
} string crlf("\r\n"), crlfcrlf("\r\n\r\n");
int prev = , next = ; //解析http请求包的起始行
if((next = http_request.find(crlf, prev)) != string::npos)
{
string first_line(http_request.substr(prev, next - prev));
prev = next;
stringstream sstream(first_line);
sstream >> (phttphdr->method);
sstream >> (phttphdr->url);
sstream >> (phttphdr->version);
}
else
{
perror("tyhp_parse_http_request: http_request has not a \\r\\n");
return false;
} //查找"\r\n\r\n"的位置
int pos_crlfcrlf = http_request.find(crlfcrlf, prev);
if(pos_crlfcrlf == string::npos)
{
perror("tyhp_parse_http_request: http_request has not a \"\r\n\r\n\"");
return false;
} //解析首部行
string buff, key, value;
while()
{
next = http_request.find(crlf, prev+); //如果找到的next不超过"\r\n\r\n"的位置
if(next <= pos_crlfcrlf)
{
//buff保存了一行
buff = http_request.substr(prev + , next - prev - );
int end = ;
//跳过前置空白符,到达首部关键字的起始位置
for(; isblank(buff[end]); ++end)
;
int beg = end;
//到达首部关键字的结束位置
for(; buff[end] != ':' && !isblank(buff[end]); ++end)
;
key = buff.substr(beg, end - beg);
//跳至首部值的起始位置
for(; (!isalpha(buff[end]) && !isdigit(buff[end])); ++end)
;
beg = end;
//到达首部值的结束位置
for(; next != end; ++end)
;
value = buff.substr(beg, end - beg);
phttphdr->header.insert(make_tyhp_header(key, value)); prev = next;
}
else
{
break;
}
} //获取http请求包的实体值(一般情况下不存在)
phttphdr->body = http_request.substr(pos_crlfcrlf + , http_request.size() - pos_crlfcrlf - ); return true;
} /*
*函数作用:根据key的值在phttphdr所指向的tyhp_http_header_t中查找相对应的值
*函数参数:key为关键字,header
*函数返回值: -返回空值表示查找失败,否则返回相应的值
*/
string tyhp_get_value_from_http_header(const string& key, const tyhp_header& header)
{
if(header.empty())
return "";
tyhp_header::const_iterator cit = header.find(key);
if(cit == header.end())
return "";
return (*cit).second;
}
 /*
*Author Zou Xiao hang
*Email 1210603696@qq.com
*File Name utility.h
*Date 2013/10/05
*/
#ifndef _UTILITY_H_
#define _UTILITY_H_ #include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/epoll.h>
#include <strings.h>
#include <string>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <utility>
#include <fstream>
#include <sstream>
#include <map>
#include <iostream>
#include <string.h>
#include <pthread.h>
#include <netinet/tcp.h>
#include <time.h>
#include <sys/stat.h> using namespace std; extern string tyhp_docroot;
#define TYHP_DOCROOT 1
extern string tyhp_domain;
#define TYHP_DOMAIN 2 /*********************************** 项目实用工具函数 *******************************************/
/*
*函数作用:得到系统时间
*函数参数:无
*函数返回值: 系统时间 例如:Fri, 22 May 2009 06:07:21 GMT
*/
string tyhp_time_gmt();
/*
*函数作用:根据http请求包中的url和配置文件中的docroot配置选项构造真正的url
*函数参数:url
*函数返回值: 真正的url(绝对路径)
*/
string tyhp_make_real_url(const string& url);
/*
*函数作用:测试文件是否存在
*函数参数:path为绝对路径+文件名
*函数返回值: -1表示文件不存在,其他值表示文件存在
*/
inline int tyhp_is_file_existed(const char *path)
{
int ret = open(path, O_RDONLY | O_EXCL);
close(ret);
return ret;
}
/*
*函数作用:获得文件长度
*函数参数:path为绝对路径+文件名
*函数返回值: 文件长度
*/
int tyhp_get_file_length(const char *path);
/*
*函数作用:获得文件最后修改时间
*函数参数:path为绝对路径+文件名
*函数返回值: 文件最后修改时间
*/
string tyhp_get_file_modified_time(const char *path);
/*
*函数作用:初始化全局变量tyhp_config_keyword_map,必须在使用tyhp_config_keyword_map前调用此函数
*函数参数:无
*函数返回值: 无
*/
void tyhp_init_config_keyword_map();
/*
*函数作用:解析配置文件
*函数参数:path为绝对路径+文件名
*函数返回值: -1表示解析失败,0代表解析成功
*/
int tyhp_parse_config(const char *path);
/*
*函数作用:设置文件描述符为非阻塞模式
*函数参数:要设置的描述符
*函数返回值: 无
*/
void tyhp_set_nonblocking(int fd);
/*
*函数作用:设置套接字SO_REUSEADDR选项
*函数参数:要设置的套接字
*函数返回值: 无
*/
void tyhp_set_reuse_addr(int sockfd);
/*
*函数作用:开启套接字TCP_NODELAY选项,关闭nagle算法
*函数参数:要设置的套接字
*函数返回值: 无
*/
void tyhp_set_off_tcp_nagle(int sockfd);
/*
*函数作用:关闭套接字TCP_NODELAY选项,开启nagle算法
*函数参数:要设置的套接字
*函数返回值: 无
*/
void tyhp_set_on_tcp_nagle(int sockfd);
/*
*函数作用:开启套接字TCP_CORK选项
*函数参数:要设置的套接字
*函数返回值: 无
*/
void tyhp_set_on_tcp_cork(int sockfd);
/*
*函数作用:关闭套接字TCP_CORK选项
*函数参数:要设置的套接字
*函数返回值: 无
*/
void tyhp_set_off_tcp_cork(int sockfd);
/*
*函数作用:设置套接字SO_RCVTIMEO选项,接收超时
*函数参数:sockfd要设置的套接字, sec秒, usec毫秒
*函数返回值: 无
*/
void tyhp_set_recv_timeo(int sockfd, int sec, int usec);
/*
*函数作用:设置套接字SO_SNDTIMEO选项,发送超时
*函数参数:sockfd要设置的套接字, sec秒, usec毫秒
*函数返回值: 无
*/
void tyhp_set_snd_timeo(int sockfd, int sec, int usec);
/******************************************************************************************/ /*********************************** 系统函数的包裹函数 *******************************************/
int tyhp_socket(int domain, int type, int protocol);
void tyhp_listen(int sockfd, int backlog);
void tyhp_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int tyhp_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
struct servent* tyhp_getservbyname(const char *name, const char *proto);
int tyhp_epoll_create(int size);
void tyhp_epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int tyhp_epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); void *tyhp_calloc(size_t nmemb, size_t size);
void *tyhp_malloc(size_t size);
void tyhp_free(void *ptr);
/******************************************************************************************/
#endif
 /*
*Author Zou Xiao hang
*Email 1210603696@qq.com
*File Name utility.cpp
*Date 2013/10/05
*/
#include "utility.h" //存储解析文件的键值对,例如key=docroot value=/home/zxh/desktop/code/webserver
map<string, int> tyhp_config_keyword_map; /*********************************** 项目实用工具函数 *******************************************/
/*
*函数作用:得到系统时间
*函数参数:无
*函数返回值: 系统时间 例如:Fri, 22 May 2009 06:07:21 GMT
*/
string tyhp_time_gmt()
{
time_t now;
struct tm *time_now;
string str_time; time(&now);
time_now = localtime(&now); switch(time_now->tm_wday)
{
case :
str_time += "Sun, ";
break;
case :
str_time += "Mon, ";
break;
case :
str_time += "Tue, ";
break;
case :
str_time += "Wed, ";
break;
case :
str_time += "Thu, ";
break;
case :
str_time += "Fri, ";
break;
case :
str_time += "Sat, ";
break;
}
char buf[];
snprintf(buf, sizeof(buf), "%d ", time_now->tm_mday);
str_time += string(buf);
switch(time_now->tm_mon)
{
case :
str_time += "Jan ";
break;
case :
str_time += "Feb ";
break;
case :
str_time += "Mar ";
break;
case :
str_time += "Apr ";
break;
case :
str_time += "May ";
break;
case :
str_time += "Jun ";
break;
case :
str_time += "Jul ";
break;
case :
str_time += "Aug ";
break;
case :
str_time += "Sep ";
break;
case :
str_time += "Oct ";
break;
case :
str_time += "Nov ";
break;
case :
str_time += "Dec ";
break;
}
snprintf(buf, sizeof(buf), "%d", time_now->tm_year + );
str_time += string(buf);
snprintf(buf, sizeof(buf), " %d:%d:%d ", time_now->tm_hour, time_now->tm_min, time_now->tm_sec);
str_time += string(buf); str_time += "GMT"; return str_time;
}
/*
*函数作用:根据http请求包中的url和配置文件中的docroot配置选项构造真正的url
*函数参数:url
*函数返回值: 真正的url(绝对路径)
*/
string tyhp_make_real_url(const string& url)
{
string real_url, url2; int n = ; if((n = url.find(tyhp_domain, )) != string::npos)//url中包含域名,要将其删去
url2 = url.substr(tyhp_domain.size(), url.size() - tyhp_domain.size());
else
url2 = url; if(tyhp_docroot[tyhp_docroot.size() - ] == '/')//配置项docroot末尾有'/'
{
if(url2[] == '/')
real_url = tyhp_docroot + url2.erase(, );
else
real_url = tyhp_docroot + url2;
}
else//配置项docroot末尾没有'\'
{
if(url2[] == '/')
real_url = tyhp_docroot + url2;
else
real_url = tyhp_docroot + '/' + url2;
} return real_url;
}
/*
*函数作用:获得文件长度
*函数参数:path为绝对路径+文件名
*函数返回值: 文件长度
*/
int tyhp_get_file_length(const char *path)
{
struct stat buf;
int ret = stat(path, &buf);
if(ret == -)
{
perror("tyhp_get_file_length");
exit(-);
}
return (int)buf.st_size;
}
/*
*函数作用:获得文件最后修改时间
*函数参数:path为绝对路径+文件名
*函数返回值: 文件最后修改时间
*/
string tyhp_get_file_modified_time(const char *path)
{
struct stat buf;
int ret = stat(path, &buf);
if(ret == -)
{
perror("tyhp_get_file_length");
exit(-);
}
char array[] = {};
snprintf(array, sizeof(array), "%s", ctime(&buf.st_mtime));
return string(array, array + strlen(array));
}
/*
*函数作用:初始化全局变量tyhp_config_keyword_map,必须在使用tyhp_config_keyword_map前调用此函数
*函数参数:无
*函数返回值: 无
*/
void tyhp_init_config_keyword_map()
{
tyhp_config_keyword_map.insert(make_pair("docroot", TYHP_DOCROOT));
tyhp_config_keyword_map.insert(make_pair("domain", TYHP_DOMAIN));
}
/*
*函数作用:解析配置文件
*函数参数:path为绝对路径+文件名
*函数返回值: -1表示解析失败,0代表解析成功
*/
int tyhp_parse_config(const char *path)
{
tyhp_init_config_keyword_map();
int ret = ;
fstream infile(path, fstream::in);
string line, word;
if(!infile)
{
printf("%s can't open\n", path);
infile.close();
return -;
}
while(getline(infile, line))
{
stringstream stream(line);
stream >> word;//keyword
map<string, int>::const_iterator cit= tyhp_config_keyword_map.find(word);
if(cit == tyhp_config_keyword_map.end())
{
printf("can't find keyword\n");
infile.close();
return -;
}
switch (cit->second)
{
case TYHP_DOCROOT:
stream >> tyhp_docroot;
break;
case TYHP_DOMAIN:
stream >> tyhp_domain;
break;
default :
infile.close();
return -;
}
}
infile.close();
return ;
}
/*
*函数作用:设置文件描述符为非阻塞模式
*函数参数:要设置的描述符
*函数返回值: 无
*/
void tyhp_set_nonblocking(int fd)
{
int flags = fcntl(fd, F_GETFL, );
if(flags < )
{
perror("fcntl: F_GETFL");
exit(-);
}
flags |= O_NONBLOCK;
int ret = fcntl(fd, F_SETFL, flags);
if(ret < )
{
perror("fcntl");
exit(-);
}
}
/*
*函数作用:设置套接字SO_REUSEADDR选项
*函数参数:要设置的套接字
*函数返回值: 无
*/
void tyhp_set_reuse_addr(int sockfd)
{
int on = ;
int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if(ret == -)
{
perror("setsockopt: SO_REUSEADDR");
exit(-);
}
}
/*
*函数作用:开启套接字TCP_NODELAY选项,关闭nagle算法
*函数参数:要设置的套接字
*函数返回值: 无
*/
void tyhp_set_off_tcp_nagle(int sockfd)
{
int on = ;
int ret = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
if(ret == -)
{
perror("setsockopt: TCP_NODELAY ON");
exit(-);
}
}
/*
*函数作用:关闭套接字TCP_NODELAY选项,开启nagle算法
*函数参数:要设置的套接字
*函数返回值: 无
*/
void tyhp_set_on_tcp_nagle(int sockfd)
{
int off = ;
int ret = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &off, sizeof(off));
if(ret == -)
{
perror("setsockopt: TCP_NODELAY OFF");
exit(-);
}
}
/*
*函数作用:开启套接字TCP_CORK选项
*函数参数:要设置的套接字
*函数返回值: 无
*/
void tyhp_set_on_tcp_cork(int sockfd)
{
int on = ;
int ret = setsockopt(sockfd, SOL_TCP, TCP_CORK, &on, sizeof(on));
if(ret == -)
{
perror("setsockopt: TCP_CORK ON");
exit(-);
}
}
/*
*函数作用:关闭套接字TCP_CORK选项
*函数参数:要设置的套接字
*函数返回值: 无
*/
void tyhp_set_off_tcp_cork(int sockfd)
{
int off = ;
int ret = setsockopt(sockfd, SOL_TCP, TCP_CORK, &off, sizeof(off));
if(ret == -)
{
perror("setsockopt: TCP_CORK OFF");
exit(-);
}
}
/*
*函数作用:设置套接字SO_RCVTIMEO选项,接收超时
*函数参数:sockfd要设置的套接字, sec秒, usec毫秒
*函数返回值: 无
*/
void tyhp_set_recv_timeo(int sockfd, int sec, int usec)
{
struct timeval time= {sec, usec};
int ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &time, sizeof(time));
if(ret == -)
{
perror("setsockopt: SO_RCVTIMEO");
exit(-);
}
}
/*
*函数作用:设置套接字SO_SNDTIMEO选项,发送超时
*函数参数:sockfd要设置的套接字, sec秒, usec毫秒
*函数返回值: 无
*/
void tyhp_set_snd_timeo(int sockfd, int sec, int usec)
{
struct timeval time= {sec, usec};
int ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &time, sizeof(time));
if(ret == -)
{
perror("setsockopt: SO_SNDTIMEO");
exit(-);
}
}
/******************************************************************************************/ /*********************************** 系统函数的包裹函数 *******************************************/
int tyhp_socket(int domain, int type, int protocol)
{
int listen_fd;
if((listen_fd = socket(AF_INET, SOCK_STREAM, )) == -)
{
perror("socket");
exit(-);
}
return listen_fd;
}
void tyhp_listen(int sockfd, int backlog)
{
if(listen(sockfd, backlog) == -)
{
perror("listen");
exit(-);
}
}
void tyhp_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
if(bind(sockfd, addr, addrlen) == -)
{
perror("bind");
exit(-);
}
}
int tyhp_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
int ret_fd = ;
for(;;)
{
ret_fd = accept(sockfd, addr, addrlen);
if(ret_fd > )
break;
else if(ret_fd == -)
{
//由于我们把监听套接字设置为了非阻塞模式
if(errno != EAGAIN && errno != EPROTO &&
errno != EINTR && errno != ECONNABORTED)
{
perror("accept");
exit(-);
}
}
else
continue;
}
return ret_fd;
}
struct servent* tyhp_getservbyname(const char *name, const char *proto)
{
struct servent *pservent;
if((pservent = getservbyname(name, proto)) == NULL)
{
perror("getservbyname");
exit(-);
}
return pservent;
}
int tyhp_epoll_create(int size)
{
int epollfd;
epollfd = epoll_create(size);
if(- == epollfd)
{
perror("epoll_create");
exit(-);
}
return epollfd;
}
void tyhp_epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
{
if(epoll_ctl(epfd, op, fd, event) == -)
{
perror("epoll_ctl");
exit(-);
}
}
int tyhp_epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
{
again:
int nfds = epoll_wait(epfd, events, maxevents, timeout);
if(nfds == -) //&& errno != EINTR)
{
if(errno != EINTR)
{
perror("epoll_wait");
exit(-);
}
else
goto again;
}
return nfds;
} void *tyhp_calloc(size_t nmemb, size_t size)
{
void *ptr = calloc(nmemb, size);
if(NULL == ptr)
{
perror("tyhp_calloc");
exit(-);
}
return ptr;
}
void *tyhp_malloc(size_t size)
{
void *ptr = malloc(size);
if(NULL == ptr)
{
perror("tyhp_malloc");
exit(-);
}
return ptr;
}
void tyhp_free(void *ptr)
{
free(ptr);
}
/******************************************************************************************/

[作业向]tinyhttp web服务器设计及完整代码的更多相关文章

  1. reactor模式前序(二):NIO WEB服务器设计

    前文介绍了传统IO的WEB经典服务器 reactor模式前序:传统IO的WEB服务器设计 下面看看JAVA NIO的WEB服务器设计 NIO是基于事件驱动的,对于NIO来说,重要组件是Selector ...

  2. reactor模式前序:传统IO的WEB服务器设计

    先看一段经典的WEB JAVA服务器设计 JAVA代码为(伪代码) 1 ServerSocket serverSocket = ...; 2 serverSocket.bind(8899); 3 4 ...

  3. C/C++编程日记:用C语言实现的简单Web服务器(Linux),全代码分享!

    相信大家对Apache都有所听闻,Apache是目前使用最为广泛我Web服务器.大家可以从news.netcraft.com/这个网站得到证实. 这是腾讯的uptime.netcraft.com/up ...

  4. Web服务精讲–搭个 Web 服务器(二)

    导读 曾几何时,你所选择的 Python Web 框架会限制你所可选择的 Web 服务器,反之亦然.如果某个框架及服务器设计用来协同工作的,那么一切正常. 在第一部分中,我提出了一个问题:“如何在你刚 ...

  5. Tomcat源码分析 (一)----- 手写一个web服务器

    作为后端开发人员,在实际的工作中我们会非常高频地使用到web服务器.而tomcat作为web服务器领域中举足轻重的一个web框架,又是不能不学习和了解的. tomcat其实是一个web框架,那么其内部 ...

  6. node 创建静态web服务器(下)(处理异步获取数据的两种方式)

    接上一章. 上一章我们说创建的静态web服务器只能识别html,css,js文件,功能较为单一,且图片格式为text/html,这是不合理的. 本章,我们将解决该问题. 这里,我们先准备好一个json ...

  7. 静态Web服务器(py版)

    近来,对http协议进行了研究,闲来无事.自己使用python3写了个静态Web服务器,以下是代码: static_Web_sever.py ''' 思路:首先使用socket创建tcp服务器,照旧绑 ...

  8. Web API设计方法论--比较完整的web api 开发过程

    为Web设计.实现和维护API不仅仅是一项挑战:对很多公司来说,这是一项势在必行的任务.本系列将带领读者走过一段旅程,从为API确定业务用例到设计方法论,解决实现难题,并从长远的角度看待在Web上维护 ...

  9. web服务器原理(作业四)

      Web服务器简介:Web服务器是指驻留于因特网上某种类型计算机的程序.当web浏览器(客户端)连到服务器上并请求文件时,服务器将处理该请求并将文件发送到该浏览器上,附带的信息会告诉浏览器如何查看该 ...

随机推荐

  1. css伪元素选择器

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  2. Delphi的VCL组件库

    Visual Component Library的缩写(可视组件库)VCL是Visual Component Library的缩写,即可视组件库,它是Delphi,C++Builder等编程语言的基本 ...

  3. ThinkPHP中关于JS文件如何添加类似__PUBLIC__图片路径

    在对html样式进行优化的时候,经常会用到Js/jquery进行一些跳转切换的样式,而我们常做的就是在Js/jquery代码中嵌url图片链接代码,以实现动态交互的页面效果. 如下图所示:

  4. [推荐]WebService开发知识介绍

    [推荐]WebService开发知识介绍 WebService开发手册  http://wenku.baidu.com/view/df3992ce050876323112128a.html WebSe ...

  5. vs2012 MSDN帮助文档离线包下载安装方法

    vs2012安装文件 自带的 MSDN帮助文档不全, 需要自己手动添加需要的离线文档包, 具体方法如下 1. 打开 vs2012 2. 按 ctrl + alt + F1 打开帮助文档管理器 3. 在 ...

  6. AngularJS中的http拦截

    $http服务允许我们与服务端交互,有时候我们希望在发出请求之前以及收到响应之后做些事情.即http拦截. $httpProvider包含了一个interceptors的数组. 我们这样创建一个int ...

  7. Android SDK Tools Platform-tools Build-tools

    (1)Android SDK (Android SDK主安装包,包含SDK Manager.AVD Manager.工具包tools,释放后的根文件夹为android-sdk-windows): re ...

  8. Hermes实时检索分析平台

    一.序言 随着TDW的发展,公司在大数据离线分析方面已经具备了行业领先的能力.但是,很多应用场景往往要求在数秒内完成对几亿.几十亿甚至几百上千亿的数据分析,从而达到不影响用户体验的目的.如何能够及时有 ...

  9. Sublime Text 3103 Crack 破解 注册码(亲测有效)

    随机复制下面的几四个注册码 粘贴到sublime text 3(Build 3103)注册框 就可以了! 第一个--first licence key : ====================== ...

  10. 解决部份机型toast不显示问题

    问题:部份机型不显示toast 解决方案: 1.自己在设置里面去允许通知,但是显然客户会说别的app都可以,so 2.自定义解决.查看toast的源码发现其附着在window上 源码下载地址:http ...