项目预期结果:该http服务器的主要功能是,在任何系统的浏览器端访问服务器ip+html文件名,如果服务端如果存在我们想要的html文件,则将其展示在浏览器端否则发送NOT FOUND页面,同时允许上千个浏览器进行访问。
网络通信的结构框图:
项目具体子函数解析:
检查错误函数per:主要是检查write或者read函数是否出错。
void per(int sock1, const char* des)
{
if (sock1 == -1)
{
printf("%s error! The reason is: %s\n", des, strerror(errno));
exit(1);
}
}
创建服务器的socket,直接先上代码:基本就是模板操作,先定义int sock_sever,再定义好sever_addr,再设好值后进行bind和listen。per函数进行校验错误。返回res进行检验函数是否出错。
//创建服务器套接字和addr,发生错误返回-1
int server_get_sock(int * sock_sever)
{
*sock_sever=(socket(AF_INET, SOCK_STREAM, 0));//创建套接字,IPV4格式ip地址;TCP协议
per(*sock_sever, "socket");
struct sockaddr_in server_addr; //创建套接字的地址和端口号
bzero(&server_addr, sizeof(server_addr));//清零
server_addr.sin_family = AF_INET; // IPV4
server_addr.sin_port = htons(SEVER_PORT); //端口号主机字节序转网络字节序
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //接收本地所有的IP地址
int res(bind(*sock_sever, (struct sockaddr*)&server_addr, sizeof(server_addr))); //绑定套接字
per(res, "bind");
res = (listen(*sock_sever, 64)); //准备接受信息,最多等待64个客户端
per(res, "listen");
printf("Waiting:...\n");
return res;
}
创建客户端socket:创建客户端socket和addr
int client_get_sock(int sock_sever)//创建客户端套接字和addr,错误执行返回-1
{
int client_sock;
struct sockaddr_in client_addr;
socklen_t client_addr_len(sizeof(client_addr)); //固定数据格式socklen_t
client_sock = accept(sock_sever, (struct sockaddr*)&client_addr, &(client_addr_len));//accept函数需要传入client_addr_len地址
per(client_sock, "accept");
//打印出客户端端口号和IP地址
char ip_buf[50];
printf("client ip:%s, port is %d\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_buf, sizeof(ip_buf)), ntohs(client_addr.sin_port));
return client_sock;
}
对服务器来说,最重要的部分就是请求方法和URL。且每一行都是\r\n作为收尾,遇到连续两个\r\n\r\n则表示请求结束。故可以采用按行来进行读取数据区解析数据包。
int get_line(int sock, char *buf, int lenth) //返回-1表示出错,返回0表示空行 ,大于0表示读取到正常数据
{
int count(0);
int len(0);
char ch('\0');
while ((count < lenth - 1)&& ch != '\n')
{
len = read(sock, &ch, sizeof(ch)); //先逐个读取,去掉空格和回车
if (len == 1)
{
if (ch == '\r')
continue;
else if (ch == '\n')
{
break;
}
buf[count] = ch;
count++;
}
else if(len == -1)//读取出错误
{
per(len, "read");
count = -1; //向其他函数表示此时读取出错
break;
}
else//断网了客户端关闭sock连接 read返回0
{
printf("client close\n");
count = -1; //向其他函数表示此时读取出错
break;
}
}
if(count >0) //读取到了数据,说明请求头没结束
buf[count] = '\0';
return count;
}
处理请求数据:优先读取出第一行数据得到请求方法(重点处理GET方法)和获取URL方便回传给浏览器目标html文件。此处的函数定义为void* xxx(void *)是为了后续的并发线程函数
void* do_http_request(void* p_sock)
{
int sock = *(int *)p_sock; // 强制转换
char buf[SIZE];//读出来每一行的数据的缓冲区
int len(0);//检查标志位
char headmed[16], url[256];// url存放目标网址, headmed存放命令类型
//根据HTTP协议先把情报头部第一行读取出来,得到method和URL
len = get_line(sock, buf, sizeof(buf));//只读一行,获取头部
printf("Head is : %s\n", buf);
if (len>0)//头部获取成功,程序只处理get请求并且获取URL
{
int i(0), j(0);
while (!isspace(buf[j]) && i < sizeof(headmed) - 1) //buf[j]不是空格,且headmed还能存放的下命令字符
{
headmed[i] = buf[j];
i++;
j++;
}
j += 2;// 跳过buf内白空格,跳过第一个/符号
headmed[i] = '\0';
if (!strncasecmp(headmed, "GET", i)) //判断是否是GET方法 且不区分大小写
{
printf("method is : GET\n");
i = 0;
while (!isspace(buf[j]) && i < sizeof(url) - 1) //buf[j]不是空格 存放URL
{
url[i] = buf[j];
i++;
j++;
}
char* p(strchr(url, '?')); //查找url中是否有?若有则把其替换成\0
if (p)
{
*p = '\0';
}
else
{
url[i] = '\0';
}
printf("URL:%s\n", url);
定位URL,存放到path中
char path[256];
sprintf(path, "./html_dock/%s", url); //把path格式化成“./html_dock/%s”
printf("Last The target issue path is :%s\n", path);
//判断文件是否存在
struct stat st; //判断目标文件是否存在服务器内
if (stat(path, &st) == -1)// 文件不存在
{
sprintf(path, "./html_dock/err.html"); //直接向客户端传输预备好的err.html
fprintf(stderr, "stat %s failed ,reason: %s\n", path, strerror(errno));
notfound(sock, path);//404函数,下面有代码
}
else
{
if (S_ISDIR(st.st_mode))// 若url内得到的是一个目录,自动为客户端传送index.html
{
strcat(path, "/index.html");//给目录URL追加一个index.html,输出默认网址
}
do_http_respons(sock, path); // 请求处理函数
}
//读取请求头部剩余的每一行
while (len > 0)
{
len = get_line(sock, buf, sizeof(buf));
per(len, "read1");
}
}
else // 非GET请求 返回给客户端501 Method not found
{
fprintf(stderr, "Error! Can not solove the request:%s", headmed);
len = get_line(sock, buf, sizeof(buf));
printf("read:%s\n", buf);
not_501(sock); //510函数
}
}
else
{
not_400(sock); //400函数
len = get_line(sock, buf, sizeof(buf));
per(len, "read1");
}
close(sock);
return NULL;
}
请求处理函数:GET:向浏览器输出指定的文件。
HTTP的响应的格式如下所示:主要包括响应头部(状态行+消息报头),响应正文两部分。
void do_http_respons(int client_sock, const char *path)
{
FILE* re = NULL;
re = fopen(path, "r"); //打开目标路径下的html文件
if (re == NULL)
{
notfound(client_sock, path); //文件不存在,发送404
return;
}
//发送http头部
headers(client_sock, re);
//发送httpbody
body(client_sock, re);//发送正文,也就是html文件内容
fclose(re);
}
发送http头部函数:
void headers(int client_sock, FILE* re)
{
char buf[SIZE] = { 0 };
strcpy(buf, "HTTP/1.0 200 OK\r\n");
strcat(buf, "Server: Ve Server\r\n");
strcat(buf, "Content-Type:text/html\r\n");
strcat(buf, "Connection:Close\r\n");
int fileid(fileno(re)); //返回re文件流的文件描述符,因为需要文件长度
struct stat st;
fstat(fileid, &st); //检查文件是否存在。
char temp[64];
sprintf(temp, "Content-Length: %ld\r\n\r\n", st.st_size); //文件长度
strcat(buf, temp);
printf("Headder:%s\n", buf);
int len=send(client_sock, buf, strlen(buf), 0);
if (!len)//如果发送失败
{
fprintf(stderr, "send failed. data: %s, reason: %s\n", buf, strerror(errno));
}
}
发送html文件内容:
void body(int client_sock, FILE* re)
{
char buf[SIZE];
//读取文件所有内容发送给客户端
do //按行读取并发送
{
fgets(buf, sizeof(buf), re);
int len = write(client_sock, buf, strlen(buf));
per(len, "write");
printf("body: %s", buf);
} while (!feof(re));//直到文件末尾
}
404函数:
void not_400(int client_sock)
{
const char* reply = "HTTP/1.0 400 BAD REQUEST Error\r\n\
Content-Type: text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\n\r\
< meta content = \"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\r\
< head >\n\r\
<meta http - equiv = \"Content-Type\" content = \"text/html; charset=UTF-8\">\n\r\
<title>BAD REQUEST< / title>\n\r\
< / head>\n\r\
<BODY>\n\r\
<P>BROWSER sent a bad request!\n\r\
< / BODY>\n\r\
< / HTML>";
int len(write(client_sock, reply, strlen(reply)));
per(len, "write111");
}
501函数:
void not_501(int client_sock)
{
const char* reply = "HTTP/1.0 501 Wrong Method\r\n\
Content-Type: text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\n\r\
<meta http - equiv = \"Content-Type\" content = \"text/html; charset=UTF-8\">\n\r\
< head >\n\r\
\
<title>BAD REQUEST< / title>\n\r\
< / head>\n\r\
<BODY>\n\r\
<P>BROWSER Use Wrong Method\n\r\
< / BODY>\n\r\
< / HTML>";
int len(write(client_sock, reply, strlen(reply)));
per(len, "write");// < meta content = \"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\r
}
400函数:
void not_400(int client_sock)
{
const char* reply = "HTTP/1.0 400 BAD REQUEST Error\r\n\
Content-Type: text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\n\r\
< meta content = \"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\r\
< head >\n\r\
<meta http - equiv = \"Content-Type\" content = \"text/html; charset=UTF-8\">\n\r\
<title>BAD REQUEST< / title>\n\r\
< / head>\n\r\
<BODY>\n\r\
<P>BROWSER sent a bad request!\n\r\
< / BODY>\n\r\
< / HTML>";
int len(write(client_sock, reply, strlen(reply)));
per(len, "write111");
}
最后的主函数:
#define SEVER_PORT 80
#define SIZE 256
int main()
{
int sock_sever,res;
res = server_get_sock(&sock_sever);
per(res, "get_sock");
int flag(1);
while (flag)
{
int client_sock(client_get_sock(sock_sever));
//开启线程,实现并发
pthread_t p_id; //保存线程id用
int err(pthread_create(&p_id, NULL, &do_http_request, (void *)&client_sock));
per(err, "p_create");
}
close(sock_sever);
return 0;
}
如何让程序运行起来
直接linux下g++ server.cpp -o -pthread server
运行
另起一个终端,远程连接到服务器
打开浏览器输入ip地址+模板url
若输入错误网址
至此已经实现了并发访问服务器