在上一节【八】http服务器开发–实现一个http服务器中我们已经实现了一个http服务器示例,但是该示例是单线程的;因此本节在上一节的基础上,添加上高并发操作。
发通常是指同时能并行的处理多个任务。
举个通俗的例子,如下图所示:
在服务大厅,可以同时为多个客服服务。
同时拥有两个或者多个线程,如果程序在单核处理器上运行,多个线程将交替的换入或者换出内存,这些线程是同时“存在”的。
每个线程都处于执行过程中的某个状态,如果运行在多核处理器上,此时,程序中的每个线程都将分配到一个处理器核上,因此可以同时运行。
高并发是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够 同时并行处理 很多请求
函数功能:
创建一个新线程,并行的执行任务。
函数实现:
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
**返回值:**
成功:0; 失败:错误号。
参数:
pthread_t
:当前Linux中可理解为:typedef unsigned long int pthread_t;
参数1:传出参数,保存系统为我们分配好的线程ID
参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
参数4:线程主函数执行期间所使用的参数。
在一个线程中调用pthread_create()
创建新的线程后,当前线程从pthread_create()
返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine
决定。start_routine
函数接收一个参数,是通过pthread_create的arg
参数传递给它的,该参数的类型为void *
,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *
,这个指针的含义同样由调用者自己定义。start_routine
返回时,这个线程就退出了,其它线程可以调用pthread_join
得到start_routine的返回值,以后再详细介绍pthread_join
。
pthread_create成功返回后,新创建的线程的id
被填写到thread参数所指向的内存单元。
attr
参数表示线程属性,本节不深入讨论线程属性,所有代码例子都传NULL给attr参数,表示线程属性取缺省值。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 80
static int debug = 1;
int get_line(int sock, char *buf, int size);
void* do_http_request(void *client_sock);
void do_http_response(int client_sock, const char *path);
int headers(int client_sock, FILE *resource);
void cat(int client_sock, FILE *resource);
void not_found(int client_sock);//404
void unimplemented(int client_sock);//500
void bad_request(int client_sock); //400
void inner_error(int client_sock);
int main(void){
int sock;//代表信箱
struct sockaddr_in server_addr;
//1.美女创建信箱
sock = socket(AF_INET, SOCK_STREAM, 0);
//2.清空标签,写上地址和端口号
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;//选择协议族IPV4
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地所有IP地址
server_addr.sin_port = htons(SERVER_PORT);//绑定端口号
//实现标签贴到收信得信箱上
bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
//把信箱挂置到传达室,这样,就可以接收信件了
listen(sock, 128);
//万事俱备,只等来信
printf("等待客户端的连接\n");
int done =1;
while(done){
struct sockaddr_in client;
int client_sock, len, i;
char client_ip[64];
char buf[256];
pthread_t id;
int* pclient_sock = NULL;
socklen_t client_addr_len;
client_addr_len = sizeof(client);
client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
//打印客服端IP地址和端口号
printf("client ip: %s\t port : %d\n",
inet_ntop(AF_INET, &client.sin_addr.s_addr,client_ip,sizeof(client_ip)),
ntohs(client.sin_port));
/*处理http 请求,读取客户端发送的数据*/
//do_http_request(client_sock);
//启动线程处理http 请求
pclient_sock = (int *)malloc(sizeof(int));
*pclient_sock = client_sock;
pthread_create(&id, NULL, do_http_request, (void *)pclient_sock);
//close(client_sock);
}
close(sock);
return 0;
}
void* do_http_request(void* pclient_sock){
int len = 0;
char buf[256];
char method[64];//请求的方法,是get还是post
char url[256];//请求的URL
char path[256];
int client_sock = *(int *)pclient_sock;
struct stat st;//用于stat函数执行后,保存文件状态
/*读取客户端发送的http 请求*/
//1.读取请求行
len = get_line(client_sock, buf, sizeof(buf));//请求行只要一行,因此只需要读一次即可
printf("--in do_http_request --,result for get_line is [%s]\n",buf);
if(len > 0){//读到了请求行,首先获取请求的方法
int i=0, j=0;
while(!isspace(buf[j]) && (i
{
method[i] = buf[j];//用来存放请求方法,是get还是post
i++;
j++;
printf("i is %d,j is %d\n",i,j);
}
//printf("i is %d,j is %d\n",i,j);
method[i] = '\0';
if(debug) printf("request method: %s\n", method);
if(strncasecmp(method, "GET", i)==0){ //只处理get请求
if(debug) printf("method = GET\n");
//获取url
//printf("i is %d,j is %d\n",i,j);
while(isspace(buf[j++]));//当buf[j++]为空格时,则跳过白空格,一直循环,直到遇到不是空格的,往下走
{
printf("buf[%d] is %c\n",j,buf[j]);
i = 0;
}
printf("i is %d,j is %d\n",i,j);
//printf("sizeof(url) is %d\n",sizeof(url));
while(!isspace(buf[j]) && (i
{
url[i] = buf[j];//用来存放URL
i++;
j++;
//printf("11111\n");
//printf("url is [%s]\n",url);
}
url[i] = '\0';
if(debug) printf("----url: [%s]\n", url);
//继续读取http 头部
do{
len = get_line(client_sock, buf, sizeof(buf));
if(debug) printf("read: %s\n", buf);
}while(len>0);
//***定位服务器本地的html文件***
//处理url 中的问号?,因为有的url是带着问号的,我们这里只取问号前面的作为URL
{
char *pos = strchr(url, '?');//查找问号第一次出现的位置
if(pos){
*pos = '\0';//将问号的地方换成\0,即字符串结束符,表示此URL到此结束
printf("real url: %s\n", url);
}
}
sprintf(path, "./html_docs/%s", url);//./html_docs是我们存放html文件的文件夹路径
if(debug) printf("path: %s\n", path);
//执行http 响应
//判断文件是否存在,如果存在就响应200 OK,同时发送相应的html 文件,如果不存在,就响应 404 NOT FOUND.
if(stat(path, &st)==-1){//文件不存在或是出错
fprintf(stderr, "stat %s failed. reason: %s\n", path, strerror(errno));
not_found(client_sock);
}else {//文件存在
if(S_ISDIR(st.st_mode))//S_ISDIR用来判断st是否是一个目录
{
strcat(path, "/index.html");//如果是一个目录,则在path的字符串后面加上/index.html
}
do_http_response(client_sock, path);
}
}else {//非get请求, 读取http 头部,并响应客户端 501 Method Not Implemented
fprintf(stderr, "warning! other request [%s]\n", method);
do{
len = get_line(client_sock, buf, sizeof(buf));
if(debug) printf("read: %s\n", buf);
}while(len>0);
unimplemented(client_sock); //请求未实现
}
}else {//请求格式有问题,出错处理
bad_request(client_sock); //在响应时再实现
}
close(client_sock);
if(pclient_sock) free(pclient_sock);//释放动态分配的内存
return NULL;
}
void do_http_response(int client_sock, const char *path){
int ret = 0;
FILE *resource = NULL;
resource = fopen(path, "r");
if(resource == NULL){
not_found(client_sock);
return ;
}
//1.发送http 头部
ret = headers(client_sock, resource);
//2.发送http body .
if(!ret){
cat(client_sock, resource);
}
fclose(resource);
}
/****************************
*返回关于响应文件信息的http 头部
*输入:
* client_sock - 客服端socket 句柄
* resource - 文件的句柄
*返回值: 成功返回0 ,失败返回-1
******************************/
int headers(int client_sock, FILE *resource){
struct stat st;
int fileid = 0;
char tmp[64];
char buf[1024]={0};
strcpy(buf, "HTTP/1.0 200 OK\r\n");
strcat(buf, "Server: Martin Server\r\n");
strcat(buf, "Content-Type: text/html\r\n");
strcat(buf, "Connection: Close\r\n");
fileid = fileno(resource);//获取该文件的file id
if(fstat(fileid, &st)== -1)//同stat函数类似,查看st文件是否存在,返回-1则说明文件不存在的
{
inner_error(client_sock);//发送500的错误码,表示服务器内部出错
return -1;
}
snprintf(tmp, 64, "Content-Length: %ld\r\n\r\n", st.st_size);//连续两个回车符和换行符表示这是消息头部的结束了,st.st_size表示的是st文件的长度
strcat(buf, tmp);
if(debug) fprintf(stdout, "header: %s\n", buf);
if(send(client_sock, buf, strlen(buf), 0)<0)//send函数表示向socket函数发送消息,返回值小于0表示发送失败了
{
fprintf(stderr, "send failed. data: %s, reason: %s\n", buf, strerror(errno));
return -1;
}
return 0;
}
/****************************
*说明:实现将html文件的内容按行
读取并送给客户端
****************************/
void cat(int client_sock, FILE *resource){
char buf[1024];
fgets(buf, sizeof(buf), resource);//将resource文件中的内容读到buf中,fgets函数每次读取一行
while(!feof(resource))//feof用来判断resource文件是否读到尾部了,文件结束返回非0,文件未结束返回0
{
int len = write(client_sock, buf, strlen(buf));//将buf的内容写进socket,即发送这一行内容
if(len<0){//发送body 的过程中出现问题,怎么办?1.重试? 2.
fprintf(stderr, "send body error. reason: %s\n", strerror(errno));
break;
}
if(debug) fprintf(stdout, "%s", buf);
fgets(buf, sizeof(buf), resource);
}
}
void do_http_response1(int client_sock){
const char *main_header = "HTTP/1.0 200 OK\r\nServer: Martin Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";
const char * welcome_content = "\
\n\
\n\
\n\
This is a test \n\
\n\
\n\
\n\
\n\
大家好,欢迎学习网络编程知识
\n\
\n\
\n\
\n\
";
//1. 发送main_header
int len = write(client_sock, main_header, strlen(main_header));
if(debug) fprintf(stdout, "... do_http_response...\n");
if(debug) fprintf(stdout, "write[%d]: %s", len, main_header);
//2. 生成Content-Length
char send_buf[64];
int wc_len = strlen(welcome_content);
len = snprintf(send_buf, 64, "Content-Length: %d\r\n\r\n", wc_len);//两个回车换行符,表示结束
len = write(client_sock, send_buf, len);//发送Content-Length
if(debug) fprintf(stdout, "write[%d]: %s", len, send_buf);
len = write(client_sock, welcome_content, wc_len);
if(debug) fprintf(stdout, "write[%d]: %s", len, welcome_content);
}
//读取请求行,返回值: -1 表示读取出错, 等于0表示读到一个空行, 大于0 表示成功读取一行
int get_line(int sock, char *buf, int size){
int count = 0;//计数器,用来对当前已经读取的字符数进行计数
char ch = '\0';//字符串结束符
int len = 0;
//当前读取的字符数小于传入的字符数size-1,且ch不是换行符时
while( (count{
len = read(sock, &ch, 1);//将sock里的数据读到ch中,每次读1个字符;当返回值len为1时,说明返回的是1,即读取成功
if(len == 1){
if(ch == '\r')//如果读到的是回车符,则continue,进行下一次循环
{
continue;
}
else if(ch == '\n')//如果读到的是换行符,说明这一行读完了,break跳出循环
{
//buf[count] = '\0';
break;
}
//这里处理一般的字符
buf[count] = ch;
count++;
}else if( len == -1 ){//读取出错
perror("read failed");
count = -1;
break;
}else {// read 返回0,客户端关闭sock 连接.
fprintf(stderr, "client close.\n");
count = -1;
break;
}
}
if(count >= 0) buf[count] = '\0';//为读取的这一行添加字符串结束符
return count;
}
void not_found(int client_sock){
const char * reply = "HTTP/1.0 404 NOT FOUND\r\n\
Content-Type: text/html\r\n\
\r\n\
\r\n\
\r\n\
\r\n\
NOT FOUND \r\n\
\r\n\
\r\n\
文件不存在!\r\n\
The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
\r\n\
"
;
int len = write(client_sock, reply, strlen(reply));
if(debug) fprintf(stdout, reply);
if(len <=0){
fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
}
}
void unimplemented(int client_sock){
const char * reply = "HTTP/1.0 501 Method Not Implemented\r\n\
Content-Type: text/html\r\n\
\r\n\
\r\n\
\r\n\
Method Not Implemented \r\n\
\r\n\
\r\n\
HTTP request method not supported.\r\n\
\r\n\
"
;
int len = write(client_sock, reply, strlen(reply));
if(debug) fprintf(stdout, reply);
if(len <=0){
fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
}
}
void bad_request(client_sock){
const char * reply = "HTTP/1.0 400 BAD REQUEST\r\n\
Content-Type: text/html\r\n\
\r\n\
\r\n\
\r\n\
BAD REQUEST \r\n\
\r\n\
\r\n\
Your browser sent a bad request!\r\n\
\r\n\
"
;
int len = write(client_sock, reply, strlen(reply));
if(len<=0){
fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
}
}
void inner_error(int client_sock){
const char * reply = "HTTP/1.0 500 Internal Sever Error\r\n\
Content-Type: text/html\r\n\
\r\n\
\r\n\
\r\n\
\r\n\
Inner Error \r\n\
\r\n\
\r\n\
服务器内部出错.\r\n\
\r\n\
"
;
int len = write(client_sock, reply, strlen(reply));
if(debug) fprintf(stdout, reply);
if(len <=0){
fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
}
}
第一步:将以上代码复制进minihttp.c文件中,编译:gcc minihttp.c -lpthread -o minihttp
第二步:新建一个html_dock文件夹,里面放置一个test.html文件。文件内容如下所示:
test
恭喜,http服务器示例测试成功
第三步:运行minihttp文件,./minihttp
第四步:在浏览器访问你的ip(或者域名)+test.html
,示例:http://anchenliang.com/test.html
,结果如下: