好久没有发文了,今天记录先前一次课堂作业 —— 用C语言写个支持cgi的基于scoket的web服务器(Windows下)。

我主要以流程图的方式进行表述:

需求分析

  1. socket实现简单Http服务器,完成html的解析;
  2. 运行该服务器可以通过浏览器访问服务器目录下的 Html文件、jpg图片、css文件的载入。 完成初步的Http服务器功能;
  3. 能够运行二进制的cgi程序。

重点

    1. socket的创建
    2. Request Headers的解析
    3. 非字符文件的传输
    4. cgi程序接口
    5. Response Headers content-type等的构造
    6. 异常请求的处理
    7. 程序全局变量的获取
    8. 针对UrlEncode编码的解析

流程

基本流程

c_web01

函数流程

c_web02

主要函数

文件、cgi传输

int
response_file(SOCKET sAccept, unsigned char * path) {
	FILE * txt = fopen(path, "rb");
	unsigned int CODE = 200;
	// 如果文件解析错误, 给它个404
	if (NULL == txt)
		CODE = response_404(sAccept);
	else {
		//发送给200的报文头过去, 并发送文件内容过去
		//get_moreheader(path);
		long lBegin, lEnd;
		unsigned char *p;
		unsigned int  nSize;

		lBegin = ftell(txt);
		fseek(txt, 0L, SEEK_END);
		lEnd = ftell(txt);
		nSize = lEnd - lBegin;
		p = (unsigned char *)malloc(nSize * sizeof(unsigned char));
		fseek(txt, 0L, SEEK_SET);
		fread(p, sizeof(unsigned char), nSize, txt);
		fclose(txt);
		response_200(sAccept, NULL, nSize);
		send(sAccept, p, nSize, 0);
	}
	return CODE;
}

unsigned int
response_cgi(SOCKET sAccept, const unsigned char * path) {
	unsigned int CODE = 888;
	FILE * txt = fopen(path, "rb");
	if (NULL == txt) {
		CODE = response_404(sAccept);
		return;
	}
	fclose(txt);
	unsigned char buf[MAX_BUFFER << 1] = {0};
	_Cgi(path, buf);
	send(sAccept, buf, strlen(buf), 0);
	return CODE;
}

主函数

int main()
{
	system("title Httpd - 0.1 [XinRoom]");
	system("chcp 65001");
	WSADATA wsaData;
	SOCKET sListen, sAccept;        //服务器监听套接字 和 连接套接字
	int serverport = DEFAULT_PORT;  //服务器端口号
	struct sockaddr_in ser, cli;    //服务器地址 和 客户端地址
	int iLen;

	//加载协议栈
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		printf("Failed to load Winsock.\n");
		getchar();
		return USER_ERROR;
	}

	//创建监听套接字,用于监听客户请求IPPROTO_TCP
	sListen = socket(AF_INET, SOCK_STREAM, 0);
	//sListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (sListen == INVALID_SOCKET)
	{
		printf("socket() Failed:%d\n", WSAGetLastError());
		getchar();
		return USER_ERROR;
	}

	//创建服务器地址:IP+端口号
	ser.sin_family = AF_INET;
	ser.sin_port = htons(serverport);          //服务器端口号
	ser.sin_addr.s_addr = htonl(INADDR_ANY);   //服务器IP地址

	//绑定监听套接字和服务器地址
	if (bind(sListen, (LPSOCKADDR)&ser, sizeof(ser)) == SOCKET_ERROR)
	{
		printf("blind() Failed:%d\n", WSAGetLastError());
		getchar();
		return USER_ERROR;
	}
	int iSockAttrOn = 1; // 开启复用
	setsockopt(sListen, SOL_SOCKET, SO_REUSEADDR, (void *)&iSockAttrOn, sizeof(iSockAttrOn));
	
	
	//通过监听套接字进行监听
	if (listen(sListen, SOMAXCONN) == SOCKET_ERROR)
	{
		printf("listen() Failed:%d\n", WSAGetLastError());
		getchar();
		return USER_ERROR;
	}
	printf("httpd running on port %d\n", DEFAULT_PORT);

	DWORD ThreadID;
	while (1)  //循环等待客户的请求
	{
		//接受客户端的连接请求,返回与该客户建立的连接套接字
		iLen = sizeof(cli);
		sAccept = accept(sListen, (struct sockaddr*)&cli, &iLen);
		//setsockopt(sAccept, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof(keepalive));
		_putenv_s("REMOTE_ADDR", inet_ntoa(cli.sin_addr));//客户端地址

		if (sAccept == INVALID_SOCKET)
		{
			printf("accept() Failed:%d\n", WSAGetLastError()); break;
		}
		//创建线程接受浏览器请求
		CreateThread(NULL, 0, SimpleHTTPServer, (LPVOID)sAccept, 0, &ThreadID);
		//SimpleHTTPServer((LPVOID)sAccept);
	}
	closesocket(sListen);
	WSACleanup();
	return 0;
}

Github连接https://github.com/XinRoom/SimpleHTTPServer
写的极不规范,实在是好久没发文了,先发一个压压惊。。0。。
(忘了当初参考过哪些文章了。)

更新于:2018-08-11