这个搞了一天才搞通,主要问题是string的函数用法还不太熟练,还得一遍学这个一遍捋逻辑,C++基础还是要打牢。
接收并处理HTTP请求的思路:
由于我们一边需要处理HTTP请求,一边还要判断请求是否正确,所以设置两个状态机,一个状态机负责HTTP的状态,一个状态机负责请求行的状态。
HTTP请求示例如下:
当接收到HTTP请求时,需要首先处理一行。也就是将一行单独摘出来,看看这一行是否正确。从状态机就负责判断这一行是正确的,还是错误的,还是没有读完需要继续读。
随后根据当前主状态机的状态,判断这行内容是请求行的还是头部字段的。
主状态机第一个状态是请求行。请求行需要首先判断是否是GET,其次判断URL是不是正确的(即http://开头,同时后面有一个’/‘),最后判断HTTP版本是不是HTTP/1.1。(这里的字符串操作比较多,我对书上的方法不太熟练,就查了查api自己写了,功能应该一样,不知道有bug没)
随后处理头部字段,头部字段每个都是一行(这个HTTP请求示例是空格,我说怎么和服务器处理逻辑对不上…)。如果头部字段是"\r\n",则代表一个头部字段处理完了。如果是"\0",代表整个请求处理结束(这点很坑,在处理行时需要额外判断,否则会卡住,搞了半天…)。
代码如下:
服务器
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#define BUFFERSIZE 4096
//主状态机状态,一个是检测请求行 另一个是检测头部
enum CHECK_STATE {CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER};
//从状态机状态,行完整,行不完整,行错误
enum LINE_STATES {LINE_OK = 0, LINE_OPEN, LINE_BAD};
//服务器处理结果,完整请求,不完整,错误请求,没有访问权限,服务器内部错误,客户端关闭
enum HTTP_CODE {GET_REQUEST = 0, NO_REQUEST, BAD_REQUEST, FORBIDEN_REQUEST, INTERNAL_ERROR, CLOSE_CONNECTION};
//应答报文
const char* szret[] = {"I get a correct result. \n", "Something wrong. \n"};
HTTP_CODE parse_header(char* buf){
printf("parse header:%s\n", buf);
if(buf[0] == '\0'){//完整结束,得到一个正确的HTTP请求
printf("finish!!!\n");
return GET_REQUEST;
}else if(strncasecmp(buf, "Host:", 5) == 0){
buf += 5;
buf += strspn(buf, "\t");
printf("The request host is : %s \n", buf);
}else{
printf("I can't handle this header:%s \n", buf);
}
return NO_REQUEST;
}
HTTP_CODE parse_request(char* buf, CHECK_STATE& check_state){
printf("buf: %s \n", buf);
char* url = strchr(buf, ' ');
if(!url){
return BAD_REQUEST;
}
*url++ = '\0';
char* method = buf;
if(strcasecmp(method, "GET") == 0){
printf("The method is GET \n");
}else{
return BAD_REQUEST;
}
char* version = strchr(url, ' ');
if(!version){
return BAD_REQUEST;
}
*version++ = '\0';
if(strcasecmp(version, "HTTP/1.1") != 0){
return BAD_REQUEST;
}else{
printf("version is %s \n", version);
}
if(strncasecmp(url, "http://", 7) == 0){
char* t = strchr(url, '/');
if(!t)
return BAD_REQUEST;
printf("url is %s \n", url);
}else{
return BAD_REQUEST;
}
check_state = CHECK_STATE_HEADER;
return NO_REQUEST;
}
//从状态机,分析行
LINE_STATES parse_line(char*buf, int& check_index, int& read_index){
printf("parse line:%s\n", buf + check_index);
char temp;
if(check_index == read_index){ //最后的\0
return LINE_OK;
}
for(;check_index < read_index; ++check_index){
temp = buf[check_index];
if(temp == '\r'){//如果是\r,则必须后面是\n是一个完整行
if((check_index + 1) == read_index){//需要继续读取数据
return LINE_OPEN;
}else if(buf[check_index + 1] == '\n'){//完整行
buf[check_index++] = '\0';
buf[check_index++] = '\0';
return LINE_OK;
}else{//错误行
return LINE_BAD;
}
}else if(temp == '\n'){ //前面必须是\r才是完整行
if(check_index > 1 && buf[check_index - 1] == '\r'){
buf[check_index - 1] = '\0';
buf[check_index++] = '\0';
return LINE_OK;
}else{//错误行
return LINE_BAD;
}
}
}
return LINE_OPEN;
}
//分析内容
HTTP_CODE parse_content(char* buf, int& start_line, int& check_index, int& read_index, CHECK_STATE& check_state){
LINE_STATES line_state = LINE_OK; //行状态
HTTP_CODE retcode = NO_REQUEST; //记录http请求的结果
//读取一行,每读一行就分析一行
while((line_state = parse_line(buf, check_index, read_index)) == LINE_OK){ //分析行
char* temp = buf + start_line;//行的起始位置
start_line = check_index; //之前的行分析过没问题了,下次就前移
switch(check_state){
case(CHECK_STATE_REQUESTLINE):{//请求行
retcode = parse_request(temp, check_state);
if(retcode == BAD_REQUEST)
return retcode;
break;
}
case(CHECK_STATE_HEADER):{//头部字段
retcode = parse_header(temp);
if(retcode == BAD_REQUEST){
return retcode;
}else if(retcode == GET_REQUEST){
return retcode;
}
break;
}
default:{
return INTERNAL_ERROR;
}
}
}
if(retcode == NO_REQUEST){//还没读完
return retcode;
}else{//出错
return BAD_REQUEST;
}
}
int main(int argc, char* argv[]){
if(argc < 3){
printf("ip & port \n");
return 0;
}
char* ip = argv[1];
int port = atoi(argv[2]);
int sockfd = socket(AF_INET,SOCK_STREAM, 0);
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &addr.sin_addr);
int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
assert(ret != -1);
ret = listen(sockfd, 5);
assert(ret != -1);
struct sockaddr_in client_addr;
socklen_t client_addrlen;
int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addrlen);
if(connfd < 0){
printf("error is %d \n", errno);
}else{
char buf[BUFFERSIZE];
int data_read = 0;
int start_index = 0;//行在buf中的起始位置
int check_index = 0;//检查到的位置
int read_index = 0;//读取的位置
CHECK_STATE check_state = CHECK_STATE_REQUESTLINE; //开始需要检查请求行
while(1){
//先读取数据
data_read = recv(connfd, buf + read_index, BUFFERSIZE - read_index, 0);
if(data_read == -1){
printf("read failure \n");
break;
}else if(data_read == 0){
printf("client closed \n");
break;
}
//多读取了一段
read_index += data_read;
//分析还未分析的客户数据
HTTP_CODE result = parse_content(buf, start_index, check_index, read_index, check_state);
if(result == GET_REQUEST){//得到一个正确的完整的请求
send(connfd, szret[0], strlen(szret[0]), 0);//strlen 和 sizeof区别还要再搞清楚,不能乱用
break;
}else if(result == NO_REQUEST){//尚未得到完整的请求
continue;
}else{//得到错误的请求
send(connfd, szret[1], strlen(szret[1]), 0);
break;
}
close(connfd);
}
close(connfd);
return 0;
}
}
客户端
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#define BUFFER_SIZE 1024
int main(int argc, char* argv[]){
if(argc <= 2){
printf("ip and port and size!!!\n");
return 0;
}
char* ip = argv[1];
int port = atoi(argv[2]);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
inet_pton(AF_INET, ip, &serveraddr.sin_addr);
if((connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr))) < 0){
printf("connect failure \n");
}else{
char str[] = "GET http://www.baidu.com/index.html HTTP/1.1\r\nUSER-Agent:Wget/1.12(linux-gnu)\r\nHost:www.baidu.com\r\nConnection:close\r\n";
printf("%s \n", str); //这里不能用sizeof,sizeof的结果不对
send(sockfd, str, strlen(str), 0);
char recvbuf[BUFFER_SIZE];
memset(recvbuf, '\0', BUFFER_SIZE);
int ret;
while((ret = recv(sockfd, recvbuf, BUFFER_SIZE - 1, 0)) != 0){
printf("recieve %d bytes, content is :%s \n", ret, recvbuf);
}
}
close(sockfd);
return 0;
}
运行结果:
服务器
客户端
PS:这个搞了很久,之前就大致看了一下,最后面一章需要写简单web服务器的时候就很迷,还是要搞清楚。最后,这个代码是我根据自己理解参考书上敲的,可能有bug,欢迎大家指正。