上嵌学习笔记

http服务器

2017-03-06  本文已影响140人  b6aed1af4328

本文对建立TCP/IP连接的服务器做了一些汇总。一个TCP/IP服务器使用epoll实现IO复用,epoll+线程池实现高并发,解决C10K问题。再者在TCP/IP连接的基础上实现了HTTP协议。

服务器的TCP/IP连接

1186132-4cab0f440489fb06.jpg

上图相当清楚的表明了如何建立TCP/IP连接,可以将服务器的TCP/IP理解为套路,晓得ipv4/ipv6的区别,设置ip和端口就能满足基本需要。若想深入了解TCP/IP协议,建议阅读<<TCP/IP详解>>卷1、2、3。

C10K

大家都知道互联网的基础就是网络通信,早期的互联网可以说是一个小群体的集合。互联网还不够普及,用户也不多。一台服务器同时在线100个用户估计在当时已经算是大型应用了。所以并不存在什么C10K的难题。互联网的爆发期应该是在www网站,浏览器,雅虎出现后。最早的互联网称之为Web1.0,互联网大部分的使用场景是下载一个Html页面,用户在浏览器中查看网页上的信息。这个时期也不存在C10K问题。
Web2.0时代到来后就不同了,一方面是普及率大大提高了,用户群体几何倍增长。另一方面是互联网不再是单纯的浏览万维网网页,逐渐开始进行交互,而且应用程序的逻辑也变的更复杂,从简单的表单提交,到即时通信和在线实时互动。C10K的问题才体现出来了。每一个用户都必须与服务器保持TCP连接才能进行实时的数据交互。Facebook这样的网站同一时间的并发TCP连接可能会过亿。
这时候问题就来了,最初的服务器都是基于进程/线程模型的,新到来一个TCP连接,就需要分配1个进程(或者线程)。而进程又是操作系统最昂贵的资源,一台机器无法创建很多进程。如果是C10K就要创建1万个进程,那么操作系统是无法承受的。如果是采用分布式系统,维持1亿用户在线需要10万台服务器,成本巨大,也只有Facebook,Google,雅虎才有财力购买如此多的服务器。这就是C10K问题的本质。
如何解决C10K问题?
一个进程处理一个连接很快遇到瓶颈,那一个线程一个连接呢?又治标不治本,所占资源仍相当可观。很快思路就转到了一个进程/线程处理多个连接,如何实现?IO多路复用。

服务器的TCP/IP连接IO复用的实现

select、poll、epoll通过异步监听连接的方式来实现IO复用,select和poll缺点很明显:select最大不能超过1024,poll没有限制,但每次收到数据需要遍历每一个连接查看哪个连接有数据请求。epoll采用事件驱动,为异步非阻塞,使用共享内存而效率最高,成为首选。
epoll是写在linux源码里的,linux提供接口函数,我们只要会调用就可以了。感兴趣的可以去看看epoll的linux源码实现。
首先实现TCP/IP连接,并设定非阻塞。然后建立epoll集合,设置监听的事件类型和模式,将服务器fd加入epoll集合来监听,当有事件产生,epoll监听到并迅速将发生事件的fd放置到events数组里,此时若fd等于客户端fd,代表有新的客户端连接,accept后将新产生的客户端fd加入epoll集合监听;如果fd不等于客户端fd,代表是客户端fd有读写要求或者fd失去连接,使用handles()函数处理读写要求,如果handles函数的返回值<0,代表该连接已关闭,将该客户端fd从监听的epoll集合里清除。当连接数超过epoll集合的最大数目时,将主动关闭连接。

#include  <unistd.h>
#include  <sys/types.h>       /* basic system data types */
#include  <sys/socket.h>      /* basic socket definitions */
#include  <netinet/in.h>      /* sockaddr_in{} and other Internet defns */
#include  <arpa/inet.h>       /* inet(3) functions */
#include <sys/epoll.h> /* epoll function */
#include <fcntl.h>     /* nonblocking */
#include <sys/resource.h> /*setrlimit */

#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>



#define MAXEPOLLSIZE 10000
#define MAXLINE 10240
int handle(int connfd);
/*(1)调用open获得描述符,并指定O_NONBLOCK标志
(2)对已经打开的文件描述符,调用fcntl,打开O_NONBLOCK文件状态标志。

flags = fcntl( s, F_GETFL, 0 ) )
fcntl( s, F_SETFL, flags | O_NONBLOCK )   */
int setnonblocking(int sockfd)
{
    if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) {
        return -1;
    }
    return 0;
}

int main(int argc, char **argv)
{
    int  servPort = 6888;
    int listenq = 1024;

    int listenfd, connfd, kdpfd, nfds, n, nread, curfds,acceptCount = 0;
    struct sockaddr_in servaddr, cliaddr;
    socklen_t socklen = sizeof(struct sockaddr_in);
    struct epoll_event ev;
    struct epoll_event events[MAXEPOLLSIZE];
    struct rlimit rt;
    char buf[MAXLINE];

    /* 设置每个进程允许打开的最大文件数 */
    rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
/*设置资源的软硬限制 RLMIT_NOFILE 指定比进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误*/
    if (setrlimit(RLIMIT_NOFILE, &rt) == -1) 
    {
        perror("setrlimit error");
        return -1;
    }

    /*字符串部分置零*/
    bzero(&servaddr, sizeof(servaddr));
    /*ipv4*/
    servaddr.sin_family = AF_INET; 
    /*inaddr_any*/
    servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
    /*servport 6888*/
    servaddr.sin_port = htons (servPort);

    listenfd = socket(AF_INET, SOCK_STREAM, 0); 
    if (listenfd == -1) {
        perror("can't create socket file");
        return -1;
    }

    int opt = 1;
    /*设置调用close(socket)后,仍可继续重用该socket。调用close(socket)一般不会立即关闭socket,而经历TIME_WAIT的过程。
BOOL bReuseaddr = TRUE;
setsockopt( s, SOL_SOCKET, SO_REUSEADDR, ( const char* )&bReuseaddr, sizeof( BOOL ) );*/
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    /*设置sockfd非阻塞*/
    if (setnonblocking(listenfd) < 0) {
        perror("setnonblock error");
    }
    /*绑定*/
    if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) == -1) 
    {
        perror("bind error");
        return -1;
    } 
    /*监听*/
    if (listen(listenfd, listenq) == -1) 
    {
        perror("listen error");
        return -1;
    }
    /* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */
    kdpfd = epoll_create(MAXEPOLLSIZE);
    /*设置事件类型 EPPLLIN和EPOLLET2种,EPOLLIN       连接到达;有数据来临;
    EPOLLOUT      有数据要写*/
    ev.events = EPOLLIN | EPOLLET;
    /*监听socket加入epoll集合里*/
    ev.data.fd = listenfd;
    /*interface面向对象编程语言中接口操作的关键字,功能是把所需成员组合起来,用来装封一定功能的集合。它好比一个模板,在其中定义了对象必须实现的成员,通过类或结构来实现它。
    epoll_ctl - control interface for an epoll descriptor控制epoll描述符的集合
       EPOLL_CTL_ADD
              Register  the  target  file  descriptor fd on the epoll instance
              referred to by the file descriptor epfd and associate the  event
              event with the internal file linked to fd.
     */

    if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listenfd, &ev) < 0) 
    {
        fprintf(stderr, "epoll set insertion error: fd=%d\n", listenfd);
        return -1;
    }
    curfds = 1;

    printf("epollserver startup,port %d, max connection is %d, backlog is %d listenfd=%d\n", servPort, MAXEPOLLSIZE, listenq,listenfd);

    for (;;) {
        /* 等待有事件发生 */
        /* epoll_wait,  epoll_pwait  -  wait  for  an  I/O  event on an epoll file descriptor*/
        /*When successful, epoll_wait() returns the number  of  file  descriptors
       ready for the requested I/O, or zero if no file descriptor became ready
       during the requested  timeout  milliseconds.   When  an  error  occurs,
       epoll_wait() returns -1 and errno is set appropriately.*/
       /*-1 timeout  The  timeout  argument  specifies  the  number  of  milliseconds   that
       epoll_wait() will block.     为-1表示一直阻塞下去直至有I/O事件或信号 */
        /*Note  that  the timeout interval will be rounded up to the system clock
       granularity, and kernel scheduling delays mean that the blocking inter‐
       val  may  overrun by a small amount.timeout 依系统时钟间隔取整*/
        /*curfds表示epoll集合所能遇到的最大事件数*/
        nfds = epoll_wait(kdpfd, events, curfds, -1);
        if (nfds == -1)
        {
            perror("epoll_wait");
            continue;
        }
         if(nfds==0)
         {
        printf("songshiqisongshiqi\n");
          }
        /* 处理所有事件 */
        printf("nfds=%d\n",nfds);
        for (n = 0; n < nfds; ++n)
        {
             /*这步是在判断是不是listenfd连接事件啊*/
            printf("nfds=%d events.data.fd=%d\n",nfds,events[n].data.fd);
            if (events[n].data.fd == listenfd) 
            {
                connfd = accept(listenfd, (struct sockaddr *)&cliaddr,&socklen);
                if (connfd < 0) 
                {
                    perror("accept error");
                    continue;
                }

                sprintf(buf, "accept form %s:%d listenfd=%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port,listenfd);
                printf("%d:%s", ++acceptCount, buf);
                /*这里疑似有误  应为nfds nfds为需处理的i/o数,超过最大值 关闭多余*/
                /*若是nfds>max 那不是循环都在关?应该设置temp=curfds,关一次temp--,直到temp<=max才不关*/
                 /*我理解有误  下文代码加入epoll集合后才算正式可处理,curfds>max,不加入epoll集合,直接关闭*/
                if (curfds >= MAXEPOLLSIZE) {
                    fprintf(stderr, "too many connection, more than %d\n", MAXEPOLLSIZE);
                    close(connfd);
                    continue;
                } 
                if (setnonblocking(connfd) < 0) {
                    perror("setnonblocking error");
                }
                /*设置事件类型    EPOLLIN 连接到达;有数据来临;EPOLLET 边缘触发模式    */
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = connfd;
                if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, connfd, &ev) < 0)
                {
                    fprintf(stderr, "add socket '%d' to epoll failed: %s\n", connfd, strerror(errno));
                    return -1;
                }
                curfds++;
                continue;
            } 
            // 处理客户端请求
              /*handle函数判断连接 若连接 则客户端所发内容原路返回发送 未连接则返回 */
            if (handle(events[n].data.fd) < 0) {
                  /*从epoll集合移除*/
                epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,&ev);
                curfds--;
            }
        }
    }
    close(listenfd);
    return 0;
}
/*可加入http */
int handle(int connfd) {
    int nread;
    char buf[MAXLINE];
    nread = read(connfd, buf, MAXLINE);//读取客户端socket流

    if (nread == 0) {
        printf("client close the connection\n");
        close(connfd);/*断了之后有事件,handle<0而移除出集合*/
        return -1;
    } 
    if (nread < 0) {
        perror("read error");
        close(connfd);
        return -1;
    }    
    write(connfd, buf, nread);//响应客户端  
    return 0;
}

线程池

线程池与epoll的结合使用,只能说是锦上添花,epoll已基本解决了C10K问题,只是面临高并发时,线程池与epoll结合使用,可更好的解决高并发。
传统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。
我们将传统方案中的线程执行过程分为三个过程:T1、T2、T3。

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <pthread.h>  
#include <assert.h>  
/* 
*线程池里所有运行和等待的任务都是一个CThread_worker 
*由于所有任务都在链表里,所以是一个链表结构 
*/  
typedef struct worker  
{  
    /*回调函数,任务运行时会调用此函数,注意也可声明成其它形式*/  
    void *(*process) (void *arg);  
    void *arg;/*回调函数的参数*/  
    struct worker *next;  
} CThread_worker;  
/*线程池结构*/  
typedef struct  
{  
    pthread_mutex_t queue_lock;  
    pthread_cond_t queue_ready;  
    /*链表结构,线程池中所有等待任务*/  
    CThread_worker *queue_head;  
    /*是否销毁线程池*/  
    int shutdown;  
    pthread_t *threadid;  
    /*线程池中允许的活动线程数目*/  
    int max_thread_num;  
    /*当前等待队列的任务数目*/  
    int cur_queue_size;  
} CThread_pool;  
int pool_add_worker (void *(*process) (void *arg), void *arg);  
void *thread_routine (void *arg);  
//share resource  
static CThread_pool *pool = NULL;  
void  
pool_init (int max_thread_num)  
{  
    pool = (CThread_pool *) malloc (sizeof (CThread_pool));  
    pthread_mutex_init (&(pool->queue_lock), NULL);  
    pthread_cond_init (&(pool->queue_ready), NULL);  
    pool->queue_head = NULL;  
    pool->max_thread_num = max_thread_num;  
    pool->cur_queue_size = 0;  
    pool->shutdown = 0;  
    pool->threadid = (pthread_t *) malloc (max_thread_num * sizeof (pthread_t));  
    int i = 0;  
    for (i = 0; i < max_thread_num; i++)  
    {   
        pthread_create (&(pool->threadid[i]), NULL, thread_routine,NULL);  
    }  
}   
/*向线程池中加入任务*/  
int  pool_add_worker (void *(*process) (void *arg), void *arg)  
{  
    /*构造一个新任务*/  
    CThread_worker *newworker = (CThread_worker *) malloc (sizeof (CThread_worker));  
    newworker->process = process;  
    newworker->arg = arg;  
    newworker->next = NULL;/*别忘置空*/  
    pthread_mutex_lock (&(pool->queue_lock));  
    /*将任务加入到等待队列中*/  
    CThread_worker *member = pool->queue_head;  
    if (member != NULL)  
    {  
        while (member->next != NULL)  
            member = member->next;  
        member->next = newworker;  
    }  
    else  
    {  
        pool->queue_head = newworker;  
    } 
    assert (pool->queue_head != NULL);  
    pool->cur_queue_size++;  
    pthread_mutex_unlock (&(pool->queue_lock));  
    /*好了,等待队列中有任务了,唤醒一个等待线程; 
    注意如果所有线程都在忙碌,这句没有任何作用*/  
    pthread_cond_signal (&(pool->queue_ready));  
    return 0;  
}  
/*销毁线程池,等待队列中的任务不会再被执行,但是正在运行的线程会一直 
把任务运行完后再退出*/  
int  pool_destroy ()  
{  
    if (pool->shutdown)  
        return -1;/*防止两次调用*/  
    pool->shutdown = 1; 
    /*唤醒所有等待线程,线程池要销毁了*/  
    pthread_cond_broadcast (&(pool->queue_ready));  
    /*阻塞等待线程退出,否则就成僵尸了*/  
    int i;  
    for (i = 0; i < pool->max_thread_num; i++)  
        pthread_join (pool->threadid[i], NULL);  
    free (pool->threadid);    
    /*销毁等待队列*/  
    CThread_worker *head = NULL;  
    while (pool->queue_head != NULL)  
    {  
        head = pool->queue_head;  
        pool->queue_head = pool->queue_head->next;  
        free (head);  
    }  
    /*条件变量和互斥量也别忘了销毁*/  
    pthread_mutex_destroy(&(pool->queue_lock));  
    pthread_cond_destroy(&(pool->queue_ready));  
      
    free (pool);  
    /*销毁后指针置空是个好习惯*/  
    pool=NULL;  
    return 0;  
}  
void *  thread_routine (void *arg)  
{  
    printf ("starting thread 0x%x\n", pthread_self ());  
    while (1)  
    {  
        pthread_mutex_lock (&(pool->queue_lock));  
        /*如果等待队列为0并且不销毁线程池,则处于阻塞状态; 注意 
        pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/  
        while (pool->cur_queue_size == 0 && !pool->shutdown)  
        {  
            printf ("thread 0x%x is waiting\n", pthread_self ());  
            pthread_cond_wait (&(pool->queue_ready), &(pool->queue_lock));  
        }  
        /*线程池要销毁了*/  
        if (pool->shutdown)  
        {  
            /*遇到break,continue,return等跳转语句,千万不要忘记先解锁*/  
            pthread_mutex_unlock (&(pool->queue_lock));  
            printf ("thread 0x%x will exit\n", pthread_self ());  
            pthread_exit (NULL);  
        }  
        printf ("thread 0x%x is starting to work\n", pthread_self ());  
        /*assert是调试的好帮手*/  
        assert (pool->cur_queue_size != 0);  
        assert (pool->queue_head != NULL); 
        /*等待队列长度减去1,并取出链表中的头元素*/  
        pool->cur_queue_size--;  
        CThread_worker *worker = pool->queue_head;  
        pool->queue_head = worker->next;  
        pthread_mutex_unlock (&(pool->queue_lock));  
        /*调用回调函数,执行任务*/  
        (*(worker->process)) (worker->arg);  
        free (worker);  
        worker = NULL;  
    }  
    /*这一句应该是不可达的*/  
    pthread_exit (NULL);  
}  
//    下面是测试代码 
void *  myprocess (void *arg)  
{  
    printf ("threadid is 0x%x, working on task %d\n", pthread_self (),*(int *) arg);  
    sleep (1);/*休息一秒,延长任务的执行时间*/  
    return NULL;  
} 
int  
main (int argc, char **argv)  
{  
    pool_init (3);/*线程池中最多三个活动线程*/  
    /*连续向池中投入10个任务*/  
    int *workingnum = (int *) malloc (sizeof (int) * 10);  
    int i;  
    for (i = 0; i < 10; i++)  
    {  
        workingnum[i] = i;  
        pool_add_worker (myprocess, &workingnum[i]);  
    }  
    /*等待所有任务完成*/  
    sleep (5);  
    /*销毁线程池*/  
    pool_destroy ();  
    free (workingnum);  
    return 0;  
}  

HTTP

HTTP协议的实现基于TCP/IP连接,实现高并发(epoll+线程池)的TCP/IP连接与实现http没有太大关系,层次和纬度不同。
这是http协议的简单实现,只实现了get请求和post请求。
服务器采用fork出的双进程。父进程负责和客户端的读写,子进程负责数据处理。父子进程间通过管道通信。其实这里可以用共享内存。采用信号量来管理父子进程。
建议着重看看httpclient.c的代码,对post请求会有很好的理解。
tinyhttpserver.c


#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <time.h>
#include <stdlib.h>

 #include <sys/sem.h>  
#include <semaphore.h> 

    union semun  
    {  
        int val;  
        struct semid_ds *buf;  
        unsigned short *arry;  
    };  
      
static int sem_id = 0;
static int sem_id1 = 0;  
      
static int set_semvalue(int,int);  
static void del_semvalue(int);  
static int semaphore_p(int);  
static int semaphore_v(int);  
//pthread_mutex_t mutex;

//sem_t father_sem;  //整型
//sem_t son_sem;

#define ISspace(x) isspace((int)(x))

#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
 clock_t start,end; 

void accept_request(int);
void bad_request(int);
void cat(int, FILE *);
void cannot_execute(int);
void error_die(const char *);
void execute_cgi(int, const char *, const char *, const char *);
int get_line(int, char *, int);
void headers(int/*, const char **/);
void not_found(int);
void serve_file(int, const char *);
int startup(u_short *);
void unimplemented(int);

/**********************************************************************/
/* A request has caused a call to accept() on the server port to
 * return.  Process the request appropriately.
 * Parameters: the socket connected to the client */
/**********************************************************************/
void accept_request(int client)
{
    char buf[1024];
    int numchars;
    char method[255];
    char url[255];
    char path[512];
    size_t i, j;
    struct stat st;
    int cgi = 0;      /* becomes true if server decides this is a CGI program */
    char *query_string = NULL;

    /*得到请求的第一行*/
    /*GET / HTTP/1.1\n*/
    numchars = get_line(client, buf, sizeof(buf));
    i = 0; j = 0;
    /*把客户端的请求方法存到 method 数组*/
   /*用空格终止*/
   /*strcasecmp忽略大小写比较字符串*/
    while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
    {
        method[i] = buf[j];
        i++; j++;
    }
    method[i] = '\0';

    /*如果既不是 GET 又不是 POST 则无法处理 */
    if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
    {
        unimplemented(client);
        return;
    }

    /* POST 的时候开启 cgi */
    if (strcasecmp(method, "POST") == 0)
    {
    printf("识别为post\n");
        cgi = 1;
    }
    /*读取 url 地址*/
    i = 0;
    while (ISspace(buf[j]) && (j < sizeof(buf)))
        j++;
    /*仍然用空格终止*/
    while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
    {
        /*存下 url */
        url[i] = buf[j];
        i++; j++;
    }
    url[i] = '\0';

    /*处理 GET 方法*/
    if (strcasecmp(method, "GET") == 0)
    {
        /* 待处理请求为 url */
        query_string = url;
        while ((*query_string != '?') && (*query_string != '\0'))
            query_string++;
        /* GET 方法特点,? 后面为参数*/
        if (*query_string == '?')
        {
            /*开启 cgi */
            cgi = 1;
            *query_string = '\0';
            query_string++;
        }
    }

    /*格式化 url 到 path 数组,html 文件都在 htdocs 中*/
    sprintf(path, "htdocs%s", url);
    /*默认情况为 index.html */
    if (path[strlen(path) - 1] == '/')
        strcat(path, "index.html");
    /*根据路径找到对应文件 */
    if (stat(path, &st) == -1) {
        /*把所有 headers 的信息都丢弃*/
    printf("路径没找到文件\n");
        while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
            numchars = get_line(client, buf, sizeof(buf));
        /*回应客户端找不到*/
        not_found(client);
    }
    else
    {
    printf("路径找到文件\n");
    printf("path:%s\n",path);
        /*如果是个目录,则默认使用该目录下 index.html 文件*/
        if ((st.st_mode & S_IFMT) == S_IFDIR)
       {
    //  printf("6666\n");
            strcat(path, "/index.html");
       }      
    /*文件权限:分别是所有者可执行、群组可执行、其他可执行*/
    if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH)    )
          cgi = 1;
      /*不是 cgi,直接把服务器文件返回,否则执行 cgi */
      if (!cgi)
    {
      printf("cgi=0,进入serve_file\n");
      printf("path:%s\n",path);
          serve_file(client, path);
    }      
    else
    {
      printf("cgi=1,进入execute_cgi\n");
          execute_cgi(client, path, method, query_string);
    }   
    }

    /*断开与客户端的连接(HTTP 特点:无连接)*/
    close(client);
}

/**********************************************************************/
/* Inform the client that a request it has made has a problem.
 * Parameters: client socket */
/**********************************************************************/
void bad_request(int client)
{
    char buf[1024];

    /*回应客户端错误的 HTTP 请求 */
    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);
}

/**********************************************************************/
/* Put the entire contents of a file out on a socket.  This function
 * is named after the UNIX "cat" command, because it might have been
 * easier just to do something like pipe, fork, and exec("cat").
 * Parameters: the client socket descriptor
 *             FILE pointer for the file to cat */
/**********************************************************************/
void cat(int client, FILE *resource)
{
    char buf[1024];
    printf("7777\n");
    /*读取文件中的所有数据写到 socket */
    fgets(buf, sizeof(buf), resource);
    while (!feof(resource))
    {
        send(client, buf, strlen(buf), 0);
        fgets(buf, sizeof(buf), resource);
    }
        printf("cat读取完毕\n");
}

/**********************************************************************/
/* Inform the client that a CGI script could not be executed.
 * Parameter: the client socket descriptor. */
/**********************************************************************/
void cannot_execute(int client)
{
    char buf[1024];

    /* 回应客户端 cgi 无法执行*/
    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);
}

/**********************************************************************/
/* Print out an error message with perror() (for system errors; based
 * on value of errno, which indicates system call errors) and exit the
 * program indicating an error. */
/**********************************************************************/
void error_die(const char *sc)
{
    /*出错信息处理 */
    perror(sc);
    exit(1);
}

/**********************************************************************/
/* Execute a CGI script.  Will need to set environment variables as
 * appropriate.
 * Parameters: client socket descriptor
 *             path to the CGI script */
/**********************************************************************/
void execute_cgi(int client, const char *path,
                 const char *method, const char *query_string)
{
 printf("进入execute_cgi\n");
 char buf[1024];
 int cgi_output[2];
 int cgi_input[2];
 pid_t pid;
 int status;
 int i;
 char c;
 int numchars = 1;
 int content_length = -1;

 buf[0] = 'A'; buf[1] = '\0';
 if (strcasecmp(method, "GET") == 0)
  while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
   numchars = get_line(client, buf, sizeof(buf));
 else    /* POST */
 {
  numchars = get_line(client, buf, sizeof(buf));
  while ((numchars > 0) && strcmp("\n", buf))
  {
   buf[15] = '\0';/*切割方便比较*/
   if (strcasecmp(buf, "Content-Length:") == 0)
   printf("buf[16]=%c\n",buf[16]);
   /*atoi函数为字符串转整型,这里取buf[16]的地址,为字符串转整型准备。*/
    content_length = atoi(&(buf[16]));
   numchars = get_line(client, buf, sizeof(buf));
  }
  if (content_length == -1) {
   printf("bad_request\n");
   bad_request(client);
   return;
  }
 }
 printf("测试bad_request  content_length=%d\n",content_length);
 sprintf(buf, "HTTP/1.0 200 OK\r\n");
 send(client, buf, strlen(buf), 0);

 if (pipe(cgi_output) < 0) {
  cannot_execute(client);
  return;
 }
 if (pipe(cgi_input) < 0) {
  cannot_execute(client);
  return;
 }
   sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);  
        
   sem_id1 = semget((key_t)1235, 1, 0666 | IPC_CREAT);
   if(!set_semvalue(sem_id,1))  
        {  
                fprintf(stderr, "Failed to initialize semaphore\n");  
                exit(EXIT_FAILURE);  
        } 
 if(!set_semvalue(sem_id1,0))  
        {  
                fprintf(stderr, "Failed to initialize semaphore\n");  
                exit(EXIT_FAILURE);  
        }  
        

 if ( (pid = fork()) < 0 ) {
  cannot_execute(client);
  return;
 }
 if (pid == 0)  /* child: CGI script */
 {
   if(!semaphore_p(sem_id))  
    exit(EXIT_FAILURE);  
//  sem_wait(&son_sem);
// pthread_mutex_lock(&mutex);
  printf("这是子进程,第一原子操作开始\n");
  char meth_env[255];
  char query_env[255];
  char length_env[255];

  dup2(cgi_output[1], 1);
  dup2(cgi_input[0], 0);
  close(cgi_output[0]);
  close(cgi_input[1]);
  sprintf(meth_env, "REQUEST_METHOD=%s", method);
  putenv(meth_env);
  if (strcasecmp(method, "GET") == 0) {
   sprintf(query_env, "QUERY_STRING=%s", query_string);
   putenv(query_env);
  }
  else {   /* POST */
   sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
   putenv(length_env);
  }
  //printf("第一原子操作完成\n");
  if(!semaphore_v(sem_id1))  
   exit(EXIT_FAILURE);
  
//  pthread_mutex_unlock(&mutex);
//  sem_post(&father_sem);
            //将信号量的值加一

//  sem_wait(&son_sem);
// pthread_mutex_lock(&mutex);
   if(!semaphore_p(sem_id))  
    exit(EXIT_FAILURE); 
    
   printf("服务器回应:\n");
//   while (read(cgi_input[0], &c, 1) > 0)

  char dd[30]={'\0'};
     //gets(dd);
   read(cgi_input[0], dd, sizeof(dd));
   read(stdin,&dd,sizeof(dd));
      printf("dd=%s\n",dd);
  execl(path, path, NULL);
  if(!semaphore_v(sem_id1))  
    exit(EXIT_FAILURE);  
//  pthread_mutex_unlock(&mutex);
  exit(0);
 } else {
 printf("这是父进程,信号量初始化\n");
 
     /* parent */
// printf("这是父进程,son_sem=%d,father_sem=%d\n",&son_sem,&father_sem);
//   sem_wait(&father_sem);
// pthread_mutex_lock(&mutex);
  if(!semaphore_p(sem_id1))  
    exit(EXIT_FAILURE); 
   printf("这是父进程 第二原子操作开始\n");
  close(cgi_output[1]);
  close(cgi_input[0]);
  if (strcasecmp(method, "POST") == 0)
   for (i = 0; i < content_length; i++) {
    recv(client, &c, 1, 0);
    printf("%c",c);/*不打\n换行符,无法显示?*/
    write(cgi_input[1], &c, 1);
   }
 
   printf("recv完毕 第二原子操作结束\n");
   if(!semaphore_v(sem_id))  
   exit(EXIT_FAILURE);
  if(!semaphore_p(sem_id1))  
    exit(EXIT_FAILURE);  
   printf("父进程 第四原子操作开始\n");
  // pthread_mutex_unlock(&mutex);
            //将信号量的值加一
// sem_post(&son_sem);
// printf("son_sem+1,son_sem=%d\n",son_sem);
// sem_wait(&father_sem);
     printf("send:\n");
  char d[40]={'\0'};
  int di=0;
  while (read(cgi_output[0], &c, 1) > 0)
   {
 
    printf("%c",c);
    d[di]=c;
    di++;
   }
   d[++di]='\0';
     send(client, d, sizeof(d), 0);
  printf("send完毕\n");
  close(cgi_output[0]);
  close(cgi_input[1]);
  printf("父进程 第四原子操作结束\n");
  del_semvalue(sem_id);
  del_semvalue(sem_id1);
  waitpid(pid, &status, 0);
 }
}

/**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,
 * carriage return, or a CRLF combination.  Terminates the string read
 * with a null character.  If no newline indicator is found before the
 * end of the buffer, the string is terminated with a null.  If any of
 * the above three line terminators is read, the last character of the
 * string will be a linefeed and the string will be terminated with a
 * null character.
 * Parameters: the socket descriptor
 *             the buffer to save the data in
 *             the size of the buffer
 * Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
int get_line(int sock, char *buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;

    /*把终止条件统一为 \n 换行符,标准化 buf 数组*/
    while ((i < size - 1) && (c != '\n'))
    {
        /*一次仅接收一个字节*/
        n = recv(sock, &c, 1, 0);
        /* DEBUG printf("%02X\n", c); */
        if (n > 0)
        {
        /*上文已经将终止条件设定为\n,接下来考虑的是:如果是以\r\n为结束位和\r为结束位,该如何将其改为\n为结束位*/
        /*对于\r为结束位,直接改为\n结束位*/
        /*对于\r\n为结束位,将\r\n改为\n,作为结束位*/
            /*收到 \r 则继续接收下个字节,因为换行符可能是 \r\n */
            if (c == '\r')
            {
        /*MSG_PEEK撇一眼*/
                /*使用 MSG_PEEK 标志使下一次读取依然可以得到这次读取的内容,可认为接收窗口不滑动*/
        /*撇一眼判断是不是\r\n*/
                n = recv(sock, &c, 1, MSG_PEEK);
                /* DEBUG printf("%02X\n", c); */
                /*但如果是换行符则把它吸收掉*/
                if ((n > 0) && (c == '\n'))
                    recv(sock, &c, 1, 0);
                else
                    c = '\n';
            }
            /*存到缓冲区*/
            buf[i] = c;
            i++;
        }
        else
            c = '\n';
    }
    buf[i] = '\0';

    /*返回 buf 数组大小*/
    return(i);
}

/**********************************************************************/
/* Return the informational HTTP headers about a file. */
/* Parameters: the socket to print the headers on
 *             the name of the file */
/**********************************************************************/
void headers(int client/*, const char *filename*/)
{
 char buf[1024];
 int ret=0;
// (void)filename;  /* could use filename to determine file type */
 printf("headers开始\n");
 strcpy(buf, "HTTP/1.0 200 OK\r\n");
 ret=send(client, buf, strlen(buf), 0);
 if(ret==-1)
 {
    printf("songshiqi\n");
    perror("1send");
   
 }
 strcpy(buf, SERVER_STRING);
 ret=send(client, buf, strlen(buf), 0);
 if(ret==-1)
 {
    printf("songshiqi\n");
    perror("2send");
   
 }
 printf("headers中场\n");
 sprintf(buf, "Content-Type: text/html\r\n");
 ret=send(client, buf, strlen(buf), 0);
 if(ret==-1)
 {
    perror("3send");
   
 }
 strcpy(buf, "\r\n");
 ret=send(client, buf, strlen(buf), 0);
 if(ret==-1)
 {
    perror("4send");
   
 }
 printf("headers结束\n");
}

/**********************************************************************/
/* Give a client a 404 not found status message. */
/**********************************************************************/
void not_found(int client)
{
    char buf[1024];

    /* 404 页面 */
    sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
    send(client, buf, strlen(buf), 0);
    /*服务器信息*/
    sprintf(buf, SERVER_STRING);
    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, "<HTML><TITLE>Not Found</TITLE>\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "your request because the resource specified\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "is unavailable or nonexistent.\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</BODY></HTML>\r\n");
    send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* Send a regular file to the client.  Use headers, and report
 * errors to client if they occur.
 * Parameters: a pointer to a file structure produced from the socket
 *              file descriptor
 *             the name of the file to serve */
/**********************************************************************/
void serve_file(int client, const char *filename)
{
 FILE *resource = NULL;
 int numchars = 1;
 char buf[1024];

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

 resource = fopen(filename, "r");
 if (resource == NULL)
  not_found(client);
 else
 {
  printf("sesource不为0,即将执行headers和cat\n");
  headers(client/*, filename*/);
  cat(client, resource);
 }
 //sleep(1);
 fclose(resource);
 printf("线程已执行完毕\n");
 end = clock();  
 printf("songshiqi\n");
printf("time=%f\n",(double)(end-start)/1000);  
}

/**********************************************************************/
/* This function starts the process of listening for web connections
 * on a specified port.  If the port is 0, then dynamically allocate a
 * port and modify the original port variable to reflect the actual
 * port.
 * Parameters: pointer to variable containing the port to connect on
 * Returns: the socket */
/**********************************************************************/
int startup(u_short *port)
{
 int httpd = 0;
 struct sockaddr_in name;

 httpd = socket(PF_INET, SOCK_STREAM, 0);/*创建一个TCP套接字*/
 if (httpd == -1)
  error_die("socket");
 memset(&name, 0, sizeof(name));
 name.sin_family = AF_INET;
 name.sin_port = htons(11004);/*若端口为0,则系统随机选择一个未被使用的端口号,这里改为指定一个空闲的端口号*/
 name.sin_addr.s_addr = htonl(INADDR_ANY);/*系统自动填入本机IP*/
 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)/*socket绑定到制定的IP地址和端口号*/
  error_die("bind");
 if (*port == 0)  /* if dynamically allocating a port */
 {
  int namelen = sizeof(name);
  /*获取主机名,从而获取分配的端口号*/
  if (getsockname(httpd, (struct sockaddr *)&name, (socklen_t *)&namelen) == -1)
   error_die("getsockname");
  *port = ntohs(name.sin_port);
 }
 if (listen(httpd, 5) < 0)/*监听连接请求。5--backlog:系统维护的一个跟踪握手未完成的连接的队列的大小*/
  error_die("listen");
 return(httpd);
}

/**********************************************************************/
/* Inform the client that the requested web method has not been
 * implemented.
 * Parameter: the client socket */
/**********************************************************************/
void unimplemented(int client)
{
    char buf[1024];

    /* HTTP method 不被支持*/
    sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
    send(client, buf, strlen(buf), 0);
    /*服务器信息*/
    sprintf(buf, SERVER_STRING);
    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, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</TITLE></HEAD>\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</BODY></HTML>\r\n");
    send(client, buf, strlen(buf), 0);
}

/**********************************************************************/


int main(void)
{
//  pthread_mutex_init(&mutex, NULL);

    


    //初始化信号量
//  sem_init(&son_sem, 1, 0);  //将son_sem值置为1
//  sem_init(&father_sem, 1, 0);  //将father_sem值置为0
    //sem_post(&father_sem);
    int server_sock = -1;
    u_short port = 0;
    int client_sock = -1;
    struct sockaddr_in client_name;
    int client_name_len = sizeof(client_name);
    pthread_t newthread;
    


    /*在对应端口建立 httpd 服务*/
    server_sock = startup(&port);
    printf("httpd running on port %d\n", port);
    int songshiqi=1;
    while (1)
    {
        /*套接字收到客户端连接请求*/
        client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);
    printf("there have a client,num %d\n",songshiqi);
    start = clock();  
    songshiqi++; 
        if (client_sock == -1)
            error_die("accept");
        /*派生新线程用 accept_request 函数处理新请求*/
        /* accept_request(client_sock); */
        if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
            perror("pthread_create");
    //    sleep(1);
    //    exit(1);
    }

    close(server_sock);
    
    return(0);
}

    static int set_semvalue(int sem_id,int i)  
    {  
        //用于初始化信号量,在使用信号量前必须这样做  
        union semun sem_union;  
      
        sem_union.val = i;  
        if(semctl(sem_id, 0, SETVAL, sem_union) == -1)  
            return 0;  
        return 1;  
    }  
      
    static void del_semvalue(int sem_id)  
    {  
        //删除信号量  
        union semun sem_union;  
      
        if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)  
            fprintf(stderr, "Failed to delete semaphore\n");  
    }  
      
    static int semaphore_p(int sem_id)  
    {  
        //对信号量做减1操作,即等待P(sv)  
        struct sembuf sem_b;  
        sem_b.sem_num = 0;  
        sem_b.sem_op = -1;//P()  
        sem_b.sem_flg = SEM_UNDO;  
        if(semop(sem_id, &sem_b, 1) == -1)  
        {  
            fprintf(stderr, "semaphore_p failed\n");  
            return 0;  
        }  
        return 1;  
    }  
      
    static int semaphore_v(int sem_id)  
    {  
        //这是一个释放操作,它使信号量变为可用,即发送信号V(sv)  
        struct sembuf sem_b;  
        sem_b.sem_num = 0;  
        sem_b.sem_op = 1;//V()  
        sem_b.sem_flg = SEM_UNDO;  
        if(semop(sem_id, &sem_b, 1) == -1)  
        {  
            fprintf(stderr, "semaphore_v failed\n");  
            return 0;  
        }  
        return 1;  
    }

tinyhttpclient.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
/*服务器发送了3个包,客户端接收一个包,但量一样,保证量。*/
/*当前读取,当前读取,可能会漏读很多*/
/*关注recv阻塞*/
 clock_t start,end; 
int main(int argc, char *argv[])
{
 int sockfd;
 int len;
 struct sockaddr_in address;
 int result;
 char buf[] = "POST / HTTP/1.1\nContent-Length:119\n\nsongshiqixieaoxieao\n\n";
 char get[1024] = {0};
 sockfd = socket(AF_INET, SOCK_STREAM, 0);
 address.sin_family = AF_INET;
 address.sin_addr.s_addr = inet_addr("127.0.0.1");/*本机IP地址*/
 address.sin_port = htons(11004);/*tinyhttpd的端口*/
 len = sizeof(address);
 result = connect(sockfd, (struct sockaddr *)&address, len);
 
 if (result == -1)
 {
  perror("oops:client:");
  _exit(1);
 }
 start = clock(); 
 write(sockfd,buf, sizeof(buf));/*发送请求*/
// time_t start,end;  
// start =time(NULL);//or time(&start);  
 int ret=1;
 int myalready=0;
 int songshiqi=0;
 sleep(2);
//ret=read(sockfd,get+myalready, sizeof(get));/*接收返回数据*/
//songshiqi++;
//        myalready=+ret;
 while(/*myalready<240*/1)
 {
    ret=read(sockfd,get+myalready, sizeof(get));/*接收返回数据*/
    if(ret==-1)
    { 
        perror("ret");
        break;
    }
    //sleep(1);
        songshiqi++;
        myalready=+ret;
    printf("songshiqi=%d  ret=%d myalready=%d\n",songshiqi,ret,myalready);

 }
 end =clock();  
 printf("time=%lf\n",(double)(end-start)/1000);  
 /*打印返回数据*/
 printf("-----------------------------show buffer -----------------------------\n");
 printf("%s",get);
 printf("----------------------------------------------------------------------\n");
 close(sockfd);
 _exit(0);
}

上一篇 下一篇

猜你喜欢

热点阅读