linux平台下C语言实现一个简单的httpsever

一.HTTP请求和应答的步骤

HTTP是基于TCP协议的短连接,一般为以下四步:

第一步、client通过tcp协议连接到server端;

第二步、client给server发送请求request(client一个连接只给server发一次request请求);

第三步、server给client回复reponse;

第四步、双方断开连接

注:(client如果想再次给server发送request那么就必须再次连接到server)

对于http请求最简单一个模型

1、客户端发送http请求中GET后面一般跟一个文件名

2、服务端会把客户端get的文件下发下去

二.HTTP协议结构介绍

HTTP请求(字符串)解析出GET / 后面的内容如果为空,则sever会把默认的index.html 发给浏览器,浏览器接收到index.html 会解析html然后查看需要的图片等其它文件,会开线程再向服务器发http请求。

如在浏览器中输入服务器的IP地址

http://172.19.198.109

firefox浏览器发的http请求消息内容

GET / HTTP/1.1
Host: 172.19.198.109
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:37.0) Gecko/20100101 Firefox/37.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: CNZZDATA5902315=cnzz_eid%3D1568106884-1431221304-%26ntime%3D1431221304
Connection: keep-alive
If-Modified-Since: Thu, 07 May 2015 00:13:52 GMT
If-None-Match: "554aae40-207"
Cache-Control: max-age=0
 

这里可以看到GET /  后面HTTP/1.1 之间的内容为空,此时,server就将默认的东西发给浏览器,比如index.html

输入:http://172.19.198.109/index.html

GET /index.html HTTP/1.1
Host: 172.19.198.109
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:37.0) Gecko/20100101 Firefox/37.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: CNZZDATA5902315=cnzz_eid%3D1568106884-1431221304-%26ntime%3D1431221304
Connection: keep-alive
If-Modified-Since: Thu, 07 May 2015 00:13:52 GMT
If-None-Match: "554aae40-207"
Cache-Control: max-age=0

可以看到GET / 的内容为index.html,这也是就是浏览器向server发出的请求,server解析出请求后将index.html 按照如下的格式回复给浏览器。

http 回复内容格式:

a.消息头(固定格式字符串)

b.消息体(二进制内容)图片,html 文件或者其他文件

c.消息尾(固定格式字符串)

a.消息头内容格式:(字符串)HTTP/1.0 200 OK\n

Content-Type: image/x-icon\n(标识给客户端回复的消息体是什么类型的文件,消息体是衣服图像文件,图像是icon格式的)

Transfer-Encoding: chunked\n

Connection: Keep-Alive\n

Accept-Ranges:bytes\n

Content-Length:2550\n \n(消息体的长度,单位:字节)

b.消息体:(二进制内容)具体要给客户端发送的文件二进制流

c.消息尾(字符串):\n\n(两个换行符)

三.阻塞Socket多线程并发程序设计

因为HTPP都是基于短连接的,所以不可能出现服务端同时存在大量client连接的情况发生,所以采用多线程,并不会导致一个进程当中同时存在大量的线程情况发生,阻塞的socket多线程还可以增加每一个客户端连接响应的速度。 对于多线程并发,即使其中某一个连接消耗了大量的时间,也不会影响其他的连接。如果采用阻塞的多线程并发设计,当主线程不关心具体子线程的退出状态,那么所有的线程可以使用可分离状态,让这些线程自生自灭。这里就设置为线程的可分离状态。

程序代码:

server.c 主程序入口

#include
#define BUFFSIZE 1024
#include"thread_work.h"
#include"pub.h"
int main(int argc,char *argv[])
{
	if(argc<2)
	{
		printf("usage:server port \n");
		return 0;
	}
	int port = atoi(argv[1]);

	if(port <= 0)
	{
		printf("port must be positive integer: \n");
		return 0;
	}

	int st =socket_create(port);
	if(st==0)
	{
		return 0;
	}
	setdaemon(); //设置为守护进程
	printf("my http server begin\n");
	socket_accept(st);
	close(st);
}

pub.c

#include
#include
#include
#include
#include
#include
#include 
#include 
#include

#include"pub.h"
#include"thread_work.h"
char LOGBUF[1024];
void save_log(char *buf)
{
      FILE *fp = fopen("log.txt","a+");  
      fputs(buf,fp);  
      fclose(fp);  
}
void setdaemon() //设置为守护进程
{
	pid_t pid, sid;
	pid = fork();
	if (pid < 0)
	{
		memset(LOGBUF,0,sizeof(LOGBUF));
		sprintf(LOGBUF,"fork failed %s\n", strerror(errno));
		save_log(LOGBUF);
		exit (EXIT_FAILURE);
	}
	if (pid > 0)
	{
		exit (EXIT_SUCCESS);
	}

	if ((sid = setsid()) < 0)
	{
		printf("setsid failed %s\n", strerror(errno));
		exit (EXIT_FAILURE);
	}
  /*if (chdir("/") < 0)
	 {
	 printf("chdir failed %s\n", strerror(errno));
	 exit(EXIT_FAILURE);
	 }*/
	 umask(0);
	 close(STDIN_FILENO);
	 close(STDOUT_FILENO);
	 close(STDERR_FILENO);

}

const char *get_filetype(const char *filename) //根据扩展名返回文件类型描述
{
	得到文件扩展名///
	char sExt[32];
	const char *p_start=filename;
	memset(sExt, 0, sizeof(sExt));
	while(*p_start)
	{
		if (*p_start == '.')
		{
			p_start++;
			strncpy(sExt, p_start, sizeof(sExt));
			break;
		}
		p_start++;
	}

	根据扩展名返回相应描述///

	if (strncmp(sExt, "bmp", 3) == 0)
		return "image/bmp";

	if (strncmp(sExt, "gif", 3) == 0)
		return "image/gif";

	if (strncmp(sExt, "ico", 3) == 0)
		return "image/x-icon";

	if (strncmp(sExt, "jpg", 3) == 0)
		return "image/jpeg";

	if (strncmp(sExt, "avi", 3) == 0)
		return "video/avi";

	if (strncmp(sExt, "css", 3) == 0)
		return "text/css";

	if (strncmp(sExt, "dll", 3) == 0)
		return "application/x-msdownload";

	if (strncmp(sExt, "exe", 3) == 0)
		return "application/x-msdownload";

	if (strncmp(sExt, "dtd", 3) == 0)
		return "text/xml";

	if (strncmp(sExt, "mp3", 3) == 0)
		return "audio/mp3";

	if (strncmp(sExt, "mpg", 3) == 0)
		return "video/mpg";

	if (strncmp(sExt, "png", 3) == 0)
		return "image/png";

	if (strncmp(sExt, "ppt", 3) == 0)
		return "application/vnd.ms-powerpoint";

	if (strncmp(sExt, "xls", 3) == 0)
		return "application/vnd.ms-excel";

	if (strncmp(sExt, "doc", 3) == 0)
		return "application/msword";

	if (strncmp(sExt, "mp4", 3) == 0)
		return "video/mpeg4";

	if (strncmp(sExt, "ppt", 3) == 0)
		return "application/x-ppt";

	if (strncmp(sExt, "wma", 3) == 0)
		return "audio/x-ms-wma";

	if (strncmp(sExt, "wmv", 3) == 0)
		return "video/x-ms-wmv";

	return "text/html";
}

int socket_create(int port)
{
	int st = socket(AF_INET, SOCK_STREAM, 0);
	int on =1;
	if (st == -1)
	{
		memset(LOGBUF,0,sizeof(LOGBUF));
		sprintf(LOGBUF,"%s,%d:socker error %s\n", __FILE__, __LINE__, strerror(errno));
		save_log(LOGBUF);
		return 0;
	}
	if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
		{
			memset(LOGBUF,0,sizeof(LOGBUF));
			sprintf(LOGBUF,"setsockopt failed %s\n", strerror(errno));
			save_log(LOGBUF);
			return 0;
		}
	struct sockaddr_in sockaddr;
	memset(&sockaddr, 0, sizeof(sockaddr));
	sockaddr.sin_port = htons(port); //指定一个端口号并将hosts字节型传化成Inet型字节型(大端或或者小端问题)
	sockaddr.sin_family = AF_INET;	//设置结构类型为TCP/IP
	sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);	//服务端是等待别人来连,不需要找谁的ip
	//这里写一个长量INADDR_ANY表示server上所有ip,这个一个server可能有多个ip地址,因为可能有多块网卡
	if (bind(st, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) == -1)
	{
		memset(LOGBUF,0,sizeof(LOGBUF));
		sprintf(LOGBUF,"%s,%d:bind error %s \n", __FILE__, __LINE__, strerror(errno));
		save_log(LOGBUF);
		return 0;
	}

	if (listen(st, 100) == -1) // 	服务端开始监听
	{
		memset(LOGBUF,0,sizeof(LOGBUF));
		sprintf(LOGBUF,"%s,%d:listen failture %s\n", __FILE__, __LINE__,
				strerror(errno));
				save_log(LOGBUF);
		return 0;
	}
	printf("start server success!\n");
	return st;

}
int socket_accept(int st)
{
	int client_st;
	struct sockaddr_in client_sockaddr;
	socklen_t len = sizeof(client_sockaddr);

	pthread_t thrd_t;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //初始化线程为可分离的
	memset(&client_sockaddr, 0, sizeof(client_sockaddr));

	while (1)
	{
		client_st = accept(st, (struct sockaddr *) &client_sockaddr, &len);
		if (client_st == -1)
		{
			memset(LOGBUF,0,sizeof(LOGBUF));
			sprintf(LOGBUF,"%s,%d:accept failture %s \n", __FILE__, __LINE__,
					strerror(errno));
			save_log(LOGBUF);
			return 0;
		} else
		{
			int *tmp = (int *) malloc(sizeof(int));
			*tmp = client_st;
			pthread_create(&thrd_t, &attr, http_thread, tmp);
		}

	}
	pthread_destory(&attr);//释放资源
}

int  get_file_content(const char *file_name, char **content) // 得到文件内容
{
	int  file_length = 0;
	FILE *fp = NULL;

	if (file_name == NULL)
	{
		return file_length;
	}

	fp = fopen(file_name, "rb");

	if (fp == NULL)
	{
		memset(LOGBUF,0,sizeof(LOGBUF));
		sprintf(LOGBUF,"file name: %s,%s,%d:open file failture %s \n",file_name, __FILE__, __LINE__,
				strerror(errno));
		save_log(LOGBUF);
		return file_length;
	}

	fseek(fp, 0, SEEK_END);
	file_length = ftell(fp);
	rewind(fp);

	*content = (char *) malloc(file_length);
	if (*content == NULL)
	{
		memset(LOGBUF,0,sizeof(LOGBUF));
		sprintf(LOGBUF,"%s,%d:malloc failture %s \n", __FILE__, __LINE__,
				strerror(errno));
		save_log(LOGBUF);
		return 0;
	}
	fread(*content, file_length, 1, fp);
	fclose(fp);

	return file_length;
}

#include"thread_work.h"
#include"pub.h"

#include 
#include 
#include
#include
#include
#include
//http 消息头
#define HEAD "HTTP/1.0 200 OK\n\
Content-Type: %s\n\
Transfer-Encoding: chunked\n\
Connection: Keep-Alive\n\
Accept-Ranges:bytes\n\
Content-Length:%d\n\n"
//http 消息尾
#define TAIL "\n\n"
extern char LOGBUF[1024];
//得到http 请求中 GET后面的字符串
void get_http_command(char *http_msg, char *command)
{
    char *p_end = http_msg;
    char *p_start = http_msg;
    while (*p_start) //GET /
    {
        if (*p_start == '/')
        {
            break;
        }
        p_start++;
    }
    p_start++;
    p_end = strchr(http_msg, '\n');
    while (p_end != p_start)
    {
        if (*p_end == ' ')
        {
            break;
        }
        p_end--;
    }
    strncpy(command, p_start, p_end - p_start);

}

//根据用户在GET中的请求,生成相应的回复内容
int make_http_content(const char *command, char **content)
{
    char *file_buf;
    int file_length;
    char headbuf[1024];

    if (command[0] == 0)
    {
        file_length = get_file_content("index.html", &file_buf);
    } else
    {
        file_length = get_file_content(command, &file_buf);
    }
    if (file_length == 0)
    {
        return 0;
    }

    memset(headbuf, 0, sizeof(headbuf));
    sprintf(headbuf, HEAD, get_filetype(command), file_length); //设置消息头

    int iheadlen = strlen(headbuf); //得到消息头长度
    int itaillen = strlen(TAIL); //得到消息尾长度
    int isumlen = iheadlen + file_length + itaillen; //得到消息总长度
    *content = (char *) malloc(isumlen); //根据消息总长度,动态分配内存
    if(*content==NULL)
    {
        memset(LOGBUF,0,sizeof(LOGBUF));
        sprintf(LOGBUF,"malloc failed %s\n", strerror(errno));
        save_log(LOGBUF);
    }
    char *tmp = *content;
    memcpy(tmp, headbuf, iheadlen); //安装消息头
    memcpy(&tmp[iheadlen], file_buf, file_length); //安装消息体
    memcpy(&tmp[iheadlen] + file_length, TAIL, itaillen); //安装消息尾
    //printf("headbuf:\n%s", headbuf);
    if (file_buf)
    {
        free(file_buf);
    }
    return isumlen; //返回消息总长度
}
void *http_thread(void *argc)
{
    //printf("thread begin \n");
    if(argc==NULL)
    {
        return NULL;
    }
    int st = *(int *) argc;
    free((int *)argc);
    char buf[1024];
    memset(buf, 0, sizeof(buf));
    int rc = recv(st, buf, sizeof(buf), 0);
    if (rc <= 0)
    {
        memset(LOGBUF,0,sizeof(LOGBUF));
        sprintf(LOGBUF,"recv failed %s\n", strerror(errno));
        save_log(LOGBUF);
    } else
    {
        //printf("recv:\n%s", buf);
        char command[1024];
        memset(command, 0, sizeof(command));
        get_http_command(buf, command); //得到http 请求中 GET后面的字符串
        //printf("get:%s \n", command);
        char *content = NULL;
        int ilen = make_http_content(command, &content); //根据用户在GET中的请求,生成相应的回复内容
        if (ilen > 0)
        {
            send(st, content, ilen, 0); //将回复的内容发送给client端socket
            free(content);
        }
    }
    close(st); //关闭client端socket
    //printf("thread_is end\n");
    return NULL;

}

编译工程:

linux平台下C语言实现一个简单的httpsever_第1张图片

在浏览器输入sever的IP的地址(看的server机器的IP地址)

linux平台下C语言实现一个简单的httpsever_第2张图片

工程源码:http://download.csdn.net/detail/huangshanchun/8697711

你可能感兴趣的:(C/C++,linux,linux,c语言,服务器)