简易HTTP服务器(多进程版本)

入职公司的试用期小内容,这个是多进程的版本,其中支持了图片,网页,大致结构比较清晰,拿出来跟大家分享下。

server.c源码如下:

#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 <string.h>
#include <errno.h>
#include <sys/stat.h>

#define SERVER_NAME "IAMCAP-SERVER"
#define PROTOCOL    "HTTP/1.1"
#define REQUESTLEN 4096
#define RESPONSELEN  1024000

#define TIMELEN 55
#define TEMPLEN 100
#define BUFFERLEN 1024

#define HOMEPATH  "/Users/iamcap/http/" 

static char* add_header(int , char *, char *, off_t , char *, int );
void parsedata(char*,int);

int main(int argc, char **argv){
        if(argc <=2){
                printf("usage: %s ip_address port_number\n", basename(argv[0]));
                return 1;
        }
        const char *ip = argv[1];
        int port = atoi(argv[2]);

        //初始化通信结构体

        struct sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &address.sin_addr);
        address.sin_port = htons(port);

        //端口重复利用

        int sock = socket(PF_INET, SOCK_STREAM, 0);
        assert( sock >= 0);
        int opt = 1;
        setsockopt(sock, SOL_SOCKET,SO_REUSEADDR, &opt, sizeof(opt));  

        int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
        assert(ret != -1);

        printf("listening ...\n");
        ret = listen(sock, 5);
        assert(ret != -1); 

        
        
        while(1){
            //保存客户端的信息
            struct sockaddr_in client;      
            socklen_t client_addrlength = sizeof(client); 
            int connectfd = accept(sock,(struct sockaddr*)&client,&client_addrlength);
            if(connectfd < 0){
                  printf("errno is: %d\n", errno);
            }else{
                  //创建worker进程
                  pid_t pid = fork();  
                  if(-1 == pid){
                        printf("wrong!!!\n");
                  }else if(0 == pid){
                        //在子进程中
                        char remote[INET_ADDRSTRLEN];
                        memset(remote,'\0',INET_ADDRSTRLEN);
                        printf("\nconnected with ip: %s and port: %d\n", inet_ntoa(client.sin_addr),ntohs(client.sin_port));
                        char recvdata[REQUESTLEN];                                  
                        memset(recvdata,'\0',REQUESTLEN);

                        //从建立的socket中接收浏览器的请求信息
                        int ret = recv(connectfd, recvdata,REQUESTLEN-1, 0);
                        printf("\n%s\n",recvdata);

                        //处理接收到的信息
                        parsedata(recvdata,connectfd);
                                 
                   }else{
                        //在父进程中
                        close(connectfd);
                   }
             }
        }
        close(sock);
        return 0;
}

// send header to the  browser 

 static char* add_header(int status, char *title, char *mime_type, off_t length, char *buffer , int buffersize)
 {
   
   char temp[TEMPLEN];
   

   memset(buffer, 0, sizeof(buffersize));
   sprintf(temp, "%s %d %s\r\n", PROTOCOL, status, title);
   //将所有的头部数据格式都按照协议的标准填充到buffer
   strcat(buffer, temp);  
   sprintf(temp, "Server: %s\r\n", SERVER_NAME);
   strcat(buffer, temp);
   sprintf(temp, "Connection: Keep-Alive\r\n");
   strcat(buffer, temp);
   sprintf(temp, "Content-Type: %s\r\n", mime_type);
   strcat(buffer, temp);
   sprintf(temp, "Content-Length: %d\r\n", (int)length);
   strcat(buffer, temp);
   sprintf(temp, "Content-Language: zh-CN\r\n");
   strcat(buffer, temp);
   strcat(buffer, "\r\n");
   return buffer;
 }

static char* get_mime_type(char *filename)
{
  char *postfix;
   postfix = strrchr(filename, '.');
   if (!strcasecmp(postfix, ".html") || !strcasecmp(postfix, ".htm")){
     return "text/html; charset=UTF-8";
   }else if (!strcasecmp(postfix, ".jpg") || !strcasecmp(postfix, ".jpeg")){
     return "image/jpeg";
   }else{
     return "text/plain; charset=UTF-8";
   }
}

void parsedata(char *recvdata, int connectfd){
    //切换到主页目录
    if(chdir(HOMEPATH)<0){
        printf("chdir error\n");
        exit(0);
    }
    char method[BUFFERLEN];
    char path[BUFFERLEN];
    char protocol[BUFFERLEN];
    memset(method,'\0',BUFFERLEN);
    memset(path,'\0',BUFFERLEN);
    memset(protocol,'\0',BUFFERLEN);

    sscanf(recvdata, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
    //打开文件或文件夹
    struct stat property;
    stat(path,&property);
    if(S_ISDIR(property.st_mode)){        //如果是文件夹
        
        if(0 == strcmp(path,"/")){ //是主页目录
            //返回index.html
            FILE *fp;
            if(fp = fopen("index.html","r")){
                char head[BUFFERLEN];
                memset(head,'\0',BUFFERLEN);
                stat(path+1,&property);
                stat("index.html",&property);
                add_header(200, "OK", "text/html", property.st_size, head, BUFFERLEN);
                char temp[RESPONSELEN];
                memset(temp,'\0',RESPONSELEN);
                strcpy(temp,head);
                fread(temp+strlen(head),1,property.st_size,fp);
                send(connectfd, temp, strlen(temp),0);
                //将应答的html报文回发过去,浏览器可能还会发送图片显示请求报文
                close(connectfd);
            }
        }else{                    //其他目录,直接返回
            exit(0);
        }
    }else{                               //如果是文件
        if(0 == strcasecmp(method,"GET")){
            FILE *fp;
            if(fp = fopen(path+1,"rb")){
                char head[BUFFERLEN];
                memset(head,'\0',BUFFERLEN);
                stat(path+1,&property);
                add_header(200, "OK", get_mime_type((path + 1)), property.st_size, head, BUFFERLEN);
                char temp[RESPONSELEN];
                memset(temp,'\0',RESPONSELEN);
                strcpy(temp,head);
                fread(temp+strlen(head),1,property.st_size,fp);
                send(connectfd, temp, strlen(head)+property.st_size,0);
                close(connectfd);
            }
        }else if(0 == strcasecmp(method,"POST")){

        }else if(0 == strcasecmp(method,"HEAD")){

        }else if(0 == strcasecmp(method,"PUT")){

        }else{
            exit(0);
        }
    }
}

index.html代码如下:

<html><body><H1>this is a page</H1><img src="mypic.jpg" /></body></html>

mypic.jpg 是 /Users/iamcap/http/路径下的一张图片。


这里需要注意的有以下几点:

           1. http的本身就是一个文本文件,你需要知道http协议的格式,可以http://blog.csdn.net/gueter/article/details/1524447

           2. 在显示图片的处理过程中,记得用fread函数,不要企图使用strlen函数来获取长度,可以借助struct  stat { } 和 HTTP头部共同拼接出所发内容的长度

           3. 浏览器在浏览的过程中,可能会发送GET请求favicon.ico,这个其实就是页面显示的那个小图标,可以不用管。

           4. 在使用数组之前,尽量用memset进行初始化,免得垃圾收据会影响内容发送。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多进程和多线程都有其优缺点,没有绝对的好坏之分,需要根据实际情况进行选择。 多进程的优点在于: 1. 进程之间相互独立,一个进程崩溃不会影响其它进程,提高了系统的稳定性; 2. 进程之间可以通过进程间通信(IPC)实现数据共享; 3. 多核CPU可以充分利用。 多进程的缺点在于: 1. 进程间切换开销大,因为进程之间需要切换虚拟地址空间、文件描述符等资源; 2. 对于大量短连接的场景,进程创建和销毁的开销比较大; 3. 进程间数据共享可能会引发同步问题,需要额外的同步机制。 多线程的优点在于: 1. 线程之间共享进程的虚拟地址空间,可以方便地进行数据共享; 2. 线程切换开销比进程切换开销小,因为线程之间共享大部分资源; 3. 对于大量短连接的场景,线程创建和销毁的开销相对较小。 多线程的缺点在于: 1. 线程之间共享资源需要考虑同步和互斥问题,容易引发死锁、竞争等问题; 2. 线程之间存在数据竞争的问题,需要使用锁、信号量等机制来保证线程安全; 3. 系统稳定性受到线程之间相互影响的影响,一个线程崩溃可能导致整个进程崩溃。 综上所述,对于HTTP服务器,如果是处理大量短连接的场景,可以考虑使用多线程;如果是长连接的场景,可以考虑使用多进程。但具体选择需要根据实际情况进行权衡。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值