网页小型服务初步
函数入口
1、设置端口
2、进程执行目录
3、start()
start()
利用epoll多路I/O复用
epoll深度理解
一般而言,由硬件产生的信号需要 CPU 立马做出回应,不然数据可能就丢失了,所以它的优先级很高。
CPU 理应中断掉正在执行的程序,去做出响应;当 CPU 完成对硬件的响应后,再重新执行用户程序。
它和函数调用差不多,只不过函数调用是事先定好位置,而中断的位置由“信号”决定。
中断程序调用
以键盘为例,当用户按下键盘某个按键时,键盘会给 CPU 的中断引脚发出一个高电平,CPU 能够捕获这个信号,然后执行键盘中断程序。
进程阻塞为什么不占用 CPU 资源?
-
操作系统如何知道网络数据对应于哪个 Socket?
-
如何同时监视多个 Socket 的数据?
所谓唤起进程,就是将进程从所有的等待队列中移除,加入到工作队列里面
跑题了:
通过一个轮询处理i/o事件
void *epoll_run(int port)
{
int i = 0;
struct epoll_event all_events[MAXSIZE];
// 创建一个epoll监听树根
int epfd = epoll_create(MAXSIZE);
if (epfd == -1) {
perror("epoll_create error");
exit(1);
}
// 创建lfd,并添加至监听树
int lfd = init_listen_fd(port, epfd);
while (1) {
// 监听节点对应事件
int ret = epoll_wait(epfd, all_events, MAXSIZE, -1);
if (ret == -1) {
perror("epoll_wait error");
exit(1);
}
for (i=0; i<ret; ++i) {
// 只处理读事件, 其他事件默认不处理
struct epoll_event *pev = &all_events[i];
// 不是读事件
if (!(pev->events & EPOLLIN)) {
continue;
}
if (pev->data.fd == lfd) { // 接受连接请求
do_accept(lfd, epfd);
} else { // 读数据
do_read(pev->data.fd, epfd);
}
}
}
}
定义读事件初步理解http协议
void do_read(int cfd, int epfd)
{
// 读 http 请求协议,获取一行,得文件名。
char line[1024] = {0};
int ret, n;
ret = get_line(cfd, line, sizeof(line));
if (ret == 0) {
printf("检测到客户端关闭...\n");
disconnect(cfd, epfd);
} else { // 读到 http 请求协议第一行
// 循环 读取剩余的 http 请求协议数据。
while (1) {
char buf[1024] = {0};
n = get_line(cfd, buf, sizeof(buf));
if (n == -1)
break;
if (buf[0]=='\n')
break;
}
}
// 判断http协议头,拆分 方法、文件名、协议号 -- "GET /hello.c HTTP/1.1"
if (strncasecmp("GET", line, 3) == 0) {
// 拆分, 获取文件名
printf("-----%s\n", line);
char method[16] = {0}, path[256] = {0}, protocol[16]={0};
sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
// 处理 浏览器请求文件
http_request(cfd, path+1);
}
}
处理http请求
void http_request(int cfd, const char *file)
{
// 判断文件是否存在。
struct stat sbuf;
int ret = stat(file, &sbuf);
if (ret == -1) {
// 回发 给 浏览器 404 错误页面
perror("stat error");
exit(1);
}
// 判断文件类型 --- 普通文件。
if (S_ISREG(sbuf.st_mode)) {
// 组织http应答协议,发送。 错误号、错误描述、错误类型、文件大小
//send_http_respond(cfd, 200, "ok", "text/plain; charset=iso-8859-1", sbuf.st_size);
send_http_respond(cfd, 200, "ok", "text/html; charset=iso-8859-1", sbuf.st_size);
//send_http_respond(cfd, 200, "ok", "image/jpeg", sbuf.st_size);
// 打开文件发送文件内容。
send_file(cfd, file);
}
}
回发
void send_http_respond(int cfd, int num, const char*desp, const char *type, long size)
{
char buf[4096] = {0};
sprintf(buf, "%s %d %s\r\n", "HTTP/1.1", num, desp);
sprintf(buf+strlen(buf), "%s%s\r\n", "Content-Type:", type);
sprintf(buf+strlen(buf), "%s%ld\r\n", "Content-Length:", size);
sprintf(buf+strlen(buf), "\r\n");
// printf("写回的http应答:|\n%s|\n", buf);
// 将组织好的 http 应答头,发送给浏览器
int ret = send(cfd, buf, strlen(buf), 0);
if (ret == -1) {
perror("send_http_respond send error");
exit(1);
}
}
发送文件内容
void send_file(int cfd, const char*file)
{
int n = 0;
char buf[4096] = {0};
int fd = open(file, O_RDONLY);
if (fd == -1) {
perror("open error");
exit(1);
}
// 循环从,本地文件读取数据,写入套接字给浏览器
while ((n = read(fd, buf, sizeof(buf))) > 0) {
send(cfd, buf, n, 0);
}
close(fd);
}
处理连接请求
void do_accept(int lfd, int epfd)
{
struct sockaddr_in clt_addr;
socklen_t clt_addr_len = sizeof(clt_addr);
int cfd = accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);
if (cfd == -1) {
perror("accept error");
exit(1);
}
// 打印客户端IP+port
char client_ip[64] = {0};
printf("New Client IP: %s, Port: %d, cfd = %d\n",
inet_ntop(AF_INET, &clt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)),
ntohs(clt_addr.sin_port), cfd);
// 设置 cfd 非阻塞
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
// 将新节点cfd 挂到 epoll 监听树上
struct epoll_event ev;
ev.data.fd = cfd;
// 边沿非阻塞模式
ev.events = EPOLLIN | EPOLLET;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
if (ret == -1) {
perror("epoll_ctl add cfd error");
exit(1);
}
}