1.客户端请求+服务器响应协议说明及流程
2.服务器接收http请求
3.服务器解析http请求
4.服务器响应http请求
5.并发处理
1.客户端请求+服务器响应
流程如下:
本质上就是读取字节流,解析字节流的信息,得到文件名,在服务器上找到文件,然后再按格式发出去。
注意:字段名: 后面是有空格的。请求头部结束是有回车换行的。
1.主函数
#include<winsock.h> //sockaddr_in
#include<socketapi.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#define SERVER_PORT 80
typedef int socklen_t;
static int debug = 0;
int get_line(int sock,char *buf,int size);
void* do_http_request(void* arg);
void do_http_response(int client_sock,const char *path);
void headers(int client_sock, FILE * resource);
void cat(int client_sock, FILE* resource);
//异常处理
void bad_request(int client_sock);//400
void unimplemented(int client_sock);//501
void not_found(int client_sock);
int httpmain(void)
{
//1.创建连接
int sock;
struct sockaddr_in server_addr;
sock = socket(AF_INET,SOCK_STREAM,0);
//bzero(&server_addr, sizeof(server_addr));//清空标签
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
bind(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
listen(sock, 128);
int done = 1;
//2.解析http
while (done)
{
struct sockaddr_in client;
int client_sock, len, i;
char client_ip[64];
char buf[256];
socklen_t client_addr_len;
sock = socket(AF_INET, SOCK_STREAM, 0);
//bzero(&server_addr, sizeof(server_addr));//清空标签
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
//打印客户端IP地址和端口号
/*printf("client ip:%s\t port:%d\n",
inet_ntop(AF_INET,&client.sin_addr,client_ip,sizeof(client_ip)),
ntohs(client.sin_port));
//读取客户端发送的数据
len = read(client_sock, buf, sizeof(buf) - 1);
buf[len] = '\0';
//转换成大写
for (i = 0; i < len; i++)
{
buf[i] = toupper(buf[i]);
}
len = write(client_sock, buf, len);
printf("finished len:%d\n", len);
close(client_sock);*/
int* pclient_sock = NULL;
//启动线程处理http请求
pclient_sock = (int*)malloc(sizeof(int));
*pclient_sock = client_sock;
pthread_create(&id, NULL, do_http_request, pclient_sock);
close(client_sock);
}
close(sock);
return 0;
}
3.服务器接收http请求
3.1读取每一行 int get_line(int sock, char* buf, int size)
//读取每一行:返回值:-1:读取出错;0:读空行;>0:成功读取
int get_line(int sock, char* buf, int size) {
int count = 0;
char ch = '\0';
int len = 0;
while ((count<size-1)&&ch!='\n')
{
len = read(sock, &ch, 1);
if (len == 1)//如果读取正常
{
if (ch == '\r')//如果是回车
{
continue;
}
else if (ch == '\n')//如果是换行
{
break;
}
buf[count] = ch;
count++;
}
else if(len==-1)//如果读取异常
{
count = -1;
break;
}
}
if (count >= 0)buf[count] = '\0';
return count;
}
3.2//解析请求:GET url 文件 void* do_http_request(void* arg);
如果碰到2个连续的回车换行即意味着请求头部结束了。
void* do_http_request(void* arg) {
int len = 0;
char buf[256];
char method[64];
char url[256];
char path[256];
struct stat st;
int client_sock = *(int*)arg; //为了被线程调用,必须要改成指针
len = get_line(client_sock, buf, sizeof(buf)); //1.读取请求行
if (len > 0)
{
int i = 0, j = 0;
while (!isspace(buf[j]) && (i < sizeof(method) - 1))
{
method[i] = buf[j];
i++;
j++;
}
method[i] = '\0';
if (debug) printf("request method:%s\n", method);
if (strncasecmp(method,"GET",i)==0) //2.GET方法 strncasecmp:比较参数s1和s2字符串前n个字符
{
if (debug) printf("method=GET\n");
while (isspace(buf[j++])); // isspace跳过白空格
i = 0;
while (!isspace(buf[j]) && (i < sizeof(url) - 1)) //3.得到URL
{
url[i] = buf[j];
i++;
j++;
}
url[i]= '\0';
if (debug) printf("url:%s\n", url);
char* pos = strchr(url, "?"); //4.获取文件 4.1截取 ?前的路径
if (pos)
{
*pos = '\0';
printf("real url: %s\n",url); //index.html ->./html_docs/index.html
}
sprintf(path, "./html_doc%s", url); //定位 只能截取一个/ path就是主目录下的全文件路径
if (path[strlen(path)-1]=='/') //如果只有/那么就显示index.html
{
strcat(path, "index.html");
}
if (debug) printf("path:%s\n", path);
do { //5.不断的读
len = get_line(client_sock, buf, sizeof(buf));
} while (len > 0);
if (stat(path, $st) == -1) //6.判断文件是否存在
{
not_found(client_sock);
}
else
{
if (S_ISDOR(st.st_mode)) // 文件存在 S_ISDOR
{
strcat(path, "/index.html");
}
do_http_response(client_sock, path); //7.响应
}
}
else {
bad_request(client_sock); //8.出错处理
}
if (arg) free(arg);
close(client_sock);
return NULL;
}
}
4.服务器响应http请求
void do_http_response(int client_sock,const char *path);
void headers(int client_sock, FILE * resource);
void cat(int client_sock, FILE* resource);
//响应:把文件发出去
void do_http_response(int client_sock, const char* path){
FILE* resource = NULL;
resource = fopen(path, "r");
if (resource == NULL) {
not_found(client_sock);
}
else
{
headers(client_sock, resource);
cat(client_sock, resource);
}
fclose(resource);
sleep(20);
}
void headers(int client_sock, FILE* resource){
char buf[1024];
char tmp[64];
int fileid = 0;
struct stat st;
strcpy(buf,"HTTP/1.0 200 OK\r\n");
strcpy(buf, "Server: Martin Server\r\n");
strcpy(buf, "Content-Type text/html\r\n");
strcpy(buf, "Connection:Close\r\n");
fileid = fileno(resource);
if (fstat(fileid, &st) == -1) {
inner_error(client_sock);
}
snprintf(tmp,64,"Content-Length: %ld\r\n\r\n",st.st_size);
strcat(buf, tmp);
if (send(client_sock, buf, strlen(buf), 0) < 0)
{
fprintf(stderr,"send failed %s, %s\n",buf,strerror(errno));
}
}
void cat(int client_sock, FILE* resource){
char buf[1024];
fgets(buf, sizeof(buf), resource);
while (!feof(resource))
{
int len = write(client_sock, buf, strlen(buf));
if (len > 0)printf("%s", buf);
fgets(buf, sizeof(buf), resource);
}
}
发的内容就是字符串 header+body.
截取?之前的字符,得到文件名。
判断文件是否存在于服务器上 ,用struct stat st;
如果不存在
内部错误
5.并发
就是用线程处理http的请求以下为注意点:
1.添加线程指针 int *pclient_sock=NULL;
2.函数返回值改成指针 void * do_http_request(void * pclient_sock)
3.函数传递的参数改成指针
void * do_http_request(void * pclient_sock)
{
int client_sock=*(int *)pclient_sock;
close(client_sock);
if(pclient_sock) free(pclient_sock);//释放动态分配的内存
return NULL;
}