1.基于reactor模型实现网络IO底层通信
网络通信代码见上篇:Linux网络:reactor模型封装epoll:学习笔记2-CSDN博客。
2.Webserver功能
- 接受来自网络端的连接请求并解析;
- 对请求做响应,处理相应的请求业务,回发数据给网络端。
3.Webserver代码
server端代码:
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include"server.h"
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
int init_server(unsigned short port);
int accept_connect(int fd);
int recv_cb(int fd);
int send_cb(int fd);
int set_event(int fd ,int event,int tag);
int epfd =0;
int opt=-1;
struct conn_item connlist[1048576]={0};
int main(int argc,char *argv[]){
int nport=20;
unsigned short port = 2048;
epfd =epoll_create(1);
for(int i=0;i<nport;i++){
int sockfd=init_server(port+i);//一个端口只能绑定一个客户端?
connlist[sockfd].fd=sockfd;
connlist[sockfd].recv_t.accept_callback=accept_connect;
set_event(sockfd, EPOLLIN, 1);
}
if(argc!=0) opt=atoi(argv[1]);
//printf("opt : %d",opt);
//gettimeofday(&nowtimes, NULL);
struct epoll_event events[1024] = {0};
while(1){
int nready =epoll_wait(epfd,events,1024,-1);
for(int i=0;i<nready;i++){
int connfd=events[i].data.fd;
if(events[i].events&EPOLLIN){
int count =connlist[connfd].recv_t.recv_callback(connfd);
//printf("recv count: %d <-- buffer: %s\n", count, connlist[connfd].rbuffer);
}
if(events[i].events&EPOLLOUT){
//printf("send --> buffer: %s\n", connlist[connfd].wbuffer);
int count =connlist[connfd].send_callback(connfd);
//printf("send --> buffer: %s\n", connlist[connfd].wbuffer);
}
}
}
return 0;
}
int init_server(unsigned short port){
int sockfd =socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(struct sockaddr_in));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
serveraddr.sin_port=htons(port);
if(-1==bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(struct sockaddr))){
perror("bind error");
return -1;
}
listen(sockfd,10);
return sockfd;
}
int set_event(int fd ,int event,int tag){
if(tag){
struct epoll_event ev;
ev.events=event;
ev.data.fd=fd;
epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);
}else{
struct epoll_event ev;
ev.events=event;
ev.data.fd=fd;
epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev);//not del?
}
return 0;
}
int event_register(int fd ,int event){
if(fd <0)return -1;
connlist[fd].fd=fd;
memset(connlist[fd].rbuffer, 0, BUFFER_LENGTH);
connlist[fd].rlen = 0;
memset(connlist[fd].wbuffer, 0, BUFFER_LENGTH);
connlist[fd].wlen = 0;
connlist[fd].recv_t.recv_callback=recv_cb;
connlist[fd].send_callback=send_cb;
set_event( fd, event, 1);
return 0;
}
int accept_connect(int fd){
struct sockaddr_in clientaddr;
socklen_t len =sizeof(clientaddr);
int clientfd=accept(fd,(struct sockaddr *)&clientaddr,&len);
if(clientfd<0){
// printf("accept errno: %d --> %s\n", errno, strerror(errno));
return -1;
}
event_register(clientfd,EPOLLIN);
// if ((clientfd % 1000) == 999) {
// struct timeval tv_cur;
// gettimeofday(&tv_cur, NULL);
// int time_used = TIME_SUB_MS(tv_cur, nowtimes);
// memcpy(&nowtimes, &tv_cur, sizeof(struct timeval));
// //printf("fd : %d, time_used: %d\n", clientfd, time_used);
// }
return 0;
}
int recv_cb(int fd){//需要注意的是每个连接端口对应一个fd
memset(connlist[fd].rbuffer,0,BUFFER_LENGTH);
int count=recv(fd,connlist[fd].rbuffer,BUFFER_LENGTH,0);
if(count ==0){
//printf("client disconnect: %d\n", fd);
//printf("disconnect\n");
close(fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
return 0;
}else if(count<0){
//printf("count: %d, errno: %d, %s\n", count, errno, strerror(errno));
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
return 0;
}
connlist[fd].rlen = count;
//memcpy(connlist[fd].wbuffer, connlist[fd].rbuffer, count);
connlist[fd].wlen = connlist[fd].rlen;
#if ENABLE_HTTP_RESPONSE
http_request(&connlist[fd]);
#elif
memcpy(connlist[fd].wbuffer, connlist[fd].rbuffer, connlist[fd].rlen);
#endif
set_event(fd, EPOLLOUT, 0);
return count;
}
int send_cb(int fd){
int count=0;
#if ENABLE_HTTP_RESPONSE
count=http_response(&connlist[fd],opt);
#elif
char *buffer = connlist[fd].wbuffer;
int wlen= connlist[fd].wlen;
int count = send(fd, buffer, wlen, 0);
#endif
#if ENABLE_HTTP_RESPONSE
if (connlist[fd].states == 1) {
//printf("SEND: %s\n", connlist[fd].wbuffer);
count = send(fd, connlist[fd].wbuffer, connlist[fd].wlen, 0);
set_event(fd, EPOLLOUT, 0);
} else if (connlist[fd].states == 2) {
set_event(fd, EPOLLOUT, 0);
} else if (connlist[fd].states == 0) {
if (connlist[fd].wlen != 0) {
count = send(fd, connlist[fd].wbuffer, connlist[fd].wlen, 0);
}
set_event(fd, EPOLLIN, 0);
}
#else
if (connlist[fd].wlen != 0)
count = send(fd, &connlist[fd].wbuffer, connlist[fd].wlen, 0);
set_event(fd, EPOLLIN, 0);
#endif
return count;
}
业务端代码:
//头文件
#ifndef __SERVER_H__
#define __SERVER_H__
#define ENABLE_HTTP_RESPONSE 1
#define BUFFER_LENGTH 1024
//struct timeval nowtimes;
typedef int (*RCALLBACK)(int fd);
struct conn_item{
int fd;
char rbuffer[BUFFER_LENGTH];
int rlen;
char wbuffer[BUFFER_LENGTH];
int wlen;
union{
RCALLBACK accept_callback;
RCALLBACK recv_callback;
}recv_t;
RCALLBACK send_callback;
int states;//状态机控制回调函数的调用0接请求,1接收数据,2接收完成
};
int http_request(struct conn_item *conn);
int http_response(struct conn_item *conn,int opt);
#endif
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/sendfile.h>
#include <errno.h>
#include"server.h"
typedef struct response_s{
int str ;
int html;
int png;
}response_t;
//response_t rpack={1,0,0};//不同的业务的处理,
int http_request(struct conn_item *conn){
//printf("request: %s\n", conn->rbuffer);
memset(conn->wbuffer,0,BUFFER_LENGTH);
conn->wlen=0;
conn->states=0;
return 0;
}
int del_string(struct conn_item *conn){
conn->wlen = sprintf(conn->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: 82\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n"
"<html><head><title>0voice.Lugy</title></head><body><h1>hello friends !</h1></body></html>\r\n\r\n");
return conn->wlen;
}
int del_html(struct conn_item *conn){
int filefd = open("index.html", O_RDONLY);
struct stat file_buf;
fstat(filefd, &file_buf);
if(conn->states==0){
conn->wlen = sprintf(conn->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %ld\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
file_buf.st_size);
conn->states=1;
}else if(conn->states==1){
int res=sendfile(conn->fd,filefd,NULL,file_buf.st_size);
if(res==-1){
printf("sendfile error :%d",errno);
}
conn->states=2;
}else if( conn->states==2){
conn->wlen = 0;
memset(conn->wbuffer, 0, BUFFER_LENGTH);
conn->states = 0;
}
close(filefd);
return conn->wlen;;
}
int del_png(struct conn_item *conn){
int filefd = open("wrk.png", O_RDONLY);
struct stat file_buf;
fstat(filefd, &file_buf);
if(conn->states==0){
conn->wlen = sprintf(conn->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Content-Type: image/png\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %ld\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
file_buf.st_size);
conn->states=1;
}else if(conn->states==1){
int res=sendfile(conn->fd,filefd,NULL,file_buf.st_size);
if(res==-1){
printf("sendfile error :%d",errno);
}
conn->states=2;
}else if( conn->states==2){
conn->wlen = 0;
memset(conn->wbuffer, 0, BUFFER_LENGTH);
conn->states = 0;
}
return conn->wlen;
}
int http_response(struct conn_item *conn,int opt){
//printf("begin :response !!!!!!!!!!!");
int count =0;
switch (opt)
{
case 0:
count =del_string(conn);
break;
case 1:
count =del_html(conn);
break;
case 2:
count =del_png(conn);
break;
default:
printf("http_response : %d\n",opt);
break;
}
#if 0
if(rpack.str==1){
}else if(rpack.html==1){
}else{
}
#endif
#if 0
int filefd = open("index.html", O_RDONLY);
struct stat file_buf;
fstat(filefd, &file_buf);
conn->wlen = sprintf(conn->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %ld\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
file_buf.st_size);
int count = read(filefd, conn->wbuffer + conn->wlen, BUFFER_LENGTH - conn->wlen);
conn->wlen += count;
close(filefd);
#endif
return count;
}
专属学习链接:https://xxetb.xetslk.com/s/4cnbDc
4.Http请求测试
在进行http请求测试时需要注意的是,当收发的数据比较大的时候,一次用户态send()和recv()无法将所有需要的信息收发完成时,可以通过额外的引入一个状态机来进行状态的迁移控制,从而达到将所需的信息全部接收的效果,但需要仔细的思考状态在什么情况下需要改变,以达到想要的效果。由于对http的解析不太熟悉,本应根据网络的请求处理相应的业务,此篇的response回应请求部分的逻辑是预先已知请求,然后对应编写的回发,之后会再进行代码逻辑的补充,感兴趣的朋友也可相互探讨。
测试结果如下:
5.代码qps的测试结果
6.reactor模型的总结
可以发现代码网络端的更改很少,几乎只用考虑业务逻辑代码的编写,基本实现内核处理逻辑和业务处理的分离,充分的体现了reactor的优势。