Linux网络编程(web服务器)

web服务器总体介绍

在这里插入图片描述

使用浏览器作为客户端访问web服务器;

使用到的知识:
  socket编程:
    socket setsockopt bind listen read write send recv close

编写网络服务器常用的模型有哪些:
  多进程版本,
  多线程版本
  多路IO复用:select poll epoll
  第三方库: libevent

TCP/IP四层模型:
  应用层—>传输层---->网络层---->数据链路层

本次案例所使用的协议:
  TCP+http协议
  http协议的请求和应答协议.
  html文件的编写

TCP协议重在数据的传输, 而http协议重在数据的解释

http协议请求报文格式

  1. 请求行 GET /test.txt HTTP/1.1
  2. 请求行 健值对
  3. 空行 \r\n
  4. 数据

http协议响应消息格式

  1. 状态行 200 表示成功, 404 表示请求的资源不存在
  2. 消息报头 健值对
  3. 空行 \r\n
  4. 响应正文

在这里插入图片描述

选择使用epoll模型作为web服务器

在这里插入图片描述

1 创建socket
2 设置端口复用
3 绑定
4 监听
5 创建epoll树
6 将监听文件描述符上epoll树
7

while(1)
  {
  	nready = epoll_wait();
  	if(nready<0)
  	{
  	
  	}
  	
  	for(i=0; i<nready; i++)
  	{
	  	//若是有新的客户端连接到来,则接受新的客户端连接, 然后上epoll树
	  	if(lfd==events[i].data.fd)
	  	{
	  		cfd = accept();
	  		epoll_ctl(epfd, cfd, EPOLL_CTL_ADD, &ev);
	  	}
	  	else
	  	{ 	
	  		http_request(cfd);
	  	}
  	}
  	
  }	
 
 //处理客户端的请求
 int http_request(int cfd)
 {
 	//读取数据
 	//先读取一行, 可以获得浏览器想请求的文件
 	//然后循环将剩余的数据读完
 	
 	//查看本地是否有这个文件
 		---若没有这个文件, 则组织应答信息并且将404.jpg文件内容发送给浏览器
 		---若有个这个文件, 则判断文件类型
 			----若是普通文件, 则组织应答消息并且将文件内容发送给浏览器
 			----若是目录, 则组织应答消息, 响应正文部分将目录下的所有文件组织
 			成文件列表的形式生成html文件(每个文件都是一个超连接), 然后发送给浏览器 
 }

read函数读通信文件描述符是阻塞的, 所以在while中循环读的时候若读完数据了会引起阻塞; 所以应该将通信文件描述符设置为非阻塞.

char *path = getenv(“HOME”);

wrap.h

#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>

void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int tcp4bind(short port,const char *IP);
#endif

wrap.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
//绑定错误显示和退出
void perr_exit(const char *s)
{
	perror(s);
	exit(-1);
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
	int n;

again:
	if ((n = accept(fd, sa, salenptr)) < 0) {
		if ((errno == ECONNABORTED) || (errno == EINTR))//ECONNABORTED 代表连接失败 ETINTR 代表被信号打断
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}

int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

	if ((n = bind(fd, sa, salen)) < 0)
		perr_exit("bind error");

    return n;
}

int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

	if ((n = connect(fd, sa, salen)) < 0)
		perr_exit("connect error");

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

	if ((n = listen(fd, backlog)) < 0)
		perr_exit("listen error");

    return n;
}

int Socket(int family, int type, int protocol)
{
	int n;

	if ((n = socket(family, type, protocol)) < 0)
		perr_exit("socket error");

	return n;
}

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;
}

int Close(int fd)
{
    int n;
	if ((n = close(fd)) == -1)
		perr_exit("close error");

    return n;
}

/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{
	size_t  nleft;              //usigned int 剩余未读取的字节数
	ssize_t nread;              //int 实际读到的字节数
	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];//定义了100的缓冲区

	if (read_cnt <= 0) {
again:
        //使用缓冲区可以避免多次从底层缓冲读取数据--为了提高效率
		if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
			if (errno == EINTR)
				goto again;
			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;//0 = '\0'
			return n - 1;
		} else
			return -1;
	}
	*ptr  = 0;

	return n;
}

int tcp4bind(short port,const char *IP)
{
    struct sockaddr_in serv_addr;
    int lfd = Socket(AF_INET,SOCK_STREAM,0);
    bzero(&serv_addr,sizeof(serv_addr));//清空serv_addr地址 对比 memset()
    if(IP == NULL){
        //如果这样使用 0.0.0.0,任意ip将可以连接
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    }else{
        if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){
            perror(IP);//转换失败
            exit(1);
        }
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port   = htons(port);
    int opt = 1;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    return lfd;
}

pub.h

#ifndef _PUB_H
#define _PUB_H
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <ctype.h>
char *get_mime_type(char *name);
int get_line(int sock, char *buf, int size);
int hexit(char c);//16进制转10进制
void strencode(char* to, size_t tosize, const char* from);//编码
void strdecode(char *to, char *from);//解码
#endif

pub.c

#include "pub.h"
//通过文件名字获得文件类型
char *get_mime_type(char *name)
{
    char* dot;

    dot = strrchr(name, '.');	//自右向左查找‘.’字符;如不存在返回NULL
    /*
     *charset=iso-8859-1	西欧的编码,说明网站采用的编码是英文;
     *charset=gb2312		说明网站采用的编码是简体中文;
     *charset=utf-8			代表世界通用的语言编码;
     *						可以用到中文、韩文、日文等世界上所有语言编码上
     *charset=euc-kr		说明网站采用的编码是韩文;
     *charset=big5			说明网站采用的编码是繁体中文;
     *
     *以下是依据传递进来的文件名,使用后缀判断是何种文件类型
     *将对应的文件类型按照http定义的关键字发送回去
     */
    if (dot == (char*)0)
        return "text/plain; charset=utf-8";
    if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
        return "text/html; charset=utf-8";
    if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
        return "image/jpeg";
    if (strcmp(dot, ".gif") == 0)
        return "image/gif";
    if (strcmp(dot, ".png") == 0)
        return "image/png";
    if (strcmp(dot, ".css") == 0)
        return "text/css";
    if (strcmp(dot, ".au") == 0)
        return "audio/basic";
    if (strcmp( dot, ".wav") == 0)
        return "audio/wav";
    if (strcmp(dot, ".avi") == 0)
        return "video/x-msvideo";
    if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
        return "video/quicktime";
    if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
        return "video/mpeg";
    if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
        return "model/vrml";
    if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
        return "audio/midi";
    if (strcmp(dot, ".mp3") == 0)
        return "audio/mpeg";
    if (strcmp(dot, ".ogg") == 0)
        return "application/ogg";
    if (strcmp(dot, ".pac") == 0)
        return "application/x-ns-proxy-autoconfig";

    return "text/plain; charset=utf-8";
}
/**********************************************************************/
/* 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) */
/**********************************************************************/
//获得一行数据,每行以\r\n作为结束标记
int get_line(int sock, char *buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;

    while ((i < size - 1) && (c != '\n'))
    {
        n = recv(sock, &c, 1, 0);
        /* DEBUG printf("%02X\n", c); */
        if (n > 0)
        {
            if (c == '\r')
            {
                n = recv(sock, &c, 1, MSG_PEEK);//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';

    return(i);
}

//下面的函数第二天使用
/*
 * 这里的内容是处理%20之类的东西!是"解码"过程。
 * %20 URL编码中的‘ ’(space)
 * %21 '!' %22 '"' %23 '#' %24 '$'
 * %25 '%' %26 '&' %27 ''' %28 '('......
 * 相关知识html中的‘ ’(space)是&nbsp
 */
void strdecode(char *to, char *from)
{
    for ( ; *from != '\0'; ++to, ++from) {

        if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) { //依次判断from中 %20 三个字符

            *to = hexit(from[1])*16 + hexit(from[2]);//字符串E8变成了真正的16进制的E8
            from += 2;                      //移过已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符
        } else
            *to = *from;
    }
    *to = '\0';
}

//16进制数转化为10进制, return 0不会出现
int hexit(char c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;

    return 0;
}

//"编码",用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。
//strencode(encoded_name, sizeof(encoded_name), name);
void strencode(char* to, size_t tosize, const char* from)
{
    int tolen;

    for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {
        if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) {
            *to = *from;
            ++to;
            ++tolen;
        } else {
            sprintf(to, "%%%02x", (int) *from & 0xff);
            to += 3;
            tolen += 3;
        }
    }
    *to = '\0';
}

webserver.c

//web服务端程序--使用epoll模型
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>

#include "pub.h"
#include "wrap.h"

int http_request(int cfd);

int main()
{
	//改变当前进程的工作目录
	char path[255] = {0};
	sprintf(path, "%s/%s", getenv("HOME"), "webpath");
	chdir(path);
	
	//创建socket--设置端口复用---bind
	int lfd = tcp4bind(9999, NULL);
	
	//设置监听
	Listen(lfd, 128);

	//创建epoll树
	int epfd = epoll_create(1024);
	if(epfd<0)
	{
		perror("epoll_create error");
		close(lfd);
		return -1;
	}
	
	//将监听文件描述符lfd上树
	struct epoll_event ev;
	ev.data.fd = lfd;
	ev.events = EPOLLIN;
	epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
	
	int i;
	int cfd;
	int nready;
	int sockfd;
	struct epoll_event events[1024];
	while(1)
	{
		//等待事件发生
		nready = epoll_wait(epfd, events, 1024, -1);
		if(nready<0)
		{
			if(errno==EINTR)
			{
				continue;
			}
			break;
		}
		
		for(i=0; i<nready; i++)
		{
			sockfd = events[i].data.fd;
			//有客户端连接请求
			if(sockfd==lfd)
			{
				//接受新的客户端连接
				cfd = Accept(lfd, NULL, NULL);
				
				//设置cfd为非阻塞
				int flag = fcntl(cfd, F_GETFL);
				flag |= O_NONBLOCK;
				fcntl(cfd, F_SETFL, flag);
				
				//将新的cfd上树
				ev.data.fd = cfd;
				ev.events = EPOLLIN;
				epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
			}
			else 
			{
				//有客户端数据发来
				http_request(cfd);
			}			
		}		
	}
}

int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{
	char buf[1024] = {0};
	sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);
	sprintf(buf+strlen(buf), "Content-Type:%s\r\n", fileType);
	if(len>0)
	{
		sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);
	}
	strcat(buf, "\r\n");
	Write(cfd, buf, strlen(buf));
	return 0;
}

int send_file(int cfd, char *fileName)
{
	//打开文件
	int fd = open(fileName, O_RDONLY);
	if(fd<0)
	{
		perror("open error");
		return -1;
	}
	
	//循环读文件, 然后发送
	int n;
	char buf[1024];
	while(1)
	{
		memset(buf, 0x00, sizeof(buf));
		n = read(fd, buf, sizeof(buf));
		if(n<=0)
		{
			break;
		}
		else 
		{
			Write(cfd, buf, n);
		}
	}
}

int http_request(int cfd)
{
	int n;
	char buf[1024];
	//读取请求行数据, 分析出要请求的资源文件名
	memset(buf, 0x00, sizeof(buf));
	Readline(cfd, buf, sizeof(buf));
	printf("buf==[%s]\n", buf);
	//GET /hanzi.c HTTP/1.1
	char reqType[16] = {0};
	char fileName[255] = {0};
	char protocal[16] = {0};
	sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);
	printf("[%s]\n", reqType);
	printf("[%s]\n", fileName);
	printf("[%s]\n", protocal);
	
	char *pFile = fileName+1;
	printf("[%s]\n", pFile);
	
	//循环读取完剩余的数据
	while((n=Readline(cfd, buf, sizeof(buf)))>0);
	
	//判断文件是否存在
	struct stat st;
	if(stat(pFile, &st)<0)
	{
		printf("file not exist\n");
		
		//发送头部信息
		send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
		
		//发送文件内容
		send_file(cfd, "error.html");	
	}
	else //若文件存在
	{
		//判断文件类型
		//普通文件
		if(S_ISREG(st.st_mode))
		{
			printf("file exist\n");
			//发送头部信息
			send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);
			
			//发送文件内容
			send_file(cfd, pFile);
		}
		//目录文件
		else if(S_ISDIR(st.st_mode))
		{
			
		}
	}
}

遍历目录的函数

1 opendir readdir closedir
2

	int scandir(const char *dirp, struct dirent ***namelist,
                int (*filter)(const struct dirent *),
                int (*compar)(const struct dirent **, const struct dirent **));
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
int main(void)
{
   struct dirent **namelist;
   int n;

   n = scandir(".", &namelist, NULL, alphasort);
   if (n < 0)
       perror("scandir");
   else {
       while (n--) {
           printf("%s\n", namelist[n]->d_name);
           free(namelist[n]);
       }
       free(namelist);
   }
}
<html><head><title>Index of ./</title></head>
<body bgcolor="#99cc99"><h4>Index of ./</h4>
<ul type=circle>
	<--文件列表部分-->
	<li>
		<a href=filename>filename</a>
	</li>
</ul>
<address><a href="http://www.itcast.com/">xhttpd</a></address>
</body></html>

SIGPIPE信号的产生: 若服务端传输数据的时候, 浏览器连接关闭, 此时由于是正在写一个
没有读端的连接, 此时内核会给web服务端发送SIGPIPE信号.

webserver.c

//web服务端程序--使用epoll模型
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>

#include "pub.h"
#include "wrap.h"

int http_request(int cfd, int epfd);

int main()
{
	//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, 
	//则web服务器就会收到SIGPIPE信号
	struct sigaction act;
	act.sa_handler = SIG_IGN;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGPIPE, &act, NULL);
	
	//改变当前进程的工作目录
	char path[255] = {0};
	sprintf(path, "%s/%s", getenv("HOME"), "webpath");
	chdir(path);
	
	//创建socket--设置端口复用---bind
	int lfd = tcp4bind(9999, NULL);
	
	//设置监听
	Listen(lfd, 128);

	//创建epoll树
	int epfd = epoll_create(1024);
	if(epfd<0)
	{
		perror("epoll_create error");
		close(lfd);
		return -1;
	}
	
	//将监听文件描述符lfd上树
	struct epoll_event ev;
	ev.data.fd = lfd;
	ev.events = EPOLLIN;
	epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
	
	int i;
	int cfd;
	int nready;
	int sockfd;
	struct epoll_event events[1024];
	while(1)
	{
		//等待事件发生
		nready = epoll_wait(epfd, events, 1024, -1);
		if(nready<0)
		{
			if(errno==EINTR)
			{
				continue;
			}
			break;
		}
		
		for(i=0; i<nready; i++)
		{
			sockfd = events[i].data.fd;
			//有客户端连接请求
			if(sockfd==lfd)
			{
				//接受新的客户端连接
				cfd = Accept(lfd, NULL, NULL);
				
				//设置cfd为非阻塞
				int flag = fcntl(cfd, F_GETFL);
				flag |= O_NONBLOCK;
				fcntl(cfd, F_SETFL, flag);
				
				//将新的cfd上树
				ev.data.fd = cfd;
				ev.events = EPOLLIN;
				epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
			}
			else 
			{
				//有客户端数据发来
				http_request(sockfd, epfd);
			}			
		}		
	}
}

int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{
	char buf[1024] = {0};
	sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);
	sprintf(buf+strlen(buf), "Content-Type:%s\r\n", fileType);
	if(len>0)
	{
		sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);
	}
	strcat(buf, "\r\n");
	Write(cfd, buf, strlen(buf));
	return 0;
}

int send_file(int cfd, char *fileName)
{
	//打开文件
	int fd = open(fileName, O_RDONLY);
	if(fd<0)
	{
		perror("open error");
		return -1;
	}
	
	//循环读文件, 然后发送
	int n;
	char buf[1024];
	while(1)
	{
		memset(buf, 0x00, sizeof(buf));
		n = read(fd, buf, sizeof(buf));
		if(n<=0)
		{
			break;
		}
		else 
		{
			Write(cfd, buf, n);
		}
	}
}

int http_request(int cfd, int epfd)
{
	int n;
	char buf[1024];
	//读取请求行数据, 分析出要请求的资源文件名
	memset(buf, 0x00, sizeof(buf));
	n = Readline(cfd, buf, sizeof(buf));
	if(n<=0)
	{
		//printf("read error or client closed, n==[%d]\n", n);
		//关闭连接
		close(cfd);
		
		//将文件描述符从epoll树上删除
		epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
		return -1;	
	}
	printf("buf==[%s]\n", buf);
	//GET /hanzi.c HTTP/1.1
	char reqType[16] = {0};
	char fileName[255] = {0};
	char protocal[16] = {0};
	sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);
	//printf("[%s]\n", reqType);
	printf("--[%s]--\n", fileName);
	//printf("[%s]\n", protocal);
	
	char *pFile = fileName;
	if(strlen(fileName)<=1)
	{
		strcpy(pFile, "./");
	}
	else 
	{
		pFile = fileName+1;
	}
	
	//转换汉字编码
	strdecode(pFile, pFile);
	printf("[%s]\n", pFile);
	
	//循环读取完剩余的数据,避免产生粘包
	while((n=Readline(cfd, buf, sizeof(buf)))>0);
	
	//判断文件是否存在
	struct stat st;
	if(stat(pFile, &st)<0)
	{
		printf("file not exist\n");
		
		//发送头部信息
		send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
		
		//发送文件内容
		send_file(cfd, "error.html");	
	}
	else //若文件存在
	{
		//判断文件类型
		//普通文件
		if(S_ISREG(st.st_mode))
		{
			printf("file exist\n");
			//发送头部信息
			send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);
			
			//发送文件内容
			send_file(cfd, pFile);
		}
		//目录文件
		else if(S_ISDIR(st.st_mode))
		{
			printf("目录文件\n");
			
			char buffer[1024];
			//发送头部信息
			send_header(cfd, "200", "OK", get_mime_type(".html"), 0);	
			
			//发送html文件头部
			send_file(cfd, "html/dir_header.html");	
			
			//文件列表信息
			struct dirent **namelist;
			int num;

			num = scandir(pFile, &namelist, NULL, alphasort);
			if (num < 0)
			{
			   perror("scandir");
			   close(cfd);
			   epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
			   return -1;
			   
			}
			else 
			{
			   while (num--) 
			   {
			       printf("%s\n", namelist[num]->d_name);
			       memset(buffer, 0x00, sizeof(buffer));
			       if(namelist[num]->d_type==DT_DIR)
			       {
			       		sprintf(buffer, "<li><a href=%s/>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
			       }
			       else
			       {
			       		sprintf(buffer, "<li><a href=%s>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
			       }
			       free(namelist[num]);
			       Write(cfd, buffer, strlen(buffer));
			   }
			   free(namelist);
			}
			//发送html尾部
			sleep(10);
			send_file(cfd, "html/dir_tail.html");		
		}
	}
	
	return 0;
}

日志

#ifndef LOG_H
#define LOG_H
/* ************************************************************************************ */

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/types.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>

#define LOG_PROCNAME      0x00000001              /* msglog 输出日志时打印程序名        */
#define LOG_PID           0x00000010              /* msglog 输出日志时打印进程 PID      */
#define LOG_PERROR        0x00000100              /* msglog 是否把告警内容输出到stderr  */
#define NLO_PROCNAME      0x11111110              /* msglog 不输出程序名                */
#define NLO_PID           0x11111101              /* msglog 不输出进程 PID              */
#define NLO_PERROR        0x11111011              /* msglog 不输出告警到stderr          */

#define MSG_INFO          0x00000001              /* msglog 输出到告警日志文件中        */
#define MSG_WARN          0x00000010              /* msglog 输出到普通日志文件中        */
#define MSG_BOTH          MSG_INFO|MSG_WARN       /* msglog 输出到普通和告警日志文件中  */

#define LOG_MESSAGE_FILE  "/home/itheima/log/tcpsvr"           /* 系统程序运行日志信息文件           */
#define LOG_MESSAGE_DFMT  "%m-%d %H:%M:%S"        /* 日志信息时间格式字串               */
#define LOG_POSTFIX_MESS  "%y%m"                  /* 程序运行日志信息文件后缀           */
#define LOG_WARNING_FILE  "/home/itheima/log/log.sys_warn"   /* 系统程序运行告警日志文件           */
#define LOG_WARNING_DFMT  "%m-%d %H:%M:%S"        /* 告警信息时间格式字串               */
#define LOG_POSTFIX_WARN  ""                      /* 程序运行告警日志文件后缀           */

/* ************************************************************************************ */
int msglog(int mtype, char *outfmt, ...);//写日志函数
int msgLogFormat(int mopt, char *mdfmt, int wopt, char *wdfmt);//对日志格式化 
int msgLogOpen(char *ident, char *mpre, char *mdate, char *wpre, char *wdate);//打开日志文件
int msgLogClose(void);//关闭日志文件

long begusec_process(void);                      /* 设置开始时间 0=ok                   */
long getusec_process(void);                      /* 返回usecond 从 begusec_process历时  */

int msgInit(char *pName);
#endif
/* ************************************************************************************ */
/* ************************************************************************************ */
/* ************************************************************************************ */
/* ************************************************************************************ */

log.c


#include "log.h"

static int  msgopt, wanopt;
static char msgdatefmt[100], wandatefmt[100], ident_name[100];
static struct timeval be_stime;
static FILE *msgfile = NULL, *wanfile = NULL;
/* ************************************************************************************ */
/* ************************************************************************************ */
/* ************************************************************************************ */
//日志文件初始化,也可以通过msgLogOpen进行初始化
int msgInit(char *pName)
{
    if (msgLogOpen(pName, LOG_MESSAGE_FILE, LOG_POSTFIX_MESS,LOG_WARNING_FILE, LOG_POSTFIX_WARN) == 0)
    {
        msgLogFormat(LOG_PROCNAME|LOG_PID, LOG_MESSAGE_DFMT, LOG_PROCNAME|LOG_PID, LOG_WARNING_DFMT);
    }
    else                                                                                                
    {
        printf("can not create log!\n");
        return -1;
    }
    return 0;
}
/* ************************************************************************************ */
int msgLogOpen(char *ident, char *mpre, char *mdate, char *wpre, char *wdate) /* 打开日志 */
{
    time_t now_time;
    char openfilename[200], timestring[100];

    now_time = time(NULL);
    if ((!msgfile) && (*mpre))
    {
        strcpy(openfilename, mpre);
        if (*mdate)
        {
            strftime(timestring, sizeof(timestring), mdate, localtime(&now_time));
            strcat(openfilename, ".");
            strcat(openfilename, timestring);
        }
        if ((msgfile = fopen(openfilename, "a+b")) == NULL)
        { /* 如果没有应该把目录建上 */
            printf("openfilename=%s\n", openfilename);
            return -1;
        }
        setlinebuf(msgfile);
    }
    if ((!wanfile) && (*wpre))
    {
        strcpy(openfilename, wpre);
        if (*wdate)
        {
            strftime(timestring, sizeof(timestring), wdate, localtime(&now_time));
            strcat(openfilename, ".");
            strcat(openfilename, timestring);
        }
        if ((wanfile = fopen(openfilename, "a+b")) == NULL)
        {
            return -1;
        }
        setlinebuf(wanfile);
    }
    if ((msgfile) && (wanfile))
    {
        if (*ident)
        {
            strcpy(ident_name, ident);
        } else {
            ident_name[0] = '\0';
        }
        msgopt = LOG_PROCNAME|LOG_PID;          /* 设置默认信息输出信息选项              */
        wanopt = LOG_PROCNAME|LOG_PID;          /* 设置默认告警输出信息选项              */
        strcpy(msgdatefmt, "%m-%d %H:%M:%S");   /* 默认信息输出时间格式 MM-DD HH24:MI:SS */
        strcpy(wandatefmt, "%m-%d %H:%M:%S");   /* 默认告警输出时间格式 MM-DD HH24:MI:SS */

        msglog(MSG_INFO,"File is msgfile=[%d],wanfile=[%d].",fileno(msgfile),fileno(wanfile));
        return 0;
    } else {
        return -1;
    }
}
/* ************************************************************************************ */
/* 自定义日志输出函数系列,可以按普通信息及告警信息分类输出程序日志                      */
int msglog(int mtype, char *outfmt, ...)
{
    time_t now_time;
    va_list ap;//变参的列表
    char logprefix[1024], tmpstring[1024];

    time(&now_time);
    if (mtype & MSG_INFO)
    { /*strftime会将localtime(&now_time)按照msgdatefmt格式,输出到logprefix.*/
        strftime(logprefix, sizeof(logprefix), msgdatefmt, localtime(&now_time));
        strcat(logprefix, " ");
        /*static int  msgopt,wanopt;*/
        if (msgopt&LOG_PROCNAME)
        {
            strcat(logprefix, ident_name);
            strcat(logprefix, " ");
        }
        if (msgopt&LOG_PID)
        {
            sprintf(tmpstring, "[%6d]", getpid());
            strcat(logprefix, tmpstring);
        }
        fprintf(msgfile, "%s: ", logprefix);
        va_start(ap, outfmt);
        vfprintf(msgfile, outfmt, ap);
        va_end(ap);
        fprintf(msgfile, "\n");
    }
    if (mtype & MSG_WARN)
    {
        strftime(logprefix, sizeof(logprefix), wandatefmt, localtime(&now_time));
        strcat(logprefix, " ");
        /*#define LOG_PROCNAME      0x00000001*/              /* msglog 输出日志时打印程序名        */
        if (wanopt & LOG_PROCNAME)
        {
            strcat(logprefix, ident_name);
            strcat(logprefix, " ");
        }
        if (wanopt & LOG_PID)
        {
            sprintf(tmpstring, "[%6d]", getpid());
            strcat(logprefix, tmpstring);
        }
        fprintf(wanfile, "%s: ", logprefix);
        va_start(ap, outfmt);
        vfprintf(wanfile, outfmt, ap);
        va_end(ap);
        fprintf(wanfile, "\n");
        if (wanopt & LOG_PERROR)
        {
            fprintf(stderr, "%s: ", logprefix);
            va_start(ap, outfmt);
            vfprintf(stderr, outfmt, ap);
            va_end(ap);
            fprintf(stderr, "\n");
        }
    }

    return 0;
}
/* ************************************************************************************ */
int msgLogFormat(int mopt, char *mdfmt, int wopt, char *wdfmt)   /* 设置日志格式及选项  */
{
    if (mopt >= 0)
    {
        msgopt = mopt;
    } else {
        msgopt = msgopt & mopt;
    }
    if (wopt >= 0)
    {
        wanopt = wopt;
    } else {
        wanopt = wanopt & wopt;
    }

    if (*mdfmt) strcpy(msgdatefmt, mdfmt);
    if (*wdfmt) strcpy(wandatefmt, wdfmt);

    return 0;
}
/* ************************************************************************************ */
int msgLogClose(void)                                           /* 关闭日志文件         */
{
    if (msgfile) fclose(msgfile);
    if (wanfile) fclose(wanfile);

    return 0;
}
/* ************************************************************************************ */
long begusec_process(void)                      /* 设置开始时间 0=ok                    */
{
    gettimeofday(&be_stime,NULL);

    return 0;
}
/* ************************************************************************************ */
long getusec_process(void)                      /* 返回usecond 从 begusec_process历时   */
{
    struct timeval ed_stime;

    gettimeofday(&ed_stime,NULL);

    return ((ed_stime.tv_sec-be_stime.tv_sec)*1000000+ed_stime.tv_usec-be_stime.tv_usec);
}
/* ************************************************************************************ */
/* ************************************************************************************ */
/* ************************************************************************************ */
/* ************************************************************************************ */

log_demo.c

#include "log.h"


int main(int argc,char *argv[])
{
    char *pName = argv[0];
    pName +=2;
    printf("pName is %s\n",pName);
    msgInit("log_demo");
    msglog(MSG_INFO,"begin run program....");
    sleep(2);
    msglog(MSG_BOTH,"begin to game over...%ld",time(NULL));
    msgLogClose();
    return 0;
}

汉字编码问题

在这里插入图片描述

%E8%8B%A6%E7%93%9C.txt
e8 8b a6 e7 93 9c

e8---->e8

‘A’—>10
‘A’-‘A’+10
‘C’- ‘A’+10 = 12

(‘e’ - ‘a’ + 10)*16 + (‘8’-‘0’)

公式为c: r

if(c>='0' && c<='9')
{
	r = c-'0';
}
else if(c>='A' && c<='F')
{
	r = c-'A' + 10;
}
else ifif(c>='a' && c<='f')
{
	r = c-'a' + 10;
}

ff—>15*16+15=255

libevent编写web服务器

event_wb.c

//通过libevent编写的web服务器
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "pub.h"
#include <event.h>
#include <event2/listener.h>
#include <dirent.h>

#define _WORK_DIR_ "%s/webpath"
#define _DIR_PREFIX_FILE_ "html/dir_header.html"
#define _DIR_TAIL_FILE_ "html/dir_tail.html"

int copy_header(struct bufferevent *bev,int op,char *msg,char *filetype,long filesize)
{
    char buf[4096]={0};
    sprintf(buf,"HTTP/1.1 %d %s\r\n",op,msg);
    sprintf(buf,"%sContent-Type: %s\r\n",buf,filetype);
    if(filesize >= 0){
        sprintf(buf,"%sContent-Length:%ld\r\n",buf,filesize);
    }
    strcat(buf,"\r\n");
    bufferevent_write(bev,buf,strlen(buf));
    return 0;
}
int copy_file(struct bufferevent *bev,const char *strFile)
{
    int fd = open(strFile,O_RDONLY);
    char buf[1024]={0};
    int ret;
    while( (ret = read(fd,buf,sizeof(buf))) > 0 ){
        bufferevent_write(bev,buf,ret);
    }
    close(fd);
    return 0;
}
//发送目录,实际上组织一个html页面发给客户端,目录的内容作为列表显示
int send_dir(struct bufferevent *bev,const char *strPath)
{
    //需要拼出来一个html页面发送给客户端
    copy_file(bev,_DIR_PREFIX_FILE_);
    //send dir info 
    DIR *dir = opendir(strPath);
    if(dir == NULL){
        perror("opendir err");
        return -1;
    }
    char bufline[1024]={0};
    struct dirent *dent = NULL;
    while( (dent= readdir(dir) ) ){
        struct stat sb;
        stat(dent->d_name,&sb);
        if(dent->d_type == DT_DIR){
            //目录文件 特殊处理
            //格式 <a href="dirname/">dirname</a><p>size</p><p>time</p></br>
            memset(bufline,0x00,sizeof(bufline));
            sprintf(bufline,"<li><a href='%s/'>%32s</a>   %8ld</li>",dent->d_name,dent->d_name,sb.st_size);
            bufferevent_write(bev,bufline,strlen(bufline));
        }
        else if(dent->d_type == DT_REG){
            //普通文件 直接显示列表即可
            memset(bufline,0x00,sizeof(bufline));
            sprintf(bufline,"<li><a href='%s'>%32s</a>     %8ld</li>",dent->d_name,dent->d_name,sb.st_size);
            bufferevent_write(bev,bufline,strlen(bufline));
        }
    }
    closedir(dir);
    copy_file(bev,_DIR_TAIL_FILE_);
    //bufferevent_free(bev);
    return 0;
}
int http_request(struct bufferevent *bev,char *path)
{
    
    strdecode(path, path);//将中文问题转码成utf-8格式的字符串
    char *strPath = path;
    if(strcmp(strPath,"/") == 0 || strcmp(strPath,"/.") == 0){
        strPath = "./";
    }
    else{
        strPath = path+1;
    }
    struct stat sb;
    
    if(stat(strPath,&sb) < 0){
        //不存在 ,给404页面
        copy_header(bev,404,"NOT FOUND",get_mime_type("error.html"),-1);
        copy_file(bev,"error.html");
        return -1;
    }
    if(S_ISDIR(sb.st_mode)){
        //处理目录
        copy_header(bev,200,"OK",get_mime_type("ww.html"),sb.st_size);
        send_dir(bev,strPath);
        
    }
    if(S_ISREG(sb.st_mode)){
        //处理文件
        //写头
        copy_header(bev,200,"OK",get_mime_type(strPath),sb.st_size);
        //写文件内容
        copy_file(bev,strPath);
    }

    return 0;
}

void read_cb(struct bufferevent *bev, void *ctx)
{
    char buf[256]={0};
    char method[10],path[256],protocol[10];
    int ret = bufferevent_read(bev, buf, sizeof(buf));
    if(ret > 0){

        sscanf(buf,"%[^ ] %[^ ] %[^ \r\n]",method,path,protocol);
        if(strcasecmp(method,"get") == 0){
            //处理客户端的请求
            char bufline[256];
            write(STDOUT_FILENO,buf,ret);
            //确保数据读完
            while( (ret = bufferevent_read(bev, bufline, sizeof(bufline)) ) > 0){
                write(STDOUT_FILENO,bufline,ret);
            }
			http_request(bev,path);//处理请求

        }
    }
}
void bevent_cb(struct bufferevent *bev, short what, void *ctx)
{
    if(what & BEV_EVENT_EOF){//客户端关闭
        printf("client closed\n");
        bufferevent_free(bev);
    }
    else if(what & BEV_EVENT_ERROR){
        printf("err to client closed\n");
        bufferevent_free(bev);
    }
    else if(what & BEV_EVENT_CONNECTED){//连接成功
        printf("client connect ok\n");
    }
}
void listen_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)
{
    //定义与客户端通信的bufferevent
    struct event_base *base = (struct event_base *)arg;
    struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    bufferevent_setcb(bev,read_cb,NULL,bevent_cb,base);//设置回调
    bufferevent_enable(bev,EV_READ|EV_WRITE);//启用读和写
}

int main(int argc,char *argv[])
{
	char workdir[256] = {0};
	sprintf(workdir,_WORK_DIR_,getenv("HOME"));//HOME=/home/itheima 
	chdir(workdir);
    struct event_base *base = event_base_new();//创建根节点
    struct sockaddr_in serv;
    serv.sin_family = AF_INET;
    serv.sin_port = htons(9999);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    struct evconnlistener * listener =evconnlistener_new_bind(base,
                                     listen_cb, base, LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
                                                        (struct sockaddr *)&serv, sizeof(serv));//连接监听器
    

    event_base_dispatch(base);//循环

    event_base_free(base); //释放根节点
    evconnlistener_free(listener);//释放链接监听器
    return 0;
}

demo.html

<html>
	<head>
		<meta http-equiv="content-Type" content="text/html; charset=utf8">
		<title>我的第一个html文档,内容很丰富</title>
	</head>
	<body>
		<!--我是注释-->
		<font color="#FFA500" size=10> hello world </font>
		<h1>how are you</h1>
		<h2>how are you</h2>
		<h3>how are you</h3>
		<br/><hr/>
		<h4>how are you</h4>
		<h5>how are you</h5>
		<br/>
		<h6>how are you</h6>
		
		<!--无序标签-->
		<ul type="circle">
			<li>语文</li>
			<li>数学</li>
			<li>英语</li>
			<li>历史</li>
		</ul>
		
		<!--有序标签-->
		<ol type="i">
			<li>语文</li>
			<li><a href="语文">语文</a></li>
			<li>数学</li>
			<li>英语</li>
			<li>历史</li>
		</ol>
		
		<!--显示图片-->
		<image src="img1.bmp" alt=="无此图" title="马云" width=600 height=400>
		<!--超连接-->
		<br/>
		<a href="http://www.baidu.com" title="去百度">去百度</a>
	</body>
</html>

inedx.html

<HTML>
<TITLE>Index</TITLE>
<BODY>
<P>Welcome to J. David's webserver.
<H1>post demo
<FORM METHOD="get">
User: <INPUT TYPE="text" NAME="user">
Psswd: <INPUT TYPE="password" NAME="user">
<INPUT TYPE="submit">
</FORM>
</BODY>
</HTML>

test.html

<html>
	<head>
		<title>my first html page</title>
		<meta http-equiv="content-Type" content="text/html; charset=utf8">
	</head>
	
	<body>
		<font color="green" size=5>hello world</font>
		<h1> hello world </h1>
		<h2> hello world </h2>
		<h3> hello world </h3>
		<h4> hello world </h4>
		<h5> hello world </h5>
		<h6> hello world </h6>
		<br/>
		
		<hr/>
		
		<ul type=square>
			<li>语文</li>
			<li>数学</li>
			<li>英语</li>
			<li>历史</li>
			<li>地理</li>
		</ul>
		
		<ol type=i>
			<li>abc</li>
			<li>bcd</li>
			<li>yyy</li>
			<li>zzz</li>
			<li>def</li>
		</ol>
		
		<font color="#000080" size=7> 你好呀 </font>
		
		</br>
		<img src="./img1.bmp" alt="mayun" title="哎呀呀" width=600 height=600>
		
		</br>
		<a href="http://www.163.com" title="goto wangyi" target="_blank">www.163.com</a>
			
		<a href="http://www.163.com" title="goto wangyi" target="_blank"><img src="./img1.bmp" alt="mayun" title="哎呀呀" width=600 height=600></a>
		
	<ul>
		<li><a href=test.log> test.log </a></li>
		
		<li><a href=test.log> test.log </a></li>
		<li><a href=test.log> test.log </a></li>
	</ul>
	</body>
</html>
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值