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请求(字符串)解析出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;
}
在浏览器输入sever的IP的地址(看的server机器的IP地址)
工程源码:http://download.csdn.net/detail/huangshanchun/8697711