C 实现一个简易的Http服务器
引言
做一个老实人挺好的,至少还觉得自己挺老实的.
再分享一首 自己喜欢的诗人的一首 情景诗. 每个人总会有问题,至少喜欢就好,
本文 参照
http 协议 http://www.cnblogs.com/rayray/p/3729533.html
html格式 http://blog.csdn.net/allenjy123/article/details/7375029
tinyhttpd 源码 https://github.com/EZLippi/Tinyhttpd
附录 本文最后完稿的资源
httpd 源码打包 http://download.csdn.net/detail/wangzhione/9461441
通过本文练习, 至少会学会 Linux上fork用法, pipe管道用法0读1写, pthread用法等.
其它的都是业务解析内容.
前言
讲的不好望见谅, 因为很多东西需要自己去写一写就有感悟了. 看懂源码和会改源码是两码事. 和 会优化更不同了.
凡事多练习. 不懂也都懂了. 我们先说一下总的结构.
client.c 是一个简易的 测试 http请求的客户端
httpd.c 使我们重点要说的 小型简易的Linux上的http服务器
index.html 测试网页 是client.c 想请求的网页
Makefile 编译文件.
这里先总的展示一下 httpd.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h> // --------------------------------------- 辅助参数宏 ----------------------------------------------
/*
* c 如果是空白字符返回 true, 否则返回false
* c : 必须是 int 值,最好是 char 范围
*/
#define sh_isspace(c) \
((c==' ')||(c>='\t'&&c<='\r')) //4.0 控制台打印错误信息, fmt必须是双引号括起来的宏
#define CERR(fmt, ...) \
fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
__FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__) //4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量
#define CERR_EXIT(fmt,...) \
CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE) //4.3 if 的 代码检测
#define IF_CHECK(code) \
if((code) < ) \
CERR_EXIT(#code) // --------------------------------------- 辅助变量宏 和 声明 ------------------------------------------ // char[]缓冲区大小
#define _INT_BUF (1024)
// listen监听队列的大小
#define _INT_LIS (7) /*
* 读取文件描述符 fd 一行的内容,保存在buf中,返回读取内容长度
* fd : 文件描述符
* buf : 保存的内容
* sz : buf 的大小
* : 返回读取的长度
*/
int getfdline(int fd, char buf[], int sz); // 返回400 请求解析失败,客户端代码错误
extern inline void response_400(int cfd); // 返回404 文件内容, 请求文件没有找见
extern inline void response_404(int cfd); // 返回501 错误, 不支持的请求
extern inline void response_501(int cfd); // 服务器内部错误,无法处理等
extern inline void response_500(int cfd); // 返回200 请求成功 内容, 后面可以加上其它参数,处理文件输出
extern inline void response_200(int cfd); /*
* 将文件 发送给客户端
* cfd : 客户端文件描述符
* path : 发送的文件路径
*/
void response_file(int cfd, const char* path); /*
* 返回启动的服务器描述符(句柄), 这里没有采用8080端口,防止冲突,用了随机端口
* pport : 输出参数和输出参数, 如果传入NULL,将不返回自动分配的端口
* : 返回 启动的文件描述符
*/
int serstart(uint16_t* pport); /*
* 在客户端链接过来,多线程处理的函数
* arg : 传入的参数, 客户端文件描述符 (int)arg
* : 返回处理结果,这里默认返回 NULL
*/
void* request_accept(void* arg); /*
* 处理客户端的http请求.
* cfd : 客户端文件描述符
* path : 请求的文件路径
* type : 请求类型,默认是POST,其它是GET
* query : 请求发送的过来的数据, url ? 后面那些数据
*/
void request_cgi(int cfd, const char* path, const char* type, const char* query); /*
* 主逻辑,启动服务,可以做成守护进程.
* 具体的实现逻辑, 启动小型玩乐级别的httpd 服务
*/
int main(int argc, char* argv[])
{
pthread_attr_t attr;
uint16_t port = ;
int sfd = serstart(&port); printf("httpd running on port %u.\n", port);
// 初始化线程属性
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
for(;;){
pthread_t tid;
struct sockaddr_in caddr;
socklen_t clen = sizeof caddr;
int cfd = accept(sfd, (struct sockaddr*)&caddr, &clen);
if(cfd < ){
CERR("accept sfd = %d is error!", sfd);
break;
}
if(pthread_create(&tid, &attr, request_accept, (void*)cfd) < )
CERR("pthread_create run is error!");
}
// 销毁吧, 一切都结束了
pthread_attr_destroy(&attr);
close(sfd);
return ;
} // ----------------------------------------- 具体的函数实现过程 ------------------------------------------------ /*
* 读取文件描述符 fd 一行的内容,保存在buf中,返回读取内容长度
* fd : 文件描述符
* buf : 保存的内容
* sz : buf 的大小
* : 返回读取的长度
*/
int
getfdline(int fd, char buf[], int sz)
{
char* tp = buf;
char c; --sz;
while((tp-buf)<sz){
if(read(fd, &c, ) <= ) //伪造结束条件
break;
if(c == '\r'){ //全部以\r分割
if(recv(fd, &c, , MSG_PEEK)> && c == '\n')
read(fd, &c, );
else //意外的结束,填充 \n 结束读取
*tp++ = '\n';
break;
}
*tp++ = c;
}
*tp = '\0';
return tp - buf;
} // 返回400 请求解析失败,客户端代码错误
inline void
response_400(int cfd)
{
const char* estr = "HTTP/1.0 400 BAD REQUEST\r\n"
"Server: wz simple httpd 1.0\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<p>你的请求有问题,请检查语法!</p>\r\n"; write(cfd, estr, strlen(estr));
} // 返回404 文件内容, 请求文件没有找见
inline void
response_404(int cfd)
{
const char* estr = "HTTP/1.0 404 NOT FOUND\r\n"
"Server: wz simple httpd 1.0\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<html>"
"<head><title>你请求的界面被查水表了!</title></head>\r\n"
"<body><p>404: 估计是回不来了</p></body>"
"</html>"; //开始发送数据
write(cfd, estr, strlen(estr));
} // 返回501 错误, 请求解析失败,不支持的请求
inline void
response_501(int cfd)
{
const char* estr = "HTTP/1.0 501 Method Not Implemented\r\n"
"Server: wz simple httpd 1.0\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<html>"
"<head><title>小伙子不要乱请求</title></head>\r\n"
"<body><p>too young too simple, 年轻人别总想弄出个大新闻.</p></body>"
"</html>"; //这里还有一个好的做法是将这些内容定义在文件中输出文件
write(cfd, estr, strlen(estr));
} // 服务器内部错误,无法处理等
inline void
response_500(int cfd)
{
const char* estr = "HTTP/1.0 500 Internal Server Error\r\n"
"Server: wz simple httpd 1.0\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<html>"
"<head><title>Sorry </title></head>\r\n"
"<body><p>最近有点方了!</p></body>"
"</html>"; write(cfd, estr, strlen(estr));
} // 返回200 请求成功 内容, 后面可以加上其它参数,处理文件输出
inline void
response_200(int cfd)
{
// 打印返回200的报文头
const char* str = "HTTP/1.0 200 OK\r\n"
"Server: wz simple httpd 1.0\r\n"
"Content-Type: text/html\r\n"
"\r\n"; write(cfd, str, strlen(str));
} /*
* 将文件 发送给客户端
* cfd : 客户端文件描述符
* path : 发送的文件路径
*/
void
response_file(int cfd, const char* path)
{
FILE* txt;
char buf[_INT_BUF]; // 读取报文头,就是过滤
while(getfdline(cfd, buf, sizeof buf)> && strcmp("\n", buf))
;
// 这里开始处理 文件内容
if((txt = fopen(path, "r")) == NULL) //文件解析错误,给它个404
response_404(cfd);
else{
response_200(cfd); //发送给200的报文头过去
// 先判断文件内容存在
while(!feof(txt) && fgets(buf, sizeof buf, txt))
write(cfd, buf, strlen(buf));
}
fclose(txt);
} /*
* 返回启动的服务器描述符(句柄)
* pport : 输出参数和输出参数, 如果传入NULL,将不返回自动分配的端口
* : 返回 启动的文件描述符
*/
int
serstart(uint16_t* pport)
{
int sfd;
struct sockaddr_in saddr = { AF_INET }; IF_CHECK(sfd = socket(PF_INET, SOCK_STREAM, ));
saddr.sin_port = !pport || !*pport ? : htons(*pport);
saddr.sin_addr.s_addr = INADDR_ANY;
// 绑定一下端口信息
IF_CHECK(bind(sfd, (struct sockaddr*)&saddr, sizeof saddr));
if(pport && !*pport){
socklen_t clen = sizeof saddr;
IF_CHECK(getsockname(sfd, (struct sockaddr*)&saddr, &clen));
*pport = ntohs(saddr.sin_port);
}
// 开启监听任务
IF_CHECK(listen(sfd, _INT_LIS));
return sfd;
} /*
* 在客户端链接过来,多线程处理的函数
* arg : 传入的参数, 客户端文件描述符 (int)arg
* : 返回处理结果,这里默认返回 NULL
*/
void*
request_accept(void* arg)
{
char buf[_INT_BUF], path[_INT_BUF>>], type[_INT_BUF>>];
char *lt, *rt, *query, *nb = buf;
struct stat st;
int iscgi, cfd = (int)arg; if(getfdline(cfd, buf, sizeof buf) <= ){ //请求错误
response_501(cfd);
close(cfd);
return NULL;
}
// 合法请求处理
for(lt=type, rt=nb; !sh_isspace(*rt) && (lt-type)< sizeof type - ; *lt++ = *rt++)
;
*lt = '\0'; //已经将 buf中开始不为empty 部分塞入了 type 中
//同样处理合法与否判断, 出错了直接返回错误结果
if((iscgi = strcasecmp(type, "POST")) && strcasecmp(type, "GET")){
response_501(cfd);
close(cfd);
return NULL;
}
// 在buf中 去掉空字符
while(*rt && sh_isspace(*rt))
++rt;
// 这里得到路径信息
*path = '.';
for(lt = path + ; (lt-path)<sizeof path - && !sh_isspace(*rt); *lt++ = *rt++)
;
*lt = '\0'; //query url路径就拼接好了 //单独处理 get 获取 ? 后面数据, 不是POST那就是GET
if(iscgi != ){
for(query = path; *query && *query != '?'; ++query)
;
if(*query == '?'){
iscgi = ;
*query++ = '\0';
}
} // type , path 和 query 已经构建好了
if(stat(path, &st) < ){
while(getfdline(cfd, buf, sizeof buf)> && strcmp("\n", buf))// 读取内容直到结束
;
response_404(cfd);
close(cfd);
return NULL;
}
// 合法情况, 执行,写入,读取权限
if ((st.st_mode & S_IXUSR) ||(st.st_mode & S_IXGRP) ||(st.st_mode & S_IXOTH))
iscgi = ;
if(iscgi) //没有cgi
response_file(cfd, path);
else
request_cgi(cfd, path, type, query); close(cfd);
return NULL;
} /*
* 处理客户端的http请求.
* cfd : 客户端文件描述符
* path : 请求的文件路径
* type : 请求类型,默认是POST,其它是GET
* query : 请求发送的过来的数据, url ? 后面那些数据
*/
void
request_cgi(int cfd, const char* path, const char* type, const char* query)
{
char buf[_INT_BUF];
int pocgi[], picgi[];
pid_t pid;
int contlen = -; //报文长度
char c; if(strcasecmp(type, "POST") == ){
while(getfdline(cfd, buf, sizeof buf)> && strcmp("\n", buf)){
buf[] = '\0';
if(!strcasecmp(buf, "Content-Length:"))
contlen = atoi(buf + );
}
if(contlen == -){ //错误的报文,直接返回错误结果
response_400(cfd);
return;
}
}
else{ // 读取报文头,就是过滤, 后面就假定是 GET
while(getfdline(cfd, buf, sizeof buf)> && strcmp("\n", buf))
;
} //这里处理请求内容, 先处理错误信息
if(pipe(pocgi) < ){
response_500(cfd);
return;
}
if(pipe(picgi) < ){ //管道 是 0读取, 1写入
close(pocgi[]), close(pocgi[]);
response_500(cfd);
return;
}
if((pid = fork())<){
close(pocgi[]), close(pocgi[]);
close(picgi[]), close(picgi[]);
response_500(cfd);
return;
}
// 这里就是多进程处理了, 先处理子进程
if(pid == ) {
// dup2 让 前者共享后者同样的文件表
dup2(pocgi[], STDOUT_FILENO); //标准输出算作 pocgi管道 的写入端
dup2(picgi[], STDIN_FILENO); //标准输入做为picgif管道的读取端
close(pocgi[]);
close(pocgi[]); // 添加环境变量,用于当前会话中
sprintf(buf, "REQUEST_METHOD=%s", type);
putenv(buf);
// 继续凑环境变量串,放到当前会话种
if(strcasecmp(buf, "POST") == )
sprintf(buf, "CONTENT_LENGTH=%d", contlen);
else
sprintf(buf, "QUERY_STRING=%s", query);
putenv(buf);
// 成功的话调到 新的执行体上
execl(path, path, NULL); // 这行代码原本是不用的, 但是防止 execl执行失败, 子进程没有退出.妙招
exit(EXIT_SUCCESS);
}
// 父进程, 为所欲为了,先发送个OK
write(cfd, "HTTP/1.0 200 OK\r\n", );
close(pocgi[]);
close(picgi[]); if(strcasecmp(type, "POST") == ){
int i; //将数据都写入到 picgi 管道中, 让子进程在 picgi[0]中读取 => STDIN_FILENO
for(i=; i<contlen; ++i){
read(cfd, &c, );
write(picgi[], &c, );
}
}
//从子进程中 读取数据 发送给客户端, 多线程跨进程阻塞模型
while(read(pocgi[], &c, ) > )
write(cfd, &c, ); close(pocgi[]);
close(picgi[]);
//等待子进程结束
waitpid(pid, NULL, );
}
我们看见 上面 函数 解释的很清楚, 对于 response_* 响应部分占了大头的一半.其实本质也就200行左右. 很适合临摹一下.
正文
现在到了正文,说的很水. 再扯一点. 自己学习反人类的库libuv, 就是note.js 底层通信的那套网络库. 也就是看官方的demo
一个个的临摹. 了解的. 也就会用. 后面也就简易的看看源码. 也就懂了. 最经看的深入之后还是觉得,越简单越直白越好.封装太多了,
容易绕晕自己,而且很多功能用不上,遇到bug了又得查看繁琐的万行源码.
总结就是, 学好基础 问题, 走到哪里都容易, 至少能做. 做的好不好, 以后再说.
那我们分析了. 第一个 看下面函数声明
// 返回400 请求解析失败,客户端代码错误
extern inline void response_400(int cfd);
这里使用了C的内联函数, 内联函数声明必须要有inline.否则编译器解析 函数名称会不一致找不见. 再扯一点对于
strcasecmp 其实是 linux上提供的函数 , window上使用需要做额外配置. 说白了就是不跨平台. 下面一种跨平台的实现如下
/*
* 这是个不区分大小写的比较函数
* ls : 左边比较字符串
* rs : 右边比较字符串
* : 返回 ls>rs => >0 ; ls = rs => 0 ; ls<rs => <0
*/
int
str_icmp(const char* ls, const char* rs)
{
int l, r;
if(!ls || !rs)
return (int)(ls - rs); do {
if((l=*ls++)>='a' && l<='z')
l -= 'a' - 'A';
if((r=*rs++)>='a' && r<='z')
r -= 'a' - 'A';
} while(l && l==r); return l-r;
}
参照编译器源码给的一种实现. 性能方面基本上还可以. 这里再扯一点. 为什么C中常说用指针速度快.
分析如下 普通的 a[6] ,访问过程是 先取a首地址,再取a+6地址后面 取*(a+6)的值.
而如果直接用 ptr = a, ++ => ptr -> a+6 那就省略了一步 a+6的问题. 所以快一点.
再扯一点 a[6]其实就是语法糖, 本质也就是 *(a + 6), 通过这个推广, a[-1] 也合法 等价于 *(a - 1).
后面再简单分析一下 细节
我们总的思路是 服务器httpd 采用多线程接收客户端请求. 再分析报文, 主要是分get请求和post请求.
get请求直接请求, 如果get 后面有? 或post请求 走 cgi 动态处理界面.
说白都很简单, http 是在tcp 基础上添加了 http报文的基础解析内容. 本质是业务逻辑的处理.
这里继续说一说 本文中采用的管道细节
//这里处理请求内容, 先处理错误信息
if(pipe(pocgi) < ){
response_500(cfd);
return;
}
if(pipe(picgi) < ){ //管道 是 0读取, 1写入
close(pocgi[]), close(pocgi[]);
response_500(cfd);
return;
}
if((pid = fork())<){
close(pocgi[]), close(pocgi[]);
close(picgi[]), close(picgi[]);
response_500(cfd);
return;
}
这里是请求失败会相应释放打开的端口. 理论上在exit之后系统会自动回收打开的端口.但是不及时.
对于上面管道 是 子进程充定向管道为标准输入输出. 父进程向管道中写入给子进程标准输入输出. 这就是传说的cgi.
最后说明一段代码
/*
* 主逻辑,启动服务,可以做成守护进程.
* 具体的实现逻辑, 启动小型玩乐级别的httpd 服务
*/
int main(int argc, char* argv[])
{
pthread_attr_t attr;
uint16_t port = ;
int sfd = serstart(&port); printf("httpd running on port %u.\n", port);
// 初始化线程属性
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
for(;;){
pthread_t tid;
struct sockaddr_in caddr;
socklen_t clen = sizeof caddr;
int cfd = accept(sfd, (struct sockaddr*)&caddr, &clen);
if(cfd < ){
CERR("accept sfd = %d is error!", sfd);
break;
}
if(pthread_create(&tid, &attr, request_accept, (void*)cfd) < )
CERR("pthread_create run is error!");
}
// 销毁吧, 一切都结束了
pthread_attr_destroy(&attr);
close(sfd);
return ;
}
这是主业务, 亮点在于 pthread_attr 这块, 添加了线程分离属性, 自己回收. 不需要内核继续保存线程尸体.
最后记得释放.
到这里基本细节我们都说完了. 对于 serstart 中采用了随机端口, 是为了不合 服务器可能的http服务8080端口冲突, 就来个随机端口.
对于socket 采用0端口,意思就是操作系统随机分配.
测试
下面我们开始测试测试 的 client.c 代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> //4.0 控制台打印错误信息, fmt必须是双引号括起来的宏
#define CERR(fmt, ...) \
fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
__FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__) //4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量
#define CERR_EXIT(fmt,...) \
CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE) //4.3 if 的 代码检测
#define IF_CHECK(code) \
if((code) < ) \
CERR_EXIT(#code) //待拼接的字符串
#define _STR_HTTP_1 "GET /index.html HTTP/1.0\r\nUser-Agent: Happy is good.\r\nHost: 127.0.0.1:"
#define _STR_HTTP_3 "\r\nConnection: close\r\n\r\n" // 简单请求一下
int main(int argc, char* argv[])
{
char buf[];
int sfd;
struct sockaddr_in saddr = { AF_INET };
int len, port;
// argc 默认为1 第一个参数 就是 执行程序串
if((argc != ) || (port=atoi(argv[])) <= )
CERR_EXIT("Usage: %s [port]", argv[]); // 开始了,就这样了
IF_CHECK(sfd = socket(PF_INET, SOCK_STREAM, ));
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = INADDR_ANY;
IF_CHECK(connect(sfd, (struct sockaddr*)&saddr, sizeof saddr)); //开始发送请求
strcpy(buf, _STR_HTTP_1);
strcat(buf, argv[]);
strcat(buf, _STR_HTTP_3);
write(sfd, buf, strlen(buf)); //读取所哟内容
while((len = read(sfd, buf, sizeof buf - ))){
buf[len] = '\0';
printf("%s", buf);
}
putchar('\n'); close(sfd);
return ;
}
这里就简单向httpd 发送get 请求 index.html界面. 这里再扯一点, 这个httpd 许多细节没有考虑,容错性不是那么健全.
这些都好做,只要理解了实现思路和详细了解HTTP协议就可以写出好的HTTP知识,当然TCP的功底不可或缺,这点也很有挑战.
对于index.html 界面如下
<html>
<head>
<title> 有意思 </title>
</head>
<body>
<p> 只有野兽不会欺骗 <p>
</body>
</html>
最后上 Makefile
all:httpd.out client.out httpd.out:httpd.c
gcc -g -Wall -o $@ $^ -lpthread
client.out:client.c
gcc -g -Wall -o $@ $^
最后执行结果示意图图如下,先启动 httpd服务器
后面开启http测试机, 需要输入端口34704 如下
到这里我们至少简单测试都过了.
一切都是那么自然而然. 前提你要个节奏,这个你能坚持. 节奏很重要, 装逼是次要的.下次有机会再分享
开发中需要用到的一些开发模型和细节. 或者分享简单高效的网络库知识. 最后扯一点, 都是从不懂,一点都不懂
坚持临摹开始的.后面就懂了, 只有不懂和痛苦,恶心才会有点意思.哈哈.
后记
错误是难免的, 欢迎交流, 拜~~~
C 实现一个简易的Http服务器的更多相关文章
- C 实现一个简易的Http服务器 (二)
正文 - 直接搞起 C 实现一个简易的Http服务器 很久以前写过一个简易的http服务器, 后面和一个朋友交流, 反思后发现问题不少.在这里简单搞一下. 让其更加简单去表现httpd本质, 弱化协议 ...
- Tinywebserver:一个简易的web服务器
这是学习网络编程后写的一个练手的小程序,可以帮助复习I/O模型,epoll使用,线程池,HTTP协议等内容. 程序代码是基于<Linux高性能服务器编程>一书编写的. 首先回顾程序中的核心 ...
- day-1 用python编写一个简易的FTP服务器
从某宝上购买了一份<Python神经网络深度学习>课程,按照视频教程,用python语言,写了一个简易的FTP服务端和客户端程序,以前也用C++写过聊天程序,编程思路差不多,但是pytho ...
- 在Android中实现一个简易的Http服务器
最近遇到一个需求需要在App中创建一个Http服务器供供浏览器调用,用了下开源的微型Htpp服务器框架:NanoHttpd,项目地址:https://github.com/NanoHttpd/nano ...
- python -m http.server 搭建一个简易web下载服务器
在打vulnhub靶场的时候遇到的一个问题 目录 一.进到需要发送的安装包目录 二.开启http服务 三.访问服务器 一.进到需要发送的安装包目录 比如设置一个专门发送,传输的文件的文件夹,cmd命令 ...
- 局域网 FTP建立,搭建一个简易的局域网服务器
1.创建用户名以及密码: 右键我的电脑 -> 管理->本地用户和组->右键用户->新用户----设置用户名密码: 2.安装IIS 和FTP :控制面板->程序->打 ...
- python3 使用http.server模块 搭建一个简易的http服务器
from http.server import HTTPServer, BaseHTTPRequestHandler import json data = {'result': 'this is a ...
- 使用Python创建一个简易的Web Server
Python 2.x中自带了SimpleHTTPServer模块,到Python3.x中,该模块被合并到了http.server模块中.使用该模块,可以快速创建一个简易的Web服务器. 我们在C:\U ...
- go 语言实现一个简单的 web 服务器
学习Go语言的一些感受,不一定准确. 假如发生战争,JAVA一般都是充当航母战斗群的角色.一旦出动,就是护卫舰.巡洋舰.航母舰载机.预警机.电子战飞机.潜艇等等浩浩荡荡,杀将过去.(JVM,数十个JA ...
随机推荐
- android NDk环境编译总结
首先,这篇文章的撰写是基于很多前人的优秀的帖子,感谢他们的分享让我能够学习这么多的知识.谢谢 Android NDK开发环境的搭建 前言: Android 上,应用程序的开发,大部分基于 Java 语 ...
- 2014款Macbook Air安装单独X64 Win7系统
之所以写出来,是因为网上大多是用BootCamp安装双系统的,安装单独Win7的教程少之又少,然后大多数还写得不清不楚,所以折腾了一阵子.其实装好之后,还是觉得挺简单的. 我主要参考了两篇文章,链接如 ...
- GUI创建各常用控件(二)
继续接着上一篇! 在我看来有一点需要申明:由于是GUI的相关知识,所以我只是在复习中粗略的总结而已,因此参考价值可能有限,更多的是当作自己学习的一个记录以及便于自己查阅. 好啦!干货继续: 1.类似于 ...
- VR就是下一个浪潮_2016 (GMGC) 全球移动游戏大会观后感
"VR就是下一个浪潮" --2016 (GMGC) 全球移动游戏大会观后感 早在2014年参会Unity举办的一年一度的金立方盛典大会,就初次体验了VR头盔设备,于是印象深刻 ...
- Editplus 注册码
EditPlus 是一款功能强大的文字处理软件.它可以充分的替换记事本,它也提供网页作家及程序设计师许多强悍的功能.支持 HTML.CSS.PHP.ASP.Perl.C/C++.Java.JavaSc ...
- Openstack-Mitaka Ceilometer 中使用 SNMP 监控真实物理机
Ceilometer 是 Openstack 的监控管理计费模块,我所用的版本为 Mitaka 版本.在 Ceilometer 中,可以使用 SNMP 监控服务器的实时硬件资源信息. 系统环境为 Ce ...
- ASPxGridView中批量提交及个别提交的写法
//获取chech box ID protected string GetProtoID() { string protoId = ""; //获取选中的记录Id List< ...
- PHP获取今天、昨天、明天的日期
<?php echo "今天:".date("Y-m-d")."<br>"; echo "昨天:".d ...
- Oracle笔记 目录索引
Oracle笔记 一.oracle的安装.sqlplus的使用 Oracle笔记 二.常用dba命令行 Oracle笔记 三.function .select Oracle笔记 四.增删改.事务 Or ...
- boost实现串口通信(一):小试牛刀
/************************************************************************/ /* 功能:boost实现串口通信类 */ /* ...