网络编程11笔记

本章介绍如何编写HTTP服务器

HTTP服务器的主要功能是:在浏览器端访问服务器IP+html文件名,结果为如果服务器存在我们想要的HTML文件,则会将其展示在浏览器端。

HTTP服务器是基于TCP/IP协议的,上图只是简单的消息发送过程,重点在服务器的行为,其对应相应回复后,立刻断开连接,不会维持客户端状态,即使同一客户端再次发送请求,服务端也认不出来,想原来一样处理信息。

下面看请求消息的结构

客户端向服务段申请的具体格式如下:

先看请求行:请求方法 URL 协议版本都是有空格隔开的 URL是网络资源地址 

<协议名+域名+路径资源+查询参数>  问号后面是查询参数

二部分是请求头部:请求头部包含多个字段,比如connection字段、Cache-Control字段等,中间有个冒号,冒号后面是该字段具体的值。

然后紧跟着一个空行,即只有回车符和换行符;用以对“请求头部”和“请求数据”进行分隔的。

最后是请求数据。
下面看相应消息的结构:

服务器端相应的状态码有如下几种:

相应消息结构示例:

HTTP/1.0 200 OK
Server: Martin Server
Content-Type: text/html
Connection: Close
Content-Length: 526

<html lang="zh-CN">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>This is a test</title>
</head>
<body>
<div align=center height="500px" >
<br/><br/><br/>
<h2>大家好,这是响应的数据!</h2><br/><br/>
<form action="commit" method="post">
尊姓大名: <input type="text" name="name" />
<br/>芳龄几何: <input type="password" name="age" />
<br/><br/><br/><input type="submit" value="提交" />
<input type="reset" value="重置" />
</form>
</div>
</body>
</html>

下面是简单的http服务器代码

#include<iostream>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<pthread.h>
#include <string.h>

const int MAX_SIZE=1024;
const int SMALL_SIZE=100;
void* requst_handle(void* arg);
void send_data( FILE*p,char* st,char* file_name);
void send_error(FILE*p);
char* content_type(char* file);

int main(int argc,char* argv[]){
    int serv_sock,clnt_sock;
    struct sockaddr_in serv,clnt;
    socklen_t clnt_size;
    char buf[MAX_SIZE];
pthread_t id;
    if(argc!=2){
        std::cout<<"error "<<std::endl;
        exit(1);
    }
    
    serv_sock=socket(PF_INET,SOCK_STREAM,0);
    memset(&serv,0,sizeof(serv));
    serv.sin_family=AF_INET;
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
    serv.sin_port=htons(atoi(argv[1]));

if(bind(serv_sock,(struct sockaddr*)&serv_sock,sizeof(serv_sock))==-1){
    std::cout<<" bind() error"<<std::endl;
}

if(listen(serv_sock,5)==-1){
    std::cout<<" listen()error"<<std::endl;
}

while(1){
clnt_size=sizeof(clnt);
clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt,&clnt_size);
if(clnt_sock==-1){
    std::cout<<"accept() error"<<std::endl;
    break;
}
std::cout<<"connect clnt_sock :"<<inet_ntoa(clnt.sin_addr)<<" the port"<<ntohs(clnt.sin_port)<<std::endl;
pthread_create(&id,nullptr,requst_handle,(void*)clnt_sock);
pthread_detach(id);
}

    close(serv_sock);
    return 0;
}

void* requst_handle(void* arg){
    int clnt_sock=*((int*)arg);
     char requir_line[SMALL_SIZE];
     FILE* clnt_write;
     FILE* clnt_read;

     char file_name[30];
     char ct[15];
     char methed[10];
     clnt_write=fdopen(clnt_sock,"w");
     clnt_read=fdopen(clnt_sock,"r");
     fgets(requir_line,SMALL_SIZE,clnt_write);//  将传入的消息存入  
     if(strstr(requir_line,"HTTP/")==nullptr){
        send_error(clnt_write);
        fclose(clnt_read);
        fclose(clnt_write);
        return nullptr;
     }

     strcpy(methed,strtok(requir_line," /"));//分割字串 以 /整体为标识符分割字串,并将其变为/0  ,aaa /dddd ded  调用两次,第一次是aaa 第二次没有 /,则以 (空格来切割)就是dddd 
     strcpy(file_name,strtok(nullptr," /"));//第二次调用开始以后 strtok函数第一个参数传递null即可 因为此时定位的起始起点是上一次调用的分割字符串位置的下一个
     strcpy(ct,content_type(file_name));//查看conteny_type
     if(strcmp(methed,"GET")==0){
        send_error(clnt_write);
        fclose(clnt_write);
        fclose(clnt_write);
        return nullptr;
     }

     fclose(clnt_read);
     send_data(clnt_write,ct,file_name);
}

void send_data(FILE* clnt_write,char* ct,char*file_name){
     char protocol[]="HTTP/1.0 200 OK\r\n";
    char server[]="Server:Linux Web Server \r\n";
    char cnt_len[]="Content-length:2048\r\n";
    char cnt_type[SMALL_SIZE];
    char buf[MAX_SIZE];
    FILE* senf;
    senf=fopen(file_name,"r");//打开对应文件 返回文件描述符
    sprintf(cnt_type,"Connect-type:%s\r\n\r\n",ct);
    if(senf==nullptr){
        send_error(clnt_write);
        return ;
    }
    //回应消息 以行为单位传进去
fputs(protocol,clnt_write);
fputs(server,clnt_write);
fputs(cnt_len,clnt_write);
fputs(cnt_type,clnt_write);

while(fgets(buf,MAX_SIZE,senf)!=nullptr){  //用于传输数据 从指定文件读取数据后存入buf  
    fputs(buf,clnt_write);//再由buf转发
    fflush(clnt_write);
}
fflush(clnt_write);
fclose(clnt_write);
//传输完毕
}

char* content_type(char* file){
char extension[SMALL_SIZE];
char file_name[SMALL_SIZE];
strcpy(file_name,file);
strtok(file_name,".");
strcpy(extension,strtok(nullptr,"."));
if(!strcmp(extension,"html")||strcmp(extension,"htm"))//即对应文件有这两个
return "text/html";
else
return "text/plain";
}

void send_error(FILE* p){
    char protocol[]="HTTP/1.0 400 Bad Request\r\n";
    char server[]="Server:Linux Web Server \r\n";
    char cnt_len[]="Content-length:2048\r\n";
    char cnt_type[]="Content-type:text/html\r\n\r\n";
    char content[]="<html><head><title>NETWORK</title></head>"
                   "<body><font size=+5><br>错误!查看请求文件名和请求方式!"
                   "</font></body></html>";

fputs(protocol,p);
fputs(server,p);
fputs(cnt_len,p);
fputs(cnt_type,p);
fflush(p);
}

上述代码简化了 只有请求行和消息头,且只分割请求行,不包括消息行,只需注意一点就是用字符串函数如何进行分割字符串即可。

下面是分析的github上面的minihttp代码,注释已经加上

#include<iostream>
#include<sys/socket.h>
#include<pthread.h>
#include<unistd.h>
#include<arpa/inet.h>
#include <string.h>
#include<sys/stat.h>
#include<sys/wait.h>

void* jieshou(void* tag);
int get(char*,int,int);
void unImplemented(int);
void serve_file(int,char*);
void execute_cgi(int,char*,char*,char*);
void not_found(int);
void cat(int,FILE*);
void headers(int,const char*);
void bad_request(int);
 void cannot_execute(int);


void* jieshou(void* tag){
    int clnt_sock=(*(int*)tag);
    char buf[1024];
    char method[256];
    char filename[256];
    char path[256];
    char* idex=nullptr;
    size_t jieshousize;
    int dix=0;
    struct stat st;
    jieshousize=get(buf,clnt_sock,sizeof(buf));
size_t i,j;
while(!isspace(buf[i])&&i<sizeof(method)-1){//isspace函数用于检查是否是空格 如果是返回非零 不是返回零
method[i++]=buf[i++];
}
method[i]='/0';//方法存入
j=i;//继续读请求行

if(strcasecmp(method,"GET")&&strcasecmp(method,"POST")){
    unImplemented(clnt_sock);//HTTP请求没有该请求
    return nullptr;
}

if(strcasecmp(method,"POST")==0)
dix=1;//POST请求中,消息头后面还有信息  GET请求不一定 但是如果GET中有? 则后面还有信息 需要处理?后面的信息

i=0;
while(isspace(buf[j])&&j<jieshousize){
   j++;
}//跳过所有空格
while(!isspace(buf[j])&&j<sizeof(filename)-1){
    filename[i++]=buf[j++];
}
filename[i]='\0';
//客户端发起请求的url有的带有? 有的没有
if(strcasecmp(method,"GET")==0 ){//GET请求方式
idex=filename;//指向urL 起始位
while((*idex!='?')&&(*idex!='\0'))
idex++;// url中如果有? 则停止在?处 如果没有 则停留在\0处
if(*idex=='?'){
    dix=1;
    *idex='\0';//?前面是网络路径+资源地址
    idex++;
}
}

//客户端请求的url中 域名IP及其前面都是必要的 后面的端口号 文件路径可能被省略
sprintf(path,"htdocs%s",filename);
if (path[strlen(path) - 1] == '/')//省略情况
        strcat(path, "index.html");//追加为完整路径 


    if (stat(path, &st) == -1) {
        while ((jieshousize > 0) && strcmp("\n", buf))  /*获得文件信息失败,将剩下HTTP请求信息存入buf中并且丢弃*/
            jieshousize= get(buf,clnt_sock,sizeof(buf));
        not_found(clnt_sock);
    }
    else
    {
        if ((st.st_mode & S_IFMT) == S_IFDIR)//存在
            strcat(path, "/index.html");
            //如果文件默认有执行权限的,自动解析为cgi程序,如果有执行权限,但不能执行 会报错
        if ((st.st_mode & S_IXUSR) ||
                (st.st_mode & S_IXGRP) ||
                (st.st_mode & S_IXOTH)    )
            dix = 1;
        if (!dix)
            serve_file(clnt_sock, path);//回应客户端的请求且不管HTTP请求体当中消息头的信息(争对的是请求行中无查询参数的,文件无默认权限的,且消息头屁股后面无消息的GET)
        else
            execute_cgi(clnt_sock, path, method,idex);//执行cgi程序(不论是GET还是POST,请求行有?的,消息头屁股后面还有信息或者默认有执行权限的)
    }

    close(clnt_sock);
}


int get(char buf[],int clnt_sock,int size){
int start=0;
int n;
char c='a';
while((start<size-1)&&c!='\n'){  //实际是0~size-1 除去最后一个\0 有效字符是0~size-2
n=read(clnt_sock,&c,1);
if(n==1){
    if(c=='/r'){//遇见空格 不管
        continue;
    }else if(c=='/n'){
        break;//此行读完
    }else{
       buf[start++]=c; 
    }
}else if(n==-1){
    std::cout<<"get-function-read-function  error"<<std::endl;
    start=-1;
    break;
}else if(n==0){
    std::cout<<"clnt close"<<std::endl;
    close(clnt_sock);
    break;
}
}//一行读取完
buf[start]='\0';
return start;
}

void serve_file(int clnt_sock,const char* path){
FILE* fp;
int size=1;
char buf[1024];

buf[0] = 'A'; buf[1] = '\0';
    while ((size > 0) && strcmp("\n", buf))  /* read & discard headers */
        size = get(buf,clnt_sock, sizeof(buf));

fp=fopen(path,"r");
if(fp==nullptr){//未找到
    not_find(clnt_sock);
}else{
    headers(clnt_sock,path);//给客户端回复文件头
    cat(clnt_sock,fp);//文件内容
}
fclose(fp);
}


//执行cgi脚本
void execute_cgi(int clnt_sock,char* path,char* method,char* index){
char buf[1024];
int output[2];
int input[2];
pid_t pid;
int i,status;
int num=1;
int cont_tent=-1;
char c;
buf[0]='A',buf[1]='\0';//确保buf中有东西 可以执行while循环

if(strcasecmp(method,"GET")==0){
while((num>0)&&(strcpy("\n",buf)))// GET模式中 将除请求行,剩下的所有信息存入buf中
num=get(buf,clnt_sock,sizeof(buf));
}else if(strcasecmp(method,"POST")==0){
//POST 先找到 Content-Length

    num=get(buf,clnt_sock,sizeof(buf));//http请求先读取一行 
    while((num>0)&&(strcpy("\n",buf))){
buf[15] = '\0';
            if (strcasecmp(buf, "Content-Length:") == 0)
                cont_tent= atoi(&(buf[16]));
            num= get(buf,clnt_sock,sizeof(buf));
    }
    if(cont_tent==-1){
        bad_request(clnt_sock);
        return ;
    }
}else{//其他请求

}
/*开辟子进程出错 或者开辟管道出错 用于结束错误*/
if (pipe(output) < 0) {
        cannot_execute(clnt_sock);
        return;
    }
    if (pipe(input) < 0) {
        cannot_execute(clnt_sock);
        return;
    }

    if ( (pid = fork()) < 0 ) {
        cannot_execute(clnt_sock);
        return;
    }
    sprintf(buf,"HTTP/1.0 200 OK\r\n");
    write(clnt_sock,buf,strlen(buf));
    if(pid==0){
        char meth_env[255];
        char query_env[255];
        char length_env[255];
         dup2(output[1], 1);//把标准输入重定位到管道写入端 即标准输入后传入管道
        dup2(input[0], 0);//标准输出重定位到管道输出端 即标准输入后 信息从input【0】进行输出
        close(output[0]);
        close(input[1]);
        //配置环境变量
        sprintf(meth_env, "REQUEST_METHOD=%s", method);
        putenv(meth_env);
        if (strcasecmp(method, "GET") == 0) {
            sprintf(query_env, "QUERY_STRING=%s",index );
            putenv(query_env);
        }
        else {   /* POST */
            sprintf(length_env, "CONTENT_LENGTH=%d",index);
            putenv(length_env);
        }
        execl(path, NULL);
        exit(0);
    } else {    /* parent */
        close(output[1]);
        close(input[0]);
        if (strcasecmp(method, "POST") == 0)
            for (i = 0; i <cont_tent; i++) {
                recv(clnt_sock, &c, 1, 0);
                write(input[1], &c, 1);
            }
        while (read(output[0], &c, 1) > 0)
            send(clnt_sock, &c, 1, 0);

        close(output[0]);
        close(input[1]);
        waitpid(pid, &status, 0);

    }

}


   void headers(int clnt_sock,const char* path){
    char buf[1024];
    (void)clnt_sock;
    strcpy(buf,"HTTP/1.0 200 OK\r\n");
   write(clnt_sock,buf,strlen(buf));
   strcpy(buf,"Server: linuxhttpd/0.1.0\r\n");
   write(clnt_sock,buf,strlen(buf));
   strcpy(buf,"Connect-Type: text/html\r\n");
   write(clnt_sock,buf,strlen(buf));
   strcpy(buf,"\r\n");
   write(clnt_sock,buf,strlen(buf));
   }

   void cat(int clnt_sock,FILE* fp){
    char buf[1024];
    fgets(buf,sizeof(buf),fp);
    while(!feof(fp)){//看文件指针是否到达文件末尾 如果未达到 返回0
        write(clnt_sock,buf,strlen(buf));
        fgets(buf,sizeof(buf),fp);
    }
   }
void not_find(int clnt_sock){
    //未找到目标文档 向客户端回复查找失败
    char buf[1024];
    sprintf(buf,"HTTP/1.0 404 NOT FOUND\r\n");
    send(clnt_sock,buf,strlen(buf),0);
    sprintf(buf,"Server: linuxhttpd/0.1.0\r\n");
    send(clnt_sock,buf,strlen(buf),0);
    sprintf(buf, "Content-Type: text/html\r\n");
    send(clnt_sock, buf, strlen(buf), 0);
    sprintf(buf, "\r\n");//剩下的消息
    send(clnt_sock, buf, strlen(buf), 0);
    sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
    send(clnt_sock, buf, strlen(buf), 0);
    sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
    send(clnt_sock, buf, strlen(buf), 0);
    sprintf(buf, "your request because the resource specified\r\n");
    send(clnt_sock, buf, strlen(buf), 0);
    sprintf(buf, "is unavailable or nonexistent.\r\n");
    send(clnt_sock, buf, strlen(buf), 0);
    sprintf(buf, "</BODY></HTML>\r\n");
    send(clnt_sock, buf, strlen(buf), 0);

}

void unImplemented(int clntsock){
    char buf[1024];
    sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
    write(clntsock, buf, strlen(buf));
    sprintf(buf, "Server: linuxhttpd/0.1.0\r\n");
    write(clntsock, buf, strlen(buf));
    sprintf(buf, "Content-Type: text/html\r\n");
    write(clntsock, buf, strlen(buf));
    sprintf(buf, "\r\n");//消息头已经结尾了
    write(clntsock, buf, strlen(buf));
    sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
    write(clntsock, buf, strlen(buf));
    sprintf(buf, "</TITLE></HEAD>\r\n");
    write(clntsock, buf, strlen(buf));
    sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
    write(clntsock, buf, strlen(buf));
    sprintf(buf, "</BODY></HTML>\r\n");
    write(clntsock, buf, strlen(buf));

}
//that a request has a problem
void bad_request(int client)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "Content-type: text/html\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "<P>Your browser sent a bad request, ");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "such as a POST without a Content-Length.\r\n");
    send(client, buf, sizeof(buf), 0);
}

void cannot_execute(int client)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "Content-type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
    send(client, buf, strlen(buf), 0);
}

int main(int argc,char* argv[]){
    struct sockaddr_in ser,clnt;
    int serv_sock,clnt_sock;
    socklen_t size;
    pthread_t t_id;
    if(argc!=2){
        std::cout<<"error"<<std::endl;
        exit(1);
    }

    serv_sock=socket(PF_INET,SOCK_STREAM,0);
    memset(&ser,0,sizeof(ser));
    ser.sin_addr.s_addr=htonl(INADDR_ANY);
    ser.sin_family=AF_INET;
    ser.sin_port=htons(atoi(argv[1]));

    if(bind(serv_sock,(struct sockaddr*)&ser,sizeof(ser))==-1){
        std::cout<<"bind()error"<<std::endl;
    }

    if(listen(serv_sock,5)==-1){
        std::cout<<"listen()error"<<std::endl;
    }

    while(1){
        size=sizeof(clnt);
        clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt,&size);
if(clnt_sock==-1){
    std::cout<<"accept()error"<<std::endl;
    break;
}
std::cout<<"connect  "<<inet_ntoa(clnt.sin_addr)<<"the port  "<<ntohs(clnt.sin_port)<<std::endl;
pthread_create(&t_id,nullptr,jieshou,(void*)clnt_sock);
pthread_detach(t_id);
    }
    close(serv_sock);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值