总体介绍
使用浏览器作为客户端访问web服务器;
使用的知识总结:
socktet编程:
socket -> setsockopt -> bind -> listen -> read -> write -> send -> recv -> close ;
常用网络服务器模型:
多进程版本;
多线程版本;
多路IO复用;
第三方库libevent;
TCP/IP四层模型:
应用层 -> 传输层 -> 网络层 -> 数据链路层
使用的协议:
TCP/IP、http协议
熟悉http协议的请求和应答协议
http协议请求报文格式:
1 请求行 GET /test.txt HTTP/1.1
2 请求行 健值对
3 空行 \r\n
4 数据
http协议响应消息格式:
1 状态行 200 表示成功, 404 表示请求的资源不存在
2 消息报头 健值对
3 空行 \r\nz
4 响应正文
使用epoll模型作为web服务器:
<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};
//getenv是函数名,从环境中取字符串,获取环境变量的值
sprintf(path, "%s/%s", getenv("HOME"), "webpath");
chdir(path);
//创建socket--设置端口复用 -- bind
//该函数 是自己写的 在 "wrap.c"中
int lfd = tcp4bind(9999, NULL);
//设置监听 "wrap.c"
Listen(lfd, 128);
//创建epoll树
int epfd = epoll_create(1024);
if(epfd < 0){
perror("epoll_create error");
close(lfd);
return -1;
}
//将监听文件描述符上树
struct epoll_event ev;
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
int i;
int cfd;
int sockfd;
int nready;
struct epoll_event events[1024];
while(1){
//等待事件发生
nready = epoll_wait(epfd, events, 1024, -1);
if(nready < 0){
if(error == EINTR){
//阻塞函数,可以被信号打断, 不应该退出进程
continue;
}
break;
}
for(i = 0; i < nready; i++){
sockfd = events[i].data.fd;
//有客户端连接请求
if(sockfd == lfd){
//接受新的客户端连接 "wrap.c"
cfd = Accept(lfd, NULL, NULL);
//设置cfd为非阻塞
int flags = fcntl(cfd, f_GETFL);
flags |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flags);
//将新的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-Length:%d\r\n", fileType);
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);
}
}
return 0;
}
int http_request(int cfd, int epfd){
int n;
char buf[1024];
//读取请求行数据,分析出要请求的资源文件名
memset(buf, 0x00, sizeof(buf));
//整行读 "wrap.c"
n = Readline(cfd, buf, sizeof(buf));
if(n <= 0){
printf("read error or client closed, n == [%d]\n", n);
close(cfd);
//将文件描述符下树
epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
return -1;