跨平台web服务器 misv
[i=s] 本帖最后由 happy886rr 于 2017-8-18 18:35 编辑 [/i]图片均为外部链接,不上传任何附件,使用源码发行。
[quote]对HTTP协议粗略看了下,于是就有了这个跨平台web服务器。支持普通的GET、HEAD协议,其他协议将会适当修改,对动态语言如php的支持将会在未来通过修改php源码做成动态加载,敬请期待。代码做到了跨平台编译、兼容gcc、VS,能在linux、win、安卓、各种小型嵌入式上可靠运行。为了取得最好的执行效果,不提供二进制文件、请源码编译。
.
演示效果:编译misv.c,在手机上创建服务器,整个网站跑在misv服务器上,通过电脑端访问手机服务器,可实现1000并发,380多RPS。单台手机服务器可满足日均300万PV点击率。
[img]http://i4.bvimg.com/604745/992cb9d37fcac4f3.jpg[/img][/quote]
用法:直接双击即可,网站根目录请自行修改HTTP_SERVER_ROOT的值,网站端口请修改HTTP_SERVER_PORT。
原创代码(使用了大量宏定义、可跨平台编译):[code]/*
CONSOLE MINI HTTP SERVER, COPYRIGHT@2017~2019 BY HAPPY, VERSION 1.0
MISV.EXE
FRI AUG 18 2017 18:16:01 GMT+0800
////////////////////////////////////////////////////////////////////
说明:
跨平台代码,可在Windows、Linux、安卓等多种嵌入式设备上直译。
兼容GCC、VS编译器。 如使用VS请先改名为misv.cpp方可正常编译。
编译时,可通过定义USE_FAST_MODE、USE_SLEEP_MODE 宏实现优化。
编译:
gcc misv.c -lWs2_32 -o misv.exe REM Windows下编译
gcc misv.c -lpthread -o misv REM Linux下编译
gcc misv.c REM 安卓及嵌入式编译
cl misv.cpp /MD /Ox /out:misv.exe REM VS下编译
////////////////////////////////////////////////////////////////////
*/
////////////////////////////////////////////////////////////////////
// 服务器名称
#define HTTP_SERVER_NAME "MISV"
// 服务器端口
#define HTTP_SERVER_PORT 80
// 服务器根目录WWW
#define HTTP_SERVER_ROOT "."
// 服务器默认目录主页
#define HTTP_SERVER_HOMEPAGE "index.html"
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
// 是否 启用 高速响应
#define USE_FAST_MODE
// 是否 启用 智能休眠
#define USE_SLEEP_MODE
////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#if defined(WIN32) || defined(_WIN32) || defined(__MINGW32__) || defined(_MSC_VER)
#include <direct.h>
#include <Winsock2.h>
#else
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#endif
#if defined(_MSC_VER)
#pragma comment(lib,"Ws2_32.lib")
#else
#include <stdbool.h>
#endif
// 队列等待最大长度(不超过5)
#define MAX_BACKLOG_SIZE 5
// 服务器最小休眠冲击量(MIN-Requests per second)
#define MIN_HTTP_RPS 1
// 最大字元长
#define MAX_BUFF_SIZE 1024*4
// 最小字元长
#define MIN_BUFF_SIZE 128
// 文件发送错误
#define FILE_SEND_ERROR -1
// 文件发送错误
#define HTTP_GENERAL_ERROR 1
// 文件路径阈值
#define MAX_PATH_SIZE 1024
#if defined(USE_FAST_MODE)
// 定义VS控制台启动方式(对VS编译器,改后缀为.cpp编译时,实现无窗化启动)
#if defined(_MSC_VER)
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#endif
#endif
// 定义服务器默认路径分隔符 "\" 或 "/"
#if defined(WIN32) || defined(_WIN32) || defined(__MINGW32__) || defined(_MSC_VER)
#define HTTP_SERVER_PATHCHARACTER "\\"
#else
#define HTTP_SERVER_PATHCHARACTER "/"
// 定义宏名错误值
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
// 跨平台数据类型
typedef int SOCKET;
typedef unsigned long DWORD;
typedef void *LPVOID;
typedef const struct sockaddr *LPSOCKADDR;
#ifndef byte
typedef unsigned char byte;
#endif
#endif
#ifndef NULL
#define NULL (void*)0
#endif
// 跨平台宏函数
#if defined(WIN32) || defined(_WIN32) || defined(__MINGW32__) || defined(_MSC_VER)
#define GETCWD _getcwd
#define SLEEP(x) Sleep(x)
#define CLOSESOCKET closesocket
#else
#define GETCWD getcwd
#define SLEEP(x) usleep(x*1000)
#define CLOSESOCKET close
#endif
// 判断小写字母宏函数
#define ISLOWERLETTER(x) ('a'<=(x)&&(x)<='z')
// 声明 服务器当前目录
char currentPath[MAX_PATH_SIZE];
// HTTP协议关键词
static const char* HTTP_KEY_WORDS[] = {"GET", "POST", "HEAD", "EDIT", NULL};
// 定义404返回页面
static const char responsePage404[] =
"<html>"
"<head>"
"</head>"
"<title>404 Not Found</title>"
"<body>"
" <h1 align='center'><font color=#FF1493>Sorry,404 Error!</font></h1>"
" <script>"
" document.write('<hr/>' + '<font color=#9400D3>' + new Date() + '</font>' + '<hr/>' + '<font color=#A9A9A9>' + 'File not found.' + '</font>');"
" </script>"
"</body>"
"</html>";
static const int responsePage404Strlen = sizeof(responsePage404) / sizeof(char);
// 通用 关键词识别函数
int HTTP_IdentifyKey(char* inStr, char** inKeyWords, const char* endChars)
{
int SN = 0;
while(inKeyWords[SN] != NULL)
{
char *op=inStr, *kp=inKeyWords[SN];
while(*kp != '\0')
{
if(
((ISLOWERLETTER(*op))?(*op-32):(*op)) != ((ISLOWERLETTER(*kp))?(*kp-32):(*kp))
)
{
break;
}
op++;
kp++;
}
if(*kp == '\0')
{
if(*op == '\0')
{
return SN;
}
while(*endChars != '\0')
{
if(*op == *(endChars++))
{
return SN;
}
}
}
SN ++;
}
return -1;
}
// 错误打印函数
int HTTP_PrintError(char* perr)
{
#if defined(WIN32) || defined(_WIN32) || defined(__MINGW32__) || defined(_MSC_VER)
fprintf(stderr, "%s: %d\n", perr, WSAGetLastError());
#else
perror(perr);
#endif
return 0;
}
// 发送200报头
int HTTP_Response200(SOCKET soc, int contentLength)
{
char* responseBuf = (char*)malloc(MIN_BUFF_SIZE * sizeof(char));
sprintf
(
responseBuf
,
"HTTP/1.1 200 OK\r\n"
"Server: %s\r\n"
"Connection: keep-alive\r\n"
"Content-Type: text/html\r\n"
"Content-Length: %d\r\n\r\n"
,
HTTP_SERVER_NAME, contentLength
);
send(soc, responseBuf, strlen(responseBuf), 0);
free(responseBuf);
return 0;
}
// 发送404报头
int HTTP_Response404(SOCKET soc)
{
char* responseBuf = (char*)malloc(MIN_BUFF_SIZE * sizeof(char));
sprintf
(
responseBuf
,
"HTTP/1.1 404 NOT FOUND\r\n"
"Server: %s\r\n"
"Connection: keep-alive\r\n"
"Content-Type: text/html\r\n\r\n"
,
HTTP_SERVER_NAME
);
send(soc, responseBuf, strlen(responseBuf), 0);
free(responseBuf);
return 0;
}
// 发送请求的资源
int HTTP_SendFile(SOCKET soc, FILE *fp, bool removeUTF8BOM)
{
// 重置文件流位置
rewind(fp);
// 分配数据拾取器
byte* pickUpBuf = (byte*)malloc(MAX_BUFF_SIZE * sizeof(byte*));
size_t freadSize = 0;
if(removeUTF8BOM)
{
// 去除UTF8 BOM头 (必要的兼容性处理)
freadSize = fread(pickUpBuf, sizeof(byte), 3, fp);
if(freadSize == 3)
{
if(
(*(pickUpBuf+0) != 0xEF) ||
(*(pickUpBuf+1) != 0xBB) ||
(*(pickUpBuf+2) != 0xBF)
)
{
rewind(fp);
}
}
}
// 发送文件二进制数据
while (!feof(fp))
{
freadSize = fread(pickUpBuf, sizeof(byte), MAX_BUFF_SIZE, fp);
if (send(soc, (const char*)pickUpBuf, freadSize, 0) == SOCKET_ERROR)
{
#ifndef USE_FAST_MODE
HTTP_PrintError("Send file Failed");
#endif
return FILE_SEND_ERROR;
}
}
free(pickUpBuf);
return 0;
}
// 服务器核心函数
DWORD
#if defined(WIN32) || defined(_WIN32) || defined(__MINGW32__) || defined(_MSC_VER)
WINAPI
#endif
HTTP_ServerCore(LPVOID lpvsoc)
{
DWORD ret = 0;
SOCKET soc = (SOCKET)lpvsoc;
// 分配客户请求接受容器
char* receiveBuf = (char*)malloc(MAX_BUFF_SIZE * sizeof(char));
int receiveCount = recv(soc, receiveBuf, MAX_BUFF_SIZE, 0);
if (receiveCount == SOCKET_ERROR)
{
#ifndef USE_FAST_MODE
HTTP_PrintError("Receive failed");
#endif
ret = HTTP_GENERAL_ERROR;
goto JMP_END;
}
else
{
// 置结束符 '\0'
receiveBuf[receiveCount]='\0';
#ifndef USE_FAST_MODE
// 接收成功,打印客户请求报文
fprintf(stdout, "Receive data from client: \n%s\n", receiveBuf);
#endif
}
// 解析 HTTP协议
char* httpMethod = strtok(receiveBuf, " \t");
if (httpMethod == NULL)
{
#ifndef USE_FAST_MODE
fprintf(stdout, "Receive request data error\n");
#endif
ret = HTTP_GENERAL_ERROR;
goto JMP_END;
}
// 获取 客户端HTTP请求 协议值
int httpMethodValue = HTTP_IdentifyKey(httpMethod, (char**)HTTP_KEY_WORDS, " \t");
// 对于 不支持的协议,则断开连接
if (httpMethodValue == -1)
{
#ifndef USE_FAST_MODE
fprintf(stdout, "Not support this method '%s'\n", httpMethod);
#endif
ret = HTTP_GENERAL_ERROR;
goto JMP_END;
}
// 解析 URL地址
char* httpURL = strtok(NULL, " \t");
if (httpURL == NULL)
{
#ifndef USE_FAST_MODE
fprintf(stdout, "Receive data error\n");
#endif
ret = HTTP_GENERAL_ERROR;
goto JMP_END;
}
// 解析 请求文件路径
char* httpFilePath = strchr(httpURL, '/');
if (httpFilePath == NULL)
{
httpFilePath = httpURL;
}
#if defined(WIN32) || defined(_WIN32) || defined(__MINGW32__) || defined(_MSC_VER)
else
{
char* p = httpFilePath;
while (*p != '\0')
{
if (*p == '/')
{
*p = (HTTP_SERVER_PATHCHARACTER)[0];
}
p++;
}
}
#endif
#ifndef USE_FAST_MODE
// 输出 客户端请求摘要
fprintf
(
stdout
,
"HTTP method: %s\n"
"File svpath: %s\n"
,
httpMethod
,
httpFilePath
);
#endif
// 拼接 请求文件路径
char* requestFilePath = (char*)malloc(MAX_PATH_SIZE * sizeof(char));
sprintf
(
requestFilePath
,
"%s%s%s%s%s"
,
currentPath, HTTP_SERVER_PATHCHARACTER, HTTP_SERVER_ROOT, HTTP_SERVER_PATHCHARACTER, httpFilePath
);
// 如果请求的是目录,则跳转到该目录主页“index.html”
int httpFilePathLen = strlen(httpFilePath);
if (*(httpFilePath + httpFilePathLen - 1) == (HTTP_SERVER_PATHCHARACTER)[0])
{
strcat(requestFilePath, HTTP_SERVER_HOMEPAGE);
}
// 以二进制方式 打开服务器文件流
FILE* fp = fopen(requestFilePath, "rb");
// 如果 读取文件失败
if (fp == NULL)
{
// 如果是HEAD方法,则发送404报头
if (httpMethodValue == 2)
{
HTTP_Response404(soc);
}
// 如果是 GET方法,则发送404页面
else if (httpMethodValue == 0)
{
send(soc, responsePage404, responsePage404Strlen, 0);
}
#ifndef USE_FAST_MODE
// 打印 网站404错误,并执行跳转
fprintf(stdout, "File not found.\n");
#endif
ret = HTTP_GENERAL_ERROR;
goto JMP_END;
}
// 测量 要请求的文件长度
fseek(fp, 0, SEEK_SET);
fseek(fp, 0, SEEK_END);
int fpSize = ftell(fp);
// 如果是HEAD方法,则发送200报头
if (httpMethodValue == 2)
{
HTTP_Response200(soc, fpSize);
goto JMP_END;
}
// 如果是GET方法则发送请求的资源
if (httpMethodValue == 0)
{
#ifndef USE_FAST_MODE
// 显示 要请求的文件长度
fprintf(stdout, "File length: %d\n", fpSize);
#endif
if (HTTP_SendFile(soc, fp, true) != FILE_SEND_ERROR)
{
#ifndef USE_FAST_MODE
fprintf(stdout, "File send OK\n");
}
else
{
fprintf(stdout, "File send failed\n");
#endif
}
}
fclose(fp);
JMP_END:
// 释放 套接口
CLOSESOCKET(soc);
#ifndef USE_FAST_MODE
// 显示 标尾信息
fprintf
(
stdout
,
"Close socket(%d)\n"
"[<==]\n"
,
soc
);
#endif
free(receiveBuf);
free(requestFilePath);
return ret;
}
// MAIN主函数
int main()
{
// 获取服务器 当地时间
time_t timeNow = time(&timeNow);
char* nowTime = ctime(&timeNow);
// 显示 标头信息
fprintf
(
stdout,
"%s"
"[===* Welcome to use %s *===]\n"
"[PORT: %d]\n"
"[>>>]\n"
,
nowTime
,
HTTP_SERVER_NAME
,
HTTP_SERVER_PORT
);
//获取当前文件所在目录
GETCWD(currentPath, MAX_PATH_SIZE);
#if defined(WIN32) || defined(_WIN32) || defined(__MINGW32__) || defined(_MSC_VER)
// 启动 安全套接字
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
{
fprintf(stdout, "Failed to create wsocket\n");
return HTTP_GENERAL_ERROR;
}
#endif
// 创建 连接套接口、监听套接口
SOCKET soc, socListen;
socListen =socket(AF_INET, SOCK_STREAM, 0);
if(socListen == INVALID_SOCKET)
{
HTTP_PrintError("Creat listen socket failed");
return HTTP_GENERAL_ERROR;
}
// 声明 服务器地址,客户端地址
struct sockaddr_in serverSockaddrIn, clientSockaddrIn;
// 配置 服务器信息
serverSockaddrIn.sin_family = AF_INET;
serverSockaddrIn.sin_port = htons(HTTP_SERVER_PORT);
serverSockaddrIn.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定 监听套接口 与 服务器地址
if (bind(socListen, (LPSOCKADDR)&serverSockaddrIn, sizeof(serverSockaddrIn)) == SOCKET_ERROR)
{
HTTP_PrintError("Blind server failed");
return HTTP_GENERAL_ERROR;
}
// 通过 监听套接口 监听
if (listen(socListen, MAX_BACKLOG_SIZE) == SOCKET_ERROR)
{
HTTP_PrintError("Listen socket failed");
return HTTP_GENERAL_ERROR;
}
// 声明 计时器变量
clock_t preClock = clock();
int countTimes = 0;
int requestsPerSecond = 1000;
// 监听 网页资源请求
while(true)
{
int clientSockaddrInLen = sizeof(clientSockaddrIn);
// 返回 客户连接套接口
soc = accept(socListen, (struct sockaddr*)&clientSockaddrIn, &clientSockaddrInLen);
if (soc == INVALID_SOCKET)
{
HTTP_PrintError("Accept failed");
break;
}
#if defined(WIN32) || defined(_WIN32) || defined(__MINGW32__) || defined(_MSC_VER)
// 创建 请求线程
DWORD ThreadID;
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)HTTP_ServerCore, (LPVOID)soc, 0, &ThreadID);
#else
pthread_t newPthreadT;
pthread_create(&newPthreadT, NULL, (void*)HTTP_ServerCore, (void*)(intptr_t)soc);
#endif
#if defined(USE_SLEEP_MODE)
// 计次器
countTimes ++;
// 每隔300毫秒检测一次服务器冲击量
if (clock()-preClock > CLOCKS_PER_SEC*0.3)
{
requestsPerSecond = CLOCKS_PER_SEC * countTimes / (clock()-preClock);
countTimes = 0;
preClock = clock();
}
// 如果冲击量过小,则休眠CPU占用率
if (requestsPerSecond < MIN_HTTP_RPS)
{
SLEEP(3);
}
#endif
}
// 关闭 安全套接口
CLOSESOCKET(socListen);
#if defined(WIN32) || defined(_WIN32) || defined(__MINGW32__) || defined(_MSC_VER)
// 关闭 安全套接字
WSACleanup();
#endif
return 0;
}
[/code] GCC编译失败
不过VC成功了
感谢分享,好东西~ [i=s] 本帖最后由 523066680 于 2017-8-18 19:53 编辑 [/i]
[b]回复 [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=202285&ptid=45083]2#[/url] [i]老刘1号[/i] [/b]
有没有加 -lws2_32,我这边 mingw gcc 编译通过 [b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=202285&ptid=45083]2#[/url] [i]老刘1号[/i] [/b]
misv只有10kb左右,随便扔一个目录,双击运行,这个目录就成网站了。比如你电脑存了几十部电影,那么把misv.exe扔到电脑目录,直接双击运行。 你就可以在任意一部手机上在线观看你电脑上的所有电影。或者你把你的地址告诉其他人, 那么别人也能通过互联网直接在线浏览你电脑里的视频。 [i=s] 本帖最后由 happy886rr 于 2017-8-18 21:15 编辑 [/i]
目前几乎支持所有格式文件的浏览。但做了安全限定,只能访问misv当前目录文件。 最多支持 1000人同时在线观看。后续版本会加入缓存机制,加入服务器管理员机制,加入增删改查功能,加入一种新的msp动态脚本语言。 [i=s] 本帖最后由 老刘1号 于 2017-8-18 23:48 编辑 [/i]
[b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=202287&ptid=45083]3#[/url] [i]523066680[/i] [/b]
这个确实没有……
我的疏忽…… [quote]目前几乎支持所有格式文件的浏览。但做了安全限定,只能访问misv当前目录文件。 最多支持 1000人同时在线观 ...
[size=2][color=#999999]happy886rr 发表于 2017-8-18 21:13[/color] [url=http://www.bathome.net/redirect.php?goto=findpost&pid=202295&ptid=45083][img]http://www.bathome.net/images/common/back.gif[/img][/url][/size][/quote]
http文件预览不是主要跟浏览器相关吗?
服务器端除了报头返回了Content-Type:text/html 也没做什么吧
结果还不是靠浏览器自己的MIME配置来预览。 [quote]回复 老刘1号
misv只有10kb左右,随便扔一个目录,双击运行,这个目录就成网站了。比如你电脑存了几十部 ...
[size=2][color=#999999]happy886rr 发表于 2017-8-18 21:10[/color] [url=http://www.bathome.net/redirect.php?goto=findpost&pid=202294&ptid=45083][img]http://www.bathome.net/images/common/back.gif[/img][/url][/size][/quote]
为什么在我这里访问 1.avi 就只是提示下载文件呢? [b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=202448&ptid=45083]8#[/url] [i]xxbdh[/i] [/b]
HTML5的video标签好像只支持mp4格式,IE的只支持特殊制式的mp4,其他的格式还需要解码器播放。浏览器并不是播放器,不可能各种视频格式直接播放。
这个misv已经淘汰了,我的新版msp比这个版本更强大,增加抗DDOS攻击,各类安全模式,多并发优化,能抵御1万5000并发,单天800万次访问量,各项指标已略超apache,总代码量超过1000行,目前还在测试阶段,好吧,我一会发布,你去看新帖就行。 基础html/text的webserver很好些,我能写到1~3kb,
真没什么意义。
那个,对支持asp的研究过嘛。不用asp.dll的isapi实现。
我网上看了一套代码,mfc写的,支持application,session等六个内置对象。
但是复杂一点的asp解析就会出错。
不想用netbox,非常想写一个媲美netbox的aspserver lite [i=s] 本帖最后由 老刘1号 于 2021-5-8 19:04 编辑 [/i]
最近折腾路由器,想挂个静态站,想起了这个帖子,
编译一切正常(编译器是mips-linux-musl-gcc),而且也能跑起来,
但是服务器无法访问,挺奇怪,隔壁msp就可以 [i=s] 本帖最后由 slimay 于 2021-5-14 00:29 编辑 [/i]
[b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=238642&ptid=45083]11#[/url] [i]老刘1号[/i] [/b]
那就直接用msp吧,核心代码都一样, 只是一些地方做了兼容改进, 支持跨平台编译.
对了, 你的mips版本gcc哪弄的?
页:
[1]