服务端
#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 BUFFER_SIZE 4096
//主状态机的两种可能的状态,当前正在分析请求行,当前正在分析头部字段
enum CHECK_STATE{CHECK_STATE_REQUESTLINE = 0,CHECK_STATE_HEADER};
//从状态机的三种可能的状态:1)请求行完整,2)行出错和行数据尚不完整
enum LINE_STATUS{LINE_OK = 0,LINE_BAD,LINE_OPEN};
/*HTTP处理服务器请求的结果:
NO_REQUEST表示请求不完整
GET_REQUEST表示完整的用户请求
BAD_REQUEST表示用户请求有语法错误
FORBIDDEN_REQUEST表示客户对资源没有访问权限
INTERNAL_ERROR 表示服务器内部错误
CLOSED_CONNECTION 表示客户端已经关闭连接了
*/
enum HTTP_CODE{NO_REQUEST,GET_REQUEST,BAD_REQUEST,
FORBIDDEN_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION};
static const char*szret[] = {"i get a correct result\n","Something wrong\n"};
LINE_STATUS parse_line(char*buffer,int &check_index,int &read_index){
char temp;
for(;check_index < read_index;++check_index){
temp = buffer[check_index];
if(temp == '\r'){
//如果读取回车符号的时候已经表示到了末尾,
//那就说明这个符号没有读完
if((check_index + 1) == read_index){
return LINE_OPEN;
//判断是否读取到了完整的一句话
}else if(buffer[check_index+1]=='\n'){
buffer[check_index++] = '\0';
//将当前的'\r'置为'\0'之后再向后移动一位
buffer[check_index++] = '\0';
//将当前的'\n'置为'\0'之后再向后移动一位
//为啥读到结尾要修改,是为了防止输入错误向前读取的时候出问题了吗?
return LINE_OK;
}
//否则说明这里面有语法错误
return LINE_BAD;
//我有个疑惑,为啥判断的时候不是先读取了'\r'再读取'\n'的吗?
//判断了'\r'的存在之后依然需要特别判断'\n'的存在,难道好巧不巧这个是分两次读取到的吗?
}else if(temp == '\n'){
if((check_index > 1)&&buffer[check_index-1] == '\r'){
buffer[check_index-1] = '\0';
buffer[check_index++] = '\0';
return LINE_OK;
}
return LINE_BAD;
}
}
return LINE_OPEN;
//表明所有的内容都读取完了也没有遇到终结字符,还需要继续读取,所以返回提示。
}
HTTP_CODE parse_requestline(char *temp,CHECK_STATE & checkstate)
{
//strpbrk是在源字符串(s1)中找出最先含有
//搜索字符串(s2)中任一字符的位置并返回,若找不到则返回空指针。
//如果请求行中没有'\t'则请求行有问题
char*url = strpbrk(temp,"\t");
if(!url){
return BAD_REQUEST;
}
*url++ = '\0';
char *method = temp;
//因为仅仅支持get方法
if(strcasecmp(method,"GET") == 0){
printf("The request method is GET\n");
}else {
return BAD_REQUEST;
}
url += strspn(url,"\t");
//strspn该函数返回 str1 中第一个不在字符串 str2 中出现的字符下标。
//也就是跳过空白字符,好像是http当中允许存在多个线性空格
//此时url为网址的开头
char *version = strpbrk(url,"\t");
if(!version){
return BAD_REQUEST;
}
*version++ = '\0';
//阶段网址
version += strspn(version,"\t");
//这个是version的正式开头
//忽略大小写差异的比较函数
if(strcasecmp(version,"HTTP/1.1") != 0){
return BAD_REQUEST;
}
if(strncasecmp(url,"http://",7) == 0){
url += 7;
url = strchr(url,'/');
//找到文件路径的第一个/
}
if(!url || url[0] != '/'){
return BAD_REQUEST;
}
printf("The request URL is:%s\n",url);
checkstate = CHECK_STATE_HEADER;
return NO_REQUEST;
}
HTTP_CODE parse_headers(char *temp){
if(temp[0]=='\0'){
return GET_REQUEST;
}else if(strncasecmp(temp,"Host:",5) == 0){
temp += 5;
temp += strspn(temp,"\t");
printf("the request host is:%s\n",temp);
}else{
printf("I can not handle this header\n");
}
return NO_REQUEST;
}
HTTP_CODE parse_content(char*buffer,int &checked_index,
CHECK_STATE &checkstate,int &read_index,int &start_line){
LINE_STATUS linestatus = LINE_OK;
HTTP_CODE retcode = NO_REQUEST;
//使用从状态机来读取一行
while ((linestatus = parse_line(buffer,checked_index,read_index))==LINE_OK)
{
char*temp = buffer + start_line;
start_line = checked_index;
switch (checkstate)
{
case CHECK_STATE_REQUESTLINE:
{
retcode = parse_requestline(temp,checkstate);
if(retcode == BAD_REQUEST)
return BAD_REQUEST;
}
break;
case CHECK_STATE_HEADER:
{
retcode = parse_headers(temp);
if(retcode == BAD_REQUEST){
return BAD_REQUEST;
}else if(retcode == GET_REQUEST){
return GET_REQUEST;
}
}
break;
default:{
return INTERNAL_ERROR;
}
}
}
if(linestatus == LINE_OPEN){
return NO_REQUEST;
}else {
return BAD_REQUEST;
}
}
int main(void){
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = ntohs(9999);
int listenfd = socket(PF_INET,SOCK_STREAM,0);
if(listenfd == -1){
perror("socket");
return -1;
}
int ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));
if(ret == -1){
perror("bind");
return -1;
}
ret = listen(listenfd,5);
if(ret == -1){
perror("listen");
return -1;
}
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof(client_address);
int fd = accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
if(fd == -1){
perror("accept");
return -1;
}else {
char buffer[BUFFER_SIZE];
memset(buffer,'\0',BUFFER_SIZE);
int data_read = 0;
int read_index = 0;
int checked_index = 0;
int start_line = 0;
CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;
while(1){
data_read = recv(fd,buffer+read_index,BUFFER_SIZE-read_index,0);
if(data_read == -1){
printf("reading failed\n");
break;
}else if(data_read == 0){
printf("remote client has closed the connection\n");
break;
}
read_index += data_read;
HTTP_CODE result = parse_content(buffer,checked_index,checkstate,read_index,start_line);
if(result == NO_REQUEST){
continue;
}else if(result == GET_REQUEST){
send(fd,szret[0],strlen(szret[0]),0);
break;
}else
{
send(fd,szret[1],strlen(szret[1]),0);
break;
}
}
close(fd);
}
close(listenfd);
return 0;
}
客户端
#include<stdio.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main(void){
//1.创建套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd == -1){
perror("socket");
exit(0);
}
//2.连接服务器端
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
inet_pton(AF_INET,"192.168.131.138",&serveraddr.sin_addr.s_addr);
serveraddr.sin_port = htons(9999);
int ret = connect(fd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
if(ret == -1){
perror("connect");
exit(0);
}
//3.通信
//char *data = "hello , i am client.";
char *data ="GET\thttp:///www.baidu.com\tHTTP/1.1\r\nHost:\twww.baidu.com\t\r\n\r\n";
write(fd,data,strlen(data));
char recvBuf[1024] = {0};
while(read(fd,recvBuf,sizeof(recvBuf)) > 0){
printf("recv data :%s\n",recvBuf);
}
close(fd);
return 0;
}
最大的收获是知道了HTTP的头部报文是咋写的。
还有状态机的转化
服务端运行结果
客户端运行结果