您的位置:首页 > 理论基础 > 计算机网络

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

2015-05-13 13:03 501 查看

一.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<stdio.h>
#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<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<arpa/inet.h>

#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 <sys/types.h>
#include <sys/socket.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
//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;

}

编译工程:



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



工程源码:http://download.csdn.net/detail/huangshanchun/8697711
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: