YC服务器是可扩展的HTTP服务器,它的源码文件是ychttp.cpp。
使用YSP语言生成的网页或数据可以通过YC服务器传给浏览器及各种客户终端。下面是该服务器的源码。
……
typedef void (*serverCall)(int userSock,const char *headBuf);
struct ychttpCLASS //ychttp.cpp对外接口
{
virtual void _stdcall start_server(int port=80,serverCall aFunc=nullptr);
virtual void _stdcall stop_server();
virtual int _stdcall check_server();
virtual int _stdcall slot_number();
};
……
头文件(部分代码):yc.h
#define YCLEV 5 //编译语法错误提示级别(可去掉)
#define YCICON "water.jpg" //独立运行时的文件图标(可去掉)
#include "yc.h"
#define INDEX_FILE "index.ysp" //主页文件
ychttpCLASS YCHTTP;
char root_path[MAX_PATH]; //主页路径
int root_len; //主页路径长度
void main(void **pObj)
{
if((int)pObj > 1000) //ychttp.cpp被其它应用程序调用时运行的代码
{
*pObj = &YCHTTP; //函数接口对象地址
root_len = strlen(YC_path());
strcpy(root_path,YC_path());
strcat(root_path,INDEX_FILE); //主页文件
if(YC_readfile(root_path,NULL) < 0) //文件不存在
{
strcpy(&root_path[root_len],YC_PATHwww); //使用子目录
root_len = strlen(root_path);
}
else root_path[root_len] = 0;
return;
}
//ychttp.cpp独立运行时的代码
int mport = 81; //端口
YCHTTP.start_server(mport); //启动服务器
printf(YCHTTP.check_server() ? "Web服务器启动成功! port=%d\n"
: "Web服务器未能启动.\n", mport);
char URLptr[8192] = "";
while(1)
{
printf("--------------press 'Q' to exit-----------\n");
printf("\n在浏览器地址栏输入: http://127.0.0.1:%d/%s\n",mport,URLptr);
printf("可浏览文件 %s\n",URLptr);
printf("\n输入文件名: ");
scanf("%s",URLptr); //从控制台输入
if(!strcmpi(URLptr,"q")) break; //是Q字符, 则退出程序
printf("slot_number = %d\n\n",YCHTTP.slot_number());
strlwr(URLptr);
char *mptr = strchr(URLptr,'.');
if(mptr && (strchr(mptr+1,'.') || !strcmp(mptr,".com")
|| !strcmp(mptr,".cn")
|| !strcmp(mptr,".htm")
|| !strcmp(mptr,".ysp")))
{
ycwinCLASS *pwin = < ycwin.cpp >; //在窗口中显示网页
pwin->createWindow( URLptr );
pwin->loop();
}
else
{
char fileName[8192];
sprintf(fileName,"http://127.0.0.1:%d/%s",mport,URLptr);
printf("%s\n",fileName);
YHTML *phtml = YC_htmlLoad(fileName);
int glen;
wchar_t *wibuf = YCHTML->sourceHTML(phtml,&glen);
char *txtbuf = YC_wideToChar(&glen,wibuf,glen);
YCWEB->MessageBoxA(phtml,txtbuf,fileName); //对话框显示文本
free(txtbuf);
YC_htmlFree(phtml);
}
}
YCHTTP.stop_server();
}
enum //本程序能处理的http类型
{
HTTP_none,
HTTP_GET,
HTTP_POST,
HTTP_HEAD,
};
int get_http_method(char *headPtr,int glen)
{
switch(glen)
{
case 4:
if(*(int*)headPtr == *(int*)"POST") return HTTP_POST;
if(*(int*)headPtr == *(int*)"HEAD") return HTTP_HEAD;
break;
case 3:
if(*(int*)headPtr == *(int*)"GET ") return HTTP_GET;
break;
}
return HTTP_none;
}
//处理HTTP请求的函数
void response_proc(int userSock,char *headBuf,int rev_cnt,
int sendLen,int keepAlive,int *server_stop)
{
char *headPtr = strchr(headBuf,' '); //headBuf: "GET /hello.ysp HTTP/1.1..."
if(!headPtr) return;
int m_method = get_http_method(headBuf, headPtr - headBuf);
if(m_method == HTTP_none) return;
headPtr++;
char *spacePtr = strchr(headPtr,' ');
if(!spacePtr) return;
char *questPtr = strchr(headPtr,'?');
char *url_ptr = YCWEB->decode_uri_file(headPtr,
(questPtr ? questPtr : spacePtr) - headPtr);
if(!url_ptr) return;
if(url_ptr[0] != '/') //url_ptr: "/hello.ysp"
{
free(url_ptr);
return;
}
char file_name[2048]; //存放HTTP请求文件名
spacePtr = url_ptr[1] ? &url_ptr[1] : INDEX_FILE;
int kk = strlen(spacePtr);
if(kk > sizeof(file_name)-1-root_len) kk = sizeof(file_name)-1-root_len;
memcpy(file_name,root_path,root_len);
memcpy(&file_name[root_len],spacePtr,kk);
kk += root_len;
file_name[kk] = 0;
char *httphead_buf = nullptr;
char *binary_buf = nullptr;
spacePtr = &file_name[kk-4];
strlwr(spacePtr);
#define STR_VALUE(str) str[0] + (str[1]<<8) + (str[2]<<16) + (str[3]<<24)
int glen = -1;
char *content_type_str;
switch((spacePtr[0]=='.' || kk>5 && spacePtr[-1]=='.') ? *(int*)spacePtr:0)
{
default:
case STR_VALUE(".htm"): //HTTP请求文件名的后缀为.htm
case STR_VALUE("html"): //HTTP请求文件名的后缀为.html
content_type_str = "Content-Type: text/html\r\n";
break;
case STR_VALUE(".jpg"): //HTTP请求文件名的后缀为.jpg
content_type_str = "Content-Type: image/jpg\r\n";
break;
case STR_VALUE(".png"): //HTTP请求文件名的后缀为.png
content_type_str = "Content-Type: image/png\r\n";
break;
case STR_VALUE(".gif"): //HTTP请求文件名的后缀为.gif
content_type_str = "Content-Type: image/gif\r\n";
break;
case STR_VALUE(".bmp"):
case STR_VALUE(".ico"):
content_type_str = "Content-Type: image/bmp\r\n";
break;
case STR_VALUE(".ysp"): //处理 YSP 文件
glen = YCHTML->run_ysp_file(file_name,binary_buf,httphead_buf,
headPtr,headBuf + rev_cnt - headPtr,
questPtr,server_stop);
content_type_str = httphead_buf ? httphead_buf : "";
break;
}
if(glen < 0) glen = YC_readfile(file_name,&binary_buf); //读取非ysp文件数据
char *userData = binary_buf;
char *errbuf = null;
int httpcode = 200;
if(glen < 0) //文件不存在,显示404错误
{
errbuf = (char*)malloc(256);
if(errbuf)
{
snprintf(errbuf,256,`HTTP/1.1 404
<span style='color:rgb(100,10,0);font-size:26px;'>
文件没有找到 : %s</span>`,url_ptr);
httpcode = 404;
userData = errbuf;
glen = strlen(errbuf);
}
else glen = 0;
}
//为了方便在ycs.htm页面中显示HTTP请求和应答信息,将应答信息放在headBuf的后面。
headBuf += rev_cnt;
kk = snprintf(headBuf,sendLen, "\r\n\r\nHTTP/1.1 %d OK\r\n"
"Content-Length: %d\r\n"
"%s"
"%s"
"\r\n",
httpcode, //应答信息类型
glen, //发给客户端的数据的长度
keepAlive ? "Connection: Keep-Alive\r\n" : "",
content_type_str); //设置发送数据
send(userSock,&headBuf[4], kk - 4, 0); //向客户端发送HTTP应答
if(m_method != HTTP_HEAD) send(userSock,userData,glen,0); //向客户端发数据
if(errbuf) free(errbuf); //释放本函数所申请的所有内存
free(url_ptr);
free(binary_buf);
free(httphead_buf);
}
//线程函数,用来接收浏览器或其它客户端发送的数据
unsigned long _stdcall user_request_thread(void *lpParameter)
{
#define MAX_REV_SIZE (2048 + 2)
YSLOT *pSlot = (YSLOT*)lpParameter; //槽口指针,表示服务器和客户端的每次连接
char *headBuf = NULL; //接收数据缓冲区
int rev_cnt,rev_len = 0;
while(1)
{
for(rev_cnt=0; ; ) //数据长度未知,可分多次接收
{
if(rev_cnt + MAX_REV_SIZE > rev_len)
{
if(!YC_realloc(&headBuf, rev_len+MAX_REV_SIZE)) goto tr_p_err;
rev_len += MAX_REV_SIZE; //申请缓冲区内存
}
int kk = recv(pSlot->userSock,&headBuf[rev_cnt],MAX_REV_SIZE-2,0);
if(kk<=0 || pSlot->servc->server_stop) break; //无数据或用户终止程序时
rev_cnt += kk;
headBuf[rev_cnt] = 0;
int postLen = YCWEB->http_get_content_length(headBuf);
if(postLen>0 && postLen != rev_cnt -
(strstr(headBuf,"\r\n\r\n") - headBuf + 4)) continue;
if(kk < MAX_REV_SIZE-2) break; //数据已接收完毕,退出循环
}
if(rev_cnt<=0 || pSlot->servc->server_stop) break; //退出线程
int keepAlive = YCWEB->http_get_keep_alive(headBuf); //是否保持长连接
if(rev_len-rev_cnt < MAX_REV_SIZE/2) //保证headBuf的后MAX_REV_SIZE/2字节可用
{
if(!YC_realloc(&headBuf, rev_cnt + MAX_REV_SIZE/2)) goto tr_p_err;
rev_len = rev_cnt + MAX_REV_SIZE/2;
}
//处理HTTP请求。目前,忽略其它请求
response_proc(pSlot->userSock,headBuf,rev_cnt,
MAX_REV_SIZE/2,keepAlive,&pSlot->servc->server_stop);
if(pSlot->servc->user_callback) //若有回调函数,则执行它
pSlot->servc->user_callback(pSlot->userSock,headBuf);
if(!keepAlive || pSlot->servc->server_stop) break; //短连接或用户终止
}
tr_p_err:
free(headBuf); //释放接收数据缓冲区所占内存
pSlot->servc->slot_delete(pSlot); //删除槽口对象
return 0;
}
//在双向链表pHead中插入pLink
template<class T>
void yc_add_link(T *pLink,T *&pHead)
{
if(!pHead) pHead = pLink->pLast = pLink;
pLink->pLast = pHead->pLast;
pLink->pNext = pHead;
pHead->pLast->pNext = pLink;
pHead->pLast = pLink;
}
//在双向链表pHead中删除pLink
template<class T>
void yc_delete_link(T *pLink,T *&pHead)
{
pLink->pLast->pNext = pLink->pNext;
pLink->pNext->pLast = pLink->pLast;
if(pHead == pLink)
{
pHead = pLink->pNext;
if(pHead == pLink) pHead = nullptr;
}
}
//用函数pFunc遍历执行双向链表pHead
template<class T>
void yc_run_link(T *pHead,void (*pFunc)(T*))
{
if(!pHead) return;
T *pLink = pHead;
do{
pFunc(pLink);
pLink = pLink->pNext;
} while(pLink != pHead);
}
struct YSLOT //槽口对象结构,每次连接都创建一个槽口对象,所有槽口对象形成一个双向链表
{
YSLOT *pNext; //下一个槽口对象执针
YSLOT *pLast; //上一个槽口对象执针
int userSock; //本槽口的套接字
struct YSERVER *servc; //服务器对象指针
void *slotHandle; //本槽口线程的句柄
};
struct YSERVER //服务器对象
{
serverCall user_callback; //用户回调函数指针
int server_stop; //用户终止程序标志
int listenSock; //服务器侦听套接字
YSERVER(int port=80,serverCall aFunc=null) : pHead(null),
slot_count(0),
user_callback(aFunc),
m_port(port)
{
listenSock = socket(AF_INET,SOCK_STREAM,0); //创建侦听套接字
if(listenSock == INVALID_SOCKET) return;
InitializeCriticalSection(&server_cris); //初始化线程锁数据
server_exit = server_stop = 0; //程序终止标志
srvHandle = NULL; //服务器侦听线程句柄
sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
saddr.sin_port = htons(m_port); //服务器端口,默认为80
if(bind(listenSock,(sockaddr*)&saddr,sizeof sockaddr)==SOCKET_ERROR)
{
svr_err: //若绑定或侦听设置失败,则退出
closesocket(listenSock);
listenSock = INVALID_SOCKET;
return;
}
if(listen(listenSock,100) == SOCKET_ERROR) goto svr_err;
srvHandle = CreateThread(null, //创建侦听线程
0,
[](void *lpParameter) //侦听线程lambda函数
{
YSERVER *svc = (YSERVER*)lpParameter;
while(1)
{
int newSok = accept(svc->listenSock,0,0);
if(svc->server_stop) //判断服务器终止标志
{
closesocket(newSok); //关闭客户端连接
break; //退出侦听线程
}
svc->slot_add(newSok); //处理新增连接
}
CloseHandle(svc->srvHandle); //释放侦听句柄
closesocket(svc->listenSock); //关闭侦听连接
svc->server_exit = 1; //设置服务器退出标志
return (unsigned long)0;
},
this, //传给侦听线程函数的服务器对象指针
0,
null);
}
~YSERVER()
{
if(listenSock == INVALID_SOCKET) return;
server_stop = 1;
//此时,阻塞函数accept()仍在执行中,需用下列代码退出accept()。
int endSock = socket(AF_INET,SOCK_STREAM,0);
sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(m_port);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
connect(endSock,(sockaddr*)&saddr,sizeof(sockaddr));
closesocket(endSock);
while(!server_exit); //等待accept()函数的退出
server_lock(); //加锁,阻止其它线程同时修改链表
yc_run_link(pHead,[](YSLOT *pLink) //遍历执行双向链表的每一项
{
closesocket(pLink->userSock);
pLink->userSock = INVALID_SOCKET;
});
server_unlock(); //解锁,允许其它线程修改链表
while(pHead); //等待双向链表的全部销毁
DeleteCriticalSection(&server_cris); //删除线程锁数据所占资源
}
void _stdcall slot_add(int newSock) //增加一个槽口
{
if(newSock == INVALID_SOCKET) return;
YSLOT *pLink = (YSLOT*)malloc(sizeof YSLOT); //为槽口对象申请内存
if(!pLink) return;
server_lock(); //加锁,阻止其它线程修改链表
yc_add_link(pLink,pHead); //将新增槽口对象(pLink)加入双向链表
slot_count++; //槽口计数增1
server_unlock(); //解锁,允许其它线程修改链表
pLink->userSock = newSock; //设置槽口套接字成员
pLink->servc = this; //设置槽口所属的服务器对象之指针
//创建槽口数据接收线程
pLink->slotHandle = CreateThread(0,0,user_request_thread,pLink,0,0);
}
void _stdcall slot_delete(YSLOT *pLink) //删除一个槽口
{
server_lock(); //加锁,阻止其它线程同时修改链表
slot_count--; //槽口计数减1
yc_delete_link(pLink,pHead); //将槽口对象(pLink)从双向链表中删除
server_unlock(); //解锁,允许其它线程修改链表
closesocket(pLink->userSock); //关闭本槽口的服务器和客户端之间的连接
CloseHandle(pLink->slotHandle); //释放槽口线程句柄
free(pLink); //释放槽口对象所占内存
}
void server_lock() //加锁函数
{
EnterCriticalSection(&server_cris);
}
void server_unlock() //解锁函数
{
LeaveCriticalSection(&server_cris);
}
int slot_number() //获得槽口数,即连接数
{
return slot_count;
}
private:
YSLOT *pHead; //双向链表头
unsigned short m_port; //服务器端口号
void *srvHandle; //服务器侦听线程句柄
int slot_count; //槽口计数器
int server_exit; //服务器退出标志
CRITICAL_SECTION server_cris; //线程同步锁结构数据
};
YSERVER *gServer; //服务器对象指针
int ychttpCLASS::check_server() //检查服务器是否已启动
{
return gServer != NULL;
}
int ychttpCLASS::slot_number() //获得服务器连接数
{
if(!gServer) return 0;
return gServer->slot_number();
}
void ychttpCLASS::start_server(int port,serverCall aFunc) //启动服务器
{
if(gServer) return;
gServer = new YSERVER(port,aFunc);
if(gServer && gServer->listenSock==INVALID_SOCKET) stop_server();
}
void ychttpCLASS::stop_server() //终止服务器
{
if(!gServer) return;
delete gServer;
gServer = NULL;
}
代码文件:ychttp.cpp
编译:用YC命令:ycc ychttp.cpp 生成 ychttp.exe
运行:在cmd界面执行ychttp.exe后,输出下列文字:
Web服务器启动成功! port=81
--------------press ‘Q’ to exit-----------
在浏览器地址栏输入: http://127.0.0.1:81/
可浏览文件
输入文件名:
在提示“输入文件名:”后,键入一些字符后按Enter键,
如果键入的是文本文件,将在对话框中显示其内容;
如果键入的是网址,则显示其页面。
在Chrome浏览器的地址栏输入 http://127.0.0.1:81 后,浏览器将进入一个研究生管理系统界面。
YC服务器中,ychttp.cpp被ycs.htm调用,在界面上能够显示各种HTTP请求应答信息,这有助于调式分析web服务程序、观察浏览器向服务器发送的信息格式。