Linux C简单的web服务器

 

目录

Linux C简单的web服务器

一、基础类型重命名

二、包裹函数(wrap.h/wrap.c 主要是网络通讯和多线程的包裹函数)

三、服务端程序(web_server.h/web_server.c)—— 使用EPOLL高并发机制

四、HTTP解析(http.h/http.c写了一个基本的框架,很容易添加需要解析的文件类型)

五、项目目录组织(以项目的角度建立工程)

六、编译执行


HTTP基本协议

HTTP基本协议参照上面链接。我这里只介绍软件的设计过程和源码。

一、基础类型重命名

#ifndef _TYPE_H_
#define _TYPE_H_

/* exact-width signed integer types */
typedef   signed           char int8_t;
typedef   signed short     int int16_t;
typedef   signed           int int32_t;

/* exact-width unsigned integer types */
typedef unsigned           char uint8_t;
typedef unsigned short     int uint16_t;
typedef unsigned           int uint32_t;
//typedef unsigned           int size_t;

#ifndef NULL
#ifdef __cplusplus              // EC++
#define NULL   0
#else
#define NULL   ((void *) 0)
#endif
#endif

#ifndef boolean
typedef uint8_t boolean;
#endif

#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE  1
#endif

#endif /* _TYPE_H_ */

二、包裹函数(wrap.h/wrap.c 主要是网络通讯和多线程的包裹函数)

#ifndef WRAP_H
#define WRAP_H

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <pthread.h>

//===================================Socket/File Wrapper Function==========================================
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
void Bind(int fd, const struct sockaddr* sa, socklen_t salen);
void Connect(int fd, const struct sockaddr* sa, socklen_t salen);
void Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
void Setsockopt(int fd, int level, int optname, void *optval, socklen_t optlen);
int Ioctl(int d, int request, ...);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void*vptr, size_t n);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int Open(const char *pathname, int flags, mode_t mode);
void Close(int fd);

//========================================Epoll Wrapper function============================================
int Epollcreate(int size);
int Epollctl(int epfd, int op, int fd, struct epoll_event *event);
int Epollwait(int epfd, struct epoll_event * events, int maxevents, int timeout);

//======================================pthread Wrapper function============================================
void Pthread_create(pthread_t *tid, const pthread_attr_t *attr, void * (*func)(void *), void *arg);
void Pthread_detach(pthread_t tid);
void Pthread_join(pthread_t tid, void **status);
void Pthread_mutex_lock(pthread_mutex_t *mptr);
void Pthread_mutex_unlock(pthread_mutex_t *mptr);
void Pthread_cond_signal(pthread_cond_t *cptr);
void Pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);

#endif
#include "wrap.h"

static void perr_exit(const char *s)
{
  perror(s);
  exit(1);
}

int Accept(int fd, struct sockaddr *sa, socklen_t* salenptr)
{
  int newfd;
again:
  if((newfd = accept(fd, sa, salenptr)) < 0)
  {
    if((errno == ECONNABORTED) || (errno == EINTR))
    {
      goto again;
    }
    else
    {
      perr_exit("accept error");
    }
  }

  return newfd;
}

void Bind(int fd, const struct sockaddr* sa, socklen_t salen)
{
  if(bind(fd, sa, salen) < 0)
  {
    perr_exit("bind error");
  }
}

void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
  if(connect(fd, sa, salen) < 0)
  {
    perr_exit("connect error");
  }
}

void Listen(int fd, int backlog)
{
  if(listen(fd, backlog) < 0)
  {
    perr_exit("listen error");
  }
}

int Socket(int family, int type, int protocol)
{
  int socketfd;
  if((socketfd = socket(family, type, protocol)) < 0)
  {
    perr_exit("socket error");
  }

  return socketfd;
}

void Setsockopt(int fd, int level, int optname, void *optval, socklen_t optlen)
{
  if(-1 == setsockopt(fd, level, optname, optval, optlen))
  {
    perr_exit("setsockopt error");
  }
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
  ssize_t n;
again:
  if((n = read(fd, ptr, nbytes)) == -1)
  {
    if(errno == EINTR)
    {
      goto again;
    }
    else
    {
      return -1;
    }
  }
  return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
  ssize_t n;
again:
  if((n = write(fd, ptr, nbytes)) == -1)
  {
    if(errno == EINTR)
    {
      goto again;
    }
    else
    {
      return -1;
    }
  }

  return n;
}

ssize_t Readn(int fd, void *vptr, size_t n)
{
  size_t nleft;
  ssize_t nread;
  char *ptr;

  ptr = vptr;
  nleft = n;
  while(nleft > 0)
  {
    if((nread = read(fd, ptr, nleft)) < 0)
    {
      if(errno == EINTR)
      {
        nread = 0;
      }
      else
      {
        return -1;
      }
    }
    else if(nread == 0)
    {
      break;
    }

    nleft -= nread;
    ptr += nread;
  }

  return (n - nleft);
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
  size_t nleft;
  ssize_t nwritten;
  const char* ptr;

  ptr = vptr;
  nleft = n;
  while(nleft > 0)
  {
    if((nwritten = write(fd, ptr, nleft)) <= 0)
    {
      if(nwritten < 0 && errno == EINTR)
      {
        nwritten = 0;
      }
      else
      {
        return -1;
      }
    }

    nleft -= nwritten;
    ptr += nwritten;
  }

  return n;
}

static ssize_t my_read(int fd, char *ptr)
{
  static int read_cnt;
  static char *read_ptr;
  static char read_buf[100];

  if(read_cnt <= 0)
  {
again:
    if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0)
    {
      if(errno == EINTR)
      {
        goto again;
      }
      else
      {
        return -1;
      }
    }
    else if(read_cnt == 0)
    {
      return 0;
    }
    read_ptr = read_buf;
  }

  read_cnt--;
  *ptr = *read_ptr++;
  return 1;
}

ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
  ssize_t n, rc;
  char c, *ptr;

  ptr = vptr;

  for(n = 1; n < maxlen; n++)
  {
    if((rc = my_read(fd, &c)) == 1)
    {
      *ptr++ = c;
      if(c == '\n')
      {
        break;
      }
    }
    else if(rc == 0)
    {
      *ptr = 0;
      return n - 1;
    }
    else
    {
      return (n - 1);
    }
  }

  *ptr = 0;
  return n;
}

int Open(const char *pathname, int flags, mode_t mode)
{
  int fd = open(pathname, flags, mode);
  if( -1 == fd )
  {
    perr_exit("open file error...");
  }
  return fd;
}

void Close(int fd)
{
  if(close(fd) == -1)
  {
    perr_exit("close error...");
  }
}

int Epollcreate(int size)
{
  int sockfd;

  if((sockfd = epoll_create(size)) == -1)
  {
    perr_exit("epoll create error...");
  }

  return sockfd;
}

int Epollctl(int epfd, int op, int fd, struct epoll_event *event)
{
  int status;

  if((status = epoll_ctl(epfd, op, fd, event)) == -1)
  {
    perr_exit("epoll ctl error...");
  }

  return status;
}

int Epollwait(int epfd, struct epoll_event * events, int maxevents, int timeout)
{
  int ret;

  if((ret = epoll_wait(epfd, events, maxevents, timeout)) == -1)
  {
    perr_exit("epoll wait error...");
  }

  return ret;
}

void Pthread_create(pthread_t *tid, const pthread_attr_t *attr,
                    void * (*func)(void *), void *arg)
{
  int n = pthread_create(tid, attr, func, arg);
  if ( n == 0)
  {
    return;
  }
  errno = n;
  perr_exit("pthread_create error");
}

void Pthread_detach(pthread_t tid)
{
  int n = pthread_detach(tid);
  if ( n == 0)
  {
    return;
  }
  errno = n;
  perr_exit("pthread_detach error");
}

void Pthread_join(pthread_t tid, void **status)
{
  int n = pthread_join(tid, status);
  if ( n == 0 )
  {
    return;
  }
  errno = n;
  perr_exit("pthread_join error");
}

void Pthread_mutex_lock(pthread_mutex_t *mptr)
{
  int n = pthread_mutex_lock(mptr);
  if ( n == 0 )
  {
    return;
  }
  errno = n;
  perr_exit("pthread_mutex_lock error...");
}

void Pthread_mutex_unlock(pthread_mutex_t *mptr)
{
  int n = pthread_mutex_unlock(mptr);
  if ( n == 0 )
  {
    return;
  }
  errno = n;
  perr_exit("pthread_mutex_unlock error...");
}

void Pthread_cond_signal(pthread_cond_t *cptr)
{
  int n = pthread_cond_signal(cptr);
  if ( n == 0 )
  {
    return;
  }
  errno = n;
  perr_exit("pthread_cond_signal error...");
}

void Pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr)
{
  int n = pthread_cond_wait(cptr, mptr);
  if ( n == 0 )
  {
    return;
  }
  errno = n;
  perr_exit("pthread_cond_wait error...");
}

三、服务端程序(web_server.h/web_server.c)—— 使用EPOLL高并发机制

#ifndef SERVER_H_
#define SERVER_H_

#include <pthread.h>
#include <sys/epoll.h>
#include "wrap.h"
#include "klist.h"

#define WEB_SERVER_PORT     80
#define WEB_SOCKET_EVENTS   65535

typedef struct
{
  int client_fd;
  struct sockaddr_in client_addr;
  struct list_head list;
} web_client_t;

typedef struct
{
  int sockfd;                // server socket
  int port;                  // server port
  struct sockaddr_in addr;   // server addr
  int epollfd;               // epoll handle
  struct epoll_event event_list[WEB_SOCKET_EVENTS]; // epoll event list
  
  pthread_t recv_thread;

  web_client_t client;       // client list -- save all client info
} web_server_t;

/* recv and send queue frame */
#define TCP_FRAME_SIZE 1200
typedef struct
{
  int sockfd;  // client socket
  uint16_t length;
  char data[TCP_FRAME_SIZE];
} web_frame_t;

void *WebServertInit(void);

#endif /* SERVER_H_ */
#include "web_server.h"
#include "http.h"

static web_server_t *web_socket_init(void)
{
  int opt = 1;
  web_server_t *current;
  struct epoll_event event;

  current = (web_server_t *)malloc(sizeof(web_server_t));

  current->port = WEB_SERVER_PORT;
  current->sockfd = Socket(AF_INET, SOCK_STREAM, 0);
  // SOL_SOCKET: port can same, ip not
  Setsockopt(current->sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
  current->addr.sin_family = AF_INET;
  current->addr.sin_port = htons(current->port);
  current->addr.sin_addr.s_addr = INADDR_ANY;

  Bind(current->sockfd, (struct sockaddr *)&current->addr, sizeof(current->addr));
  Listen(current->sockfd, 10);

  current->epollfd = Epollcreate(WEB_SOCKET_EVENTS);
  event.events = EPOLLIN | EPOLLET;
  event.data.fd = current->sockfd;
  // epoll_ctl set add
  Epollctl(current->epollfd, EPOLL_CTL_ADD, current->sockfd, &event);

  INIT_LIST_HEAD(&current->client.list);

  return current;
}

static void web_socket_accept(web_server_t *arg)
{
  web_server_t *current = arg;
  struct sockaddr_in addr;
  int len = sizeof(struct sockaddr_in);
  int new_fd = Accept(current->sockfd, (struct sockaddr *)&addr, (socklen_t *)&len);

  printf("new connection client_fd ( %d ) %s: %d\n", new_fd, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
  /* register epoll */
  struct epoll_event event;
  event.data.fd = new_fd;
  event.events = EPOLLIN | EPOLLET;
  Epollctl(current->epollfd, EPOLL_CTL_ADD, new_fd, &event);

  /* add client node */
  web_client_t *node = (web_client_t *)malloc(sizeof(web_client_t));
  node->client_fd = new_fd;
  memcpy(&node->client_addr, &addr, sizeof(struct sockaddr_in));

  list_add(&node->list, &current->client.list);
}

static void web_socket_recv(web_server_t *arg, int sockfd)
{
  web_server_t *current = arg;
  int length = 0;
  web_frame_t frame = {0};
  web_client_t *point = NULL;
  struct list_head *pos, *cur = NULL;

  length = read(sockfd, frame.data, TCP_FRAME_SIZE);

  if(0 == length)
  {
    list_for_each_safe(pos, cur, &current->client.list)
    {
      /* delete client node, close connect socket */
      point = list_entry(pos, web_client_t, list);
      if(point->client_fd == sockfd)
      {
        printf("client[%d] close\n", sockfd);
        list_del(pos);
        free(point);
        Close(sockfd);
      }
    }
  }
  else if(length > 0)
  {
    frame.sockfd = sockfd;
    frame.length = length;

    printf("***************************************\r\n");
    printf("%s\r\n", frame.data);
    printf("***************************************\r\n");

    HTTPSend(sockfd, frame.data, frame.length);

#if 0
    list_for_each_safe(pos, cur, &current->client.list)
    {
      /* delete client node, close connect socket */
      point = list_entry(pos, web_client_t, list);
      if(point->client_fd == sockfd)
      {
        printf("client[%d] close\n", sockfd);
        list_del(pos);
        free(point);
        Close(sockfd);
        return;
      }
    }
#endif
  }
  else
  {
    exit(-1);
  }

}

static void *server_recv_thread(void *arg)
{
  web_server_t *current = (web_server_t *)arg;

  while(1)
  {
    int timeout = 300, i;
    int ret = Epollwait(current->epollfd, current->event_list, WEB_SOCKET_EVENTS, timeout);

    if(ret == 0)
    {
      // timeout
      continue;
    }

    for(i = 0; i < ret; ++i)
    {
      if((current->event_list[i].events & EPOLLERR) ||
          (current->event_list[i].events & EPOLLHUP) ||
          !(current->event_list[i].events & EPOLLIN))
      {
        printf("epoll error\n");
        Close(current->event_list[i].data.fd);
        exit(-1);
      }
      if(current->event_list[i].data.fd == current->sockfd)
      {
        web_socket_accept(current);
      }
      else
      {
        web_socket_recv(current, current->event_list[i].data.fd);
      }
    }
  }

  Close(current->epollfd);
  Close(current->sockfd);

  return NULL;
}

void * WebServertInit(void)
{
  web_server_t *current = web_socket_init();

  Pthread_create(&current->recv_thread, NULL, server_recv_thread, current);

  return (void *)current;
}

四、HTTP解析(http.h/http.c写了一个基本的框架,很容易添加需要解析的文件类型)

#ifndef _HTTP_H_
#define _HTTP_H_

#include "type.h"

void HTTPSend(int socketfd, char *content, uint16_t length);

#endif /* _HTTP_H_ */
#include "http.h"
#include <stdio.h>
#include <string.h>
#include "wrap.h"

static void http_html_cb(int sockfd, const char *filename);
static void http_jpg_cb(int sockfd, const char *filename);
static void http_gif_cb(int sockfd, const char *filename);

typedef struct
{
  char *type;
  void (* http_cb)(int sockfd, const char *data);
} http_type_t;

static http_type_t http_items[] =
{
  {".html", http_html_cb},
  {".jpg",  http_jpg_cb},
  {".jpeg", http_jpg_cb},
  {".gif" , http_gif_cb},
};


static const char *http_ack = "HTTP/1.1 200 OK\r\n"
                              "Content-Type: %s\r\n"
                              "Server: LiBang's Server V1.0\r\n"
                              "Accept-Ranges: bytes\r\n"
                              "Content-Length: %d\r\n\r\n";


static void http_url(char *http_head, char *file_type, char *filename)
{
  uint16_t i = 0, j = 0, k = 0;

  while(http_head[i] != '/')
  {
    ++i;
  }

  while(http_head[i] != '.')
  {
    ++i;
    filename[k++] = http_head[i];
  }

  while(http_head[i] != ' ')
  {
    file_type[j++] = http_head[i++];
    filename[k++] = http_head[i];
  }

  filename[k - 1] = '\0'; // delete ' '
}

static void http_pakedge_send(int sockfd, const char *filename, char *type)
{
  FILE *filefd;
  char http_head[2048] = {0}, buffer[1024000] = {0};
  uint32_t len;

  filefd = fopen(filename, "rb");
  if(filefd == NULL)
  {
    printf("\r\n error...\r\n");
    return;
  }

  fseek(filefd, 0, SEEK_END);
  len = ftell(filefd);
  rewind(filefd);
  fread(buffer, len, 1, filefd);

  sprintf(http_head, http_ack, type, len);

  send(sockfd, http_head, strlen(http_head), 0);
  send(sockfd, buffer, len, 0);
}

static void http_html_cb(int sockfd, const char *filename)
{
  http_pakedge_send(sockfd, filename, "text/html");
}

static void http_jpg_cb(int sockfd, const char *filename)
{
  http_pakedge_send(sockfd, filename, "image/jpeg");
}

static void http_gif_cb(int sockfd, const char *filename)
{
  http_pakedge_send(sockfd, filename, "image/gif");
}

void HTTPSend(int sockfd, char *content, uint16_t length)
{
  char i, type[36] = {0}, filename[100] = {0};

  http_url(content, type, filename);
  printf("URL: %s,  filename: %s\r\n", type, filename);

  for(i = 0; i < sizeof(http_items) / sizeof(http_items[0]); ++i)
  {
    if(memcmp(http_items[i].type, type, strlen(http_items[i].type)) == 0)
    {
      http_items[i].http_cb(sockfd, filename);
      break;
    }
  }
}

要添加的文件类型就在http_items这个结构体数组里面,按照格式添加就OK了。

接下来创建一个html文件(index.html)

<html>
<head><title> Test Page </title></head>
<body>
	<p> Test OK </p>
	<img src = "mypic.jpg">
</body>\
</html>

在这个html里面,网页会显示“Test OK”和一张图片(这个图片自己想办法弄吧,哈哈)。

到这里,项目的架构基本上就完成了,再建一个main.c文件,调用一下接口就完了。

#include <stdio.h>
#include "web_server.h"

int main(void)
{
  WebServertInit();

  printf("------------test-----------\n");

  while(1)
  {
    sleep(1);
  }

  return 0;
}

五、项目目录组织(以项目的角度建立工程)

common/

               klist.h  type.h  wrap.c  wrap.h  Makefile

webserver/

               http.h  http.c  web_server.h  web_server.c  Makefile

index.html

mypic.jpj

main.c

Makefile

Makefile.build

顶层目录有一个Makefile,各子层目录下都有一个Makefile。

先写顶层目录下的Makefile和Makefile.build

依次是Makefile和Mkefile.build文件,最终会在顶层生成一个可执行文件http(目录可以自行指定):


CROSS_COMPILE =

AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm

STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump

export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP

CFLAGS := -Wall -O2 -g 
CFLAGS += -I $(shell pwd)/common
CFLAGS += -I $(shell pwd)/webserver
CFLAGS += -I $(shell pwd)/

LDFLAGS := -lm -lpthread

export CFLAGS LDFLAGS

TOPDIR := $(shell pwd)
export TOPDIR

TARGET := http

obj-y += common/
obj-y += webserver/
obj-y += main.o

all : 
	make -C ./ -f $(TOPDIR)/Makefile.build
	$(CC) -o $(TARGET) built-in.o $(LDFLAGS)

clean:
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)
PHONY := __build
__build:


obj-y :=
subdir-y :=

include Makefile

__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y	+= $(__subdir-y)

subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)

cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))

ifneq ($(dep_files),)
  include $(dep_files)
endif


PHONY += $(subdir-y)


__build : $(subdir-y) built-in.o

$(subdir-y):
	make -C $@ -f $(TOPDIR)/Makefile.build

built-in.o : $(cur_objs) $(subdir_objs)
	$(LD) -r -o $@ $^

dep_file = .$@.d

%.o : %.c
	$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
	
.PHONY : $(PHONY)

好了,说完了顶层目录(我就不给各位详细分析Makefile了,我以前也写过Makefile的博客,可以参考),再说说各子层Makefile的编写。上面把模板写好了,各子层就只需要包含它自己的.o文件就好。

common目录下的Makefile:

obj-y += wrap.o

webserver目录下的Makefile:

obj-y += http.o
obj-y += web_server.o

六、编译执行

切换到顶层目录,执行以下make即可生成可执行文件http。

有四个警告,你们去消除吧,哈哈,执行以下看看效果呗,在web网页上面输入 http://自己的IP/index.html,这个index.html是自己编写的。好了看看效果吧,正常解析出了这个html。

我们在log里面也打印出了接收到的消息,我们来看看。

OK,到这里一个最简单的一个web服务器就完成了,服务器采用了epoll机制,打开多个网页不成问题,http解析的部分写的比较简单,但是结构是比较清晰的,根据自己的需要可以添加各种类型的文件,一种类型一个处理函数,框架是不用动的。不过有一点可惜的是,目前大多数都是用https协议了(http+ssl),就是不再是透明传输了,加了一层加密层,让数据更加安全,这个简单的https的实现后面再说。

 

 

 

 

 

 

 

 

 

 

 

 

Logo

更多推荐