Repository: Buerzhu/TinyWeb
Branch: master
Commit: 62e7559a1ebd
Files: 18
Total size: 57.0 KB
Directory structure:
gitextract_wdce6jin/
├── Client/
│ ├── Codes/
│ │ ├── README.md
│ │ ├── client.pro
│ │ └── main.cpp
│ └── Debug/
│ ├── Makefile
│ ├── client
│ └── main.o
├── README.md
└── Server/
├── Codes/
│ ├── README.md
│ ├── http_conn.h
│ ├── main.cpp
│ ├── server.pro
│ ├── web_function.h
│ └── web_thread.h
└── Debug/
├── Makefile
├── cgi-bin/
│ └── adder
├── html/
│ └── index.html
├── main.o
└── server
================================================
FILE CONTENTS
================================================
================================================
FILE: Client/Codes/README.md
================================================
一个针对服务器的压力测试程序,可自定义请求客户的数目
================================================
FILE: Client/Codes/client.pro
================================================
QT += core
QT -= gui
TARGET = client
CONFIG += console
QMAKE_CXXFLAGS += -std=c++11
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp
================================================
FILE: Client/Codes/main.cpp
================================================
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
static const char* request = "GET http://localhost/html/index.html HTTP/1.1\r\nConnection: keep-alive\r\n\r\nxxxxxxxxxxxx";
int setnonblocking( int fd )
{
int old_option = fcntl( fd, F_GETFL );
int new_option = old_option | O_NONBLOCK;
fcntl( fd, F_SETFL, new_option );
return old_option;
}
void addfd( int epoll_fd, int fd )
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLOUT | EPOLLET | EPOLLERR;
epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &event );
setnonblocking( fd );
}
bool write_nbytes( int sockfd, const char* buffer, int len )
{
int bytes_write = 0;
printf( "write out %d bytes to socket %d\n", len, sockfd );
while( 1 )
{
bytes_write = send( sockfd, buffer, len, 0 );
if ( bytes_write == -1 )
{
return false;
}
else if ( bytes_write == 0 )
{
return false;
}
len -= bytes_write;
buffer = buffer + bytes_write;
if ( len <= 0 )
{
return true;
}
}
}
bool read_once( int sockfd, char* buffer, int len )
{
int bytes_read = 0;
memset( buffer, '\0', len );
bytes_read = recv( sockfd, buffer, len, 0 );
if ( bytes_read == -1 )
{
return false;
}
else if ( bytes_read == 0 )
{
return false;
}
printf( "read in %d bytes from socket %d with content: %s\n", bytes_read, sockfd, buffer );
return true;
}
void start_conn( int epoll_fd, int num, const char* ip, int port )
{
int ret = 0;
struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
for ( int i = 0; i < num; ++i )
{
sleep( 0.01 );
int sockfd = socket( PF_INET, SOCK_STREAM, 0 );
printf( "create 1 sock\n" );
if( sockfd < 0 )
{
continue;
}
if ( connect( sockfd, ( struct sockaddr* )&address, sizeof( address ) ) == 0 )
{
printf( "build connection %d\n", i );
addfd( epoll_fd, sockfd );
}
}
}
void close_conn( int epoll_fd, int sockfd )
{
epoll_ctl( epoll_fd, EPOLL_CTL_DEL, sockfd, 0 );
close( sockfd );
}
int main( int argc, char* argv[] )
{
if(argc<4)
{
fprintf(stderr,"usage: %s server_ip server_port client_num ",argv[0]);
exit(0);
}
int epoll_fd = epoll_create( 100 );
start_conn( epoll_fd, atoi( argv[ 3 ] ), argv[1], atoi( argv[2] ) );
epoll_event events[ 10000 ];
char buffer[ 2048 ];
while ( 1 )
{
int fds = epoll_wait( epoll_fd, events, 10000, 2000 );
for ( int i = 0; i < fds; i++ )
{
int sockfd = events[i].data.fd;
if ( events[i].events & EPOLLIN )
{
if ( ! read_once( sockfd, buffer, 2048 ) )
{
close_conn( epoll_fd, sockfd );
}
struct epoll_event event;
event.events = EPOLLOUT | EPOLLET | EPOLLERR;
event.data.fd = sockfd;
epoll_ctl( epoll_fd, EPOLL_CTL_MOD, sockfd, &event );
}
else if( events[i].events & EPOLLOUT )
{
if ( ! write_nbytes( sockfd, request, strlen( request ) ) )
{
close_conn( epoll_fd, sockfd );
}
struct epoll_event event;
event.events = EPOLLIN | EPOLLET | EPOLLERR;
event.data.fd = sockfd;
epoll_ctl( epoll_fd, EPOLL_CTL_MOD, sockfd, &event );
}
else if( events[i].events & EPOLLERR )
{
close_conn( epoll_fd, sockfd );
}
}
}
}
================================================
FILE: Client/Debug/Makefile
================================================
#############################################################################
# Makefile for building: client
# Generated by qmake (2.01a) (Qt 4.8.7) on: ?? 2? 27 15:14:57 2020
# Project: ../Codes/client.pro
# Template: app
# Command: /usr/lib/x86_64-linux-gnu/qt4/bin/qmake -spec /usr/share/qt4/mkspecs/linux-g++-64 CONFIG+=debug -o Makefile ../Codes/client.pro
#############################################################################
####### Compiler, tools and options
CC = gcc
CXX = g++
DEFINES = -DQT_CORE_LIB -DQT_SHARED
CFLAGS = -m64 -pipe -g -Wall -W -D_REENTRANT $(DEFINES)
CXXFLAGS = -m64 -pipe -std=c++11 -g -Wall -W -D_REENTRANT $(DEFINES)
INCPATH = -I/usr/share/qt4/mkspecs/linux-g++-64 -I../Codes -I/usr/include/qt4/QtCore -I/usr/include/qt4 -I. -I../Codes -I.
LINK = g++
LFLAGS = -m64
LIBS = $(SUBLIBS) -L/usr/lib/x86_64-linux-gnu -lQtCore -lpthread
AR = ar cqs
RANLIB =
QMAKE = /usr/lib/x86_64-linux-gnu/qt4/bin/qmake
TAR = tar -cf
COMPRESS = gzip -9f
COPY = cp -f
SED = sed
COPY_FILE = $(COPY)
COPY_DIR = $(COPY) -r
STRIP = strip
INSTALL_FILE = install -m 644 -p
INSTALL_DIR = $(COPY_DIR)
INSTALL_PROGRAM = install -m 755 -p
DEL_FILE = rm -f
SYMLINK = ln -f -s
DEL_DIR = rmdir
MOVE = mv -f
CHK_DIR_EXISTS= test -d
MKDIR = mkdir -p
####### Output directory
OBJECTS_DIR = ./
####### Files
SOURCES = ../Codes/main.cpp
OBJECTS = main.o
DIST = /usr/share/qt4/mkspecs/common/unix.conf \
/usr/share/qt4/mkspecs/common/linux.conf \
/usr/share/qt4/mkspecs/common/gcc-base.conf \
/usr/share/qt4/mkspecs/common/gcc-base-unix.conf \
/usr/share/qt4/mkspecs/common/g++-base.conf \
/usr/share/qt4/mkspecs/common/g++-unix.conf \
/usr/share/qt4/mkspecs/qconfig.pri \
/usr/share/qt4/mkspecs/features/qt_functions.prf \
/usr/share/qt4/mkspecs/features/qt_config.prf \
/usr/share/qt4/mkspecs/features/exclusive_builds.prf \
/usr/share/qt4/mkspecs/features/default_pre.prf \
/usr/share/qt4/mkspecs/features/debug.prf \
/usr/share/qt4/mkspecs/features/default_post.prf \
/usr/share/qt4/mkspecs/features/shared.prf \
/usr/share/qt4/mkspecs/features/unix/gdb_dwarf_index.prf \
/usr/share/qt4/mkspecs/features/warn_on.prf \
/usr/share/qt4/mkspecs/features/qt.prf \
/usr/share/qt4/mkspecs/features/unix/thread.prf \
/usr/share/qt4/mkspecs/features/moc.prf \
/usr/share/qt4/mkspecs/features/resources.prf \
/usr/share/qt4/mkspecs/features/uic.prf \
/usr/share/qt4/mkspecs/features/yacc.prf \
/usr/share/qt4/mkspecs/features/lex.prf \
/usr/share/qt4/mkspecs/features/include_source_dir.prf \
../Codes/client.pro
QMAKE_TARGET = client
DESTDIR =
TARGET = client
first: all
####### Implicit rules
.SUFFIXES: .o .c .cpp .cc .cxx .C
.cpp.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.cc.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.cxx.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.C.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.c.o:
$(CC) -c $(CFLAGS) $(INCPATH) -o "$@" "$<"
####### Build rules
all: Makefile $(TARGET)
$(TARGET): $(OBJECTS)
$(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJCOMP) $(LIBS)
{ test -n "$(DESTDIR)" && DESTDIR="$(DESTDIR)" || DESTDIR=.; } && test $$(gdb --version | sed -e 's,[^0-9][^0-9]*\([0-9]\)\.\([0-9]\).*,\1\2,;q') -gt 72 && gdb --nx --batch --quiet -ex 'set confirm off' -ex "save gdb-index $$DESTDIR" -ex quit '$(TARGET)' && test -f $(TARGET).gdb-index && objcopy --add-section '.gdb_index=$(TARGET).gdb-index' --set-section-flags '.gdb_index=readonly' '$(TARGET)' '$(TARGET)' && rm -f $(TARGET).gdb-index || true
Makefile: ../Codes/client.pro /usr/share/qt4/mkspecs/linux-g++-64/qmake.conf /usr/share/qt4/mkspecs/common/unix.conf \
/usr/share/qt4/mkspecs/common/linux.conf \
/usr/share/qt4/mkspecs/common/gcc-base.conf \
/usr/share/qt4/mkspecs/common/gcc-base-unix.conf \
/usr/share/qt4/mkspecs/common/g++-base.conf \
/usr/share/qt4/mkspecs/common/g++-unix.conf \
/usr/share/qt4/mkspecs/qconfig.pri \
/usr/share/qt4/mkspecs/features/qt_functions.prf \
/usr/share/qt4/mkspecs/features/qt_config.prf \
/usr/share/qt4/mkspecs/features/exclusive_builds.prf \
/usr/share/qt4/mkspecs/features/default_pre.prf \
/usr/share/qt4/mkspecs/features/debug.prf \
/usr/share/qt4/mkspecs/features/default_post.prf \
/usr/share/qt4/mkspecs/features/shared.prf \
/usr/share/qt4/mkspecs/features/unix/gdb_dwarf_index.prf \
/usr/share/qt4/mkspecs/features/warn_on.prf \
/usr/share/qt4/mkspecs/features/qt.prf \
/usr/share/qt4/mkspecs/features/unix/thread.prf \
/usr/share/qt4/mkspecs/features/moc.prf \
/usr/share/qt4/mkspecs/features/resources.prf \
/usr/share/qt4/mkspecs/features/uic.prf \
/usr/share/qt4/mkspecs/features/yacc.prf \
/usr/share/qt4/mkspecs/features/lex.prf \
/usr/share/qt4/mkspecs/features/include_source_dir.prf \
/usr/lib/x86_64-linux-gnu/libQtCore.prl
$(QMAKE) -spec /usr/share/qt4/mkspecs/linux-g++-64 CONFIG+=debug -o Makefile ../Codes/client.pro
/usr/share/qt4/mkspecs/common/unix.conf:
/usr/share/qt4/mkspecs/common/linux.conf:
/usr/share/qt4/mkspecs/common/gcc-base.conf:
/usr/share/qt4/mkspecs/common/gcc-base-unix.conf:
/usr/share/qt4/mkspecs/common/g++-base.conf:
/usr/share/qt4/mkspecs/common/g++-unix.conf:
/usr/share/qt4/mkspecs/qconfig.pri:
/usr/share/qt4/mkspecs/features/qt_functions.prf:
/usr/share/qt4/mkspecs/features/qt_config.prf:
/usr/share/qt4/mkspecs/features/exclusive_builds.prf:
/usr/share/qt4/mkspecs/features/default_pre.prf:
/usr/share/qt4/mkspecs/features/debug.prf:
/usr/share/qt4/mkspecs/features/default_post.prf:
/usr/share/qt4/mkspecs/features/shared.prf:
/usr/share/qt4/mkspecs/features/unix/gdb_dwarf_index.prf:
/usr/share/qt4/mkspecs/features/warn_on.prf:
/usr/share/qt4/mkspecs/features/qt.prf:
/usr/share/qt4/mkspecs/features/unix/thread.prf:
/usr/share/qt4/mkspecs/features/moc.prf:
/usr/share/qt4/mkspecs/features/resources.prf:
/usr/share/qt4/mkspecs/features/uic.prf:
/usr/share/qt4/mkspecs/features/yacc.prf:
/usr/share/qt4/mkspecs/features/lex.prf:
/usr/share/qt4/mkspecs/features/include_source_dir.prf:
/usr/lib/x86_64-linux-gnu/libQtCore.prl:
qmake: FORCE
@$(QMAKE) -spec /usr/share/qt4/mkspecs/linux-g++-64 CONFIG+=debug -o Makefile ../Codes/client.pro
dist:
@$(CHK_DIR_EXISTS) .tmp/client1.0.0 || $(MKDIR) .tmp/client1.0.0
$(COPY_FILE) --parents $(SOURCES) $(DIST) .tmp/client1.0.0/ && $(COPY_FILE) --parents ../Codes/main.cpp .tmp/client1.0.0/ && (cd `dirname .tmp/client1.0.0` && $(TAR) client1.0.0.tar client1.0.0 && $(COMPRESS) client1.0.0.tar) && $(MOVE) `dirname .tmp/client1.0.0`/client1.0.0.tar.gz . && $(DEL_FILE) -r .tmp/client1.0.0
clean:compiler_clean
-$(DEL_FILE) $(OBJECTS)
-$(DEL_FILE) *~ core *.core
####### Sub-libraries
distclean: clean
-$(DEL_FILE) $(TARGET)
-$(DEL_FILE) Makefile
check: first
mocclean: compiler_moc_header_clean compiler_moc_source_clean
mocables: compiler_moc_header_make_all compiler_moc_source_make_all
compiler_moc_header_make_all:
compiler_moc_header_clean:
compiler_rcc_make_all:
compiler_rcc_clean:
compiler_image_collection_make_all: qmake_image_collection.cpp
compiler_image_collection_clean:
-$(DEL_FILE) qmake_image_collection.cpp
compiler_moc_source_make_all:
compiler_moc_source_clean:
compiler_uic_make_all:
compiler_uic_clean:
compiler_yacc_decl_make_all:
compiler_yacc_decl_clean:
compiler_yacc_impl_make_all:
compiler_yacc_impl_clean:
compiler_lex_make_all:
compiler_lex_clean:
compiler_clean:
####### Compile
main.o: ../Codes/main.cpp
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o main.o ../Codes/main.cpp
####### Install
install: FORCE
uninstall: FORCE
FORCE:
================================================
FILE: README.md
================================================
* **前言**
本项目是基于C++线程池的轻量级Web并发服务器,事件处理模式采用Reactor模式,主线程只负责监听文件描述符是否有事件发生,读写数据、接收新的连接、以及处理客户请求均在工作线程中实现;使用半同步/半异步模式,每个线程(主线程和工作线程)都通过一个epoll维护自己的事件循环,它们各自独立地监听不同事件。使用C++ 11的atomic原子变量来同步线程访问从而避免同步错误;使用STL的优先队列作为定时器容器来回收非活动长连接。本项目的具体设计思路可以参照本人的知乎文章[C++网络编程入门:轻量级Web并发服务器开发](https://zhuanlan.zhihu.com/p/109905285),纯属抛砖引玉,欢迎大佬们批评指正。
* **运行环境**
(1).系统:Ubuntu16.04;
(2).语言:C++ 11及以上版本;
* **支持功能**
(1).支持Get请求;
(2).支持长连接/短连接;
(3).支持ipv4/ipv6;(4).支持tcp;(5).支持请求静态内容(6).支持并发请求
* **开始运行**
(1).下载程序;
`git clone git@github.com:Buerzhu/TinyWeb.git`
(2).打开新终端,指定ip地址和端口运行服务器程序:
`cd ~/TinyWeb/Server/Debug`
`./server 127.0.0.1 12345`
(3).打开新终端,指定ip地址、端口、客户连接数运行压力测试程序:
`cd ~/TinyWeb/Client/Debug`
`./client 127.0.0.1 12345 1000`
================================================
FILE: Server/Codes/README.md
================================================
* **web_thread.h**
(1). 定义了webthread类;
(2). 该类是主线程与子线程通信的媒介,本程序对于每个子线程建立对应的wedthread全局对象,主线程通过该对象来与子线程通信,子线程通过该对象接收来自主线程的消息,并运行该对象的work()函数来处理主线程消息和用户的http请求。
* **http_conn.h**
(1).定义了http_conn类和util_timer类;
(2).http_conn类是用于处理http请求和作出http应答的一个类,本程序对于每个新连接的用户都分配一个http_conn对象用于处理http请求;
(3).util_timer类是定时器类,本程序对于每个新连接的用户都分配一个定时器对象,并设置该定时器的超时时间,如果长连接用户在超时时间内都处于非活动状态则关闭用户连接,如果用户在超时时间到达之前重新活动则延长超时时间。
* **web_function.h**
(1).该文件用于定义全局变量和常量,包括线程数、当前总用户数、读写缓冲区大小等,如果要支持更多的并发用户请求,应该修改该文件中定义的这些常量;
(2).该文件还提供了一些通用的基本功能函数,例如显示当前时区时间的函数,显示用户ip地址的函数等;
* **main.cpp**
(1). 程序运行入口
(2). 主线程只负责监听文件描述符是否有事件发生而不处理面向用户的业务逻辑(Reactor模式);
* **server.pro**
(1). 该文件是qtcreator关于qmake的工程文件,qt通过该文件来生成Makefile文件。
================================================
FILE: Server/Codes/http_conn.h
================================================
#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <errno.h>
#include<time.h>
#include<queue>
#include<stack>
#include"web_function.h"
using namespace std;
const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from this server.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The requested file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requested file.\n";
char* doc_root=new char[256];
class util_timer;
class http_conn
{
public:
util_timer* timer;
enum METHOD { GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH };//http请求方法,本程序只支持get
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };//解析客户请求时当前所处的状态
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };//服务器处理http请求的可能结果
enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };//每一行的读取状态
public:
http_conn(){}
~http_conn(){}
public:
void init(int epollfd, int sockfd, const sockaddr_in& addr ,util_timer* _timer,int index,stack<int>* _st);//初始化新接受的连接
void close_conn( bool real_close = true );//关闭连接
void process();//处理客户请求
bool read();//非阻塞读操作
bool write();//非阻塞写操作
private:
void init();//初始化连接
HTTP_CODE process_read();//解析http请求
bool process_write( HTTP_CODE ret );//填充http应答
//下面这一组函数被process_read()函数调用以分析http请求
HTTP_CODE parse_request_line( char* text );
HTTP_CODE parse_headers( char* text );
HTTP_CODE parse_content( char* text );
HTTP_CODE do_request();
char* get_line() { return m_read_buf + m_start_line; }
LINE_STATUS parse_line();
//下面这一组函数被process_write()函数调用以填充http应答
void unmap();
bool add_response( const char* format, ... );
bool add_content( const char* content );
bool add_status_line( int status, const char* title );
bool add_headers( int content_length );
bool add_content_length( int content_length );
bool add_linger();
bool add_blank_line();
public:
int m_epollfd;
private:
int m_sockfd;
sockaddr_in m_address;
char m_read_buf[ READ_BUFFER_SIZE ];
int m_read_idx;
int m_checked_idx;
int m_start_line;
char m_write_buf[ WRITE_BUFFER_SIZE ];
int m_write_idx;
CHECK_STATE m_check_state;
METHOD m_method;
char m_real_file[ FILENAME_LEN ];
char* m_url;
char* m_version;
char* m_host;
int m_content_length;
bool m_linger;
char* m_file_address;
struct stat m_file_stat;
struct iovec m_iv[2];
int m_iv_count;
int user_index;
stack<int>* index_free;
};
class util_timer
{
public:
util_timer(time_t t):expire(t),free(true) {}
time_t expire;//超时时间
bool free;//是否被某个用户占用
int sockfd;//关联的连接描述符
void timed_event(){client->close_conn();}//超时操作,关闭关联的连接
http_conn* client;//关联的用户地址
};
void http_conn::close_conn( bool real_close )
{
if( real_close && ( m_sockfd != -1 ) )
{
removefd( m_epollfd, m_sockfd );
timer->free=true;
index_free->push(user_index);
m_sockfd = -1;
show_sys_time();
printf("close connection: ");
show_addr(m_address);
user_count--;//C++11
printf("user_online: %d\n",(int)user_count);//C++11
}
}
void http_conn::init( int epollfd,int sockfd, const sockaddr_in& addr,util_timer* _timer,int index,stack<int>* st)
{
m_epollfd=epollfd;
m_sockfd = sockfd;
m_address = addr;
timer=_timer;
timer->free=false;
timer->sockfd=sockfd;
user_index=index;
index_free=st;
int error = 0;
socklen_t len = sizeof( error );
getsockopt( m_sockfd, SOL_SOCKET, SO_ERROR, &error, &len );
// int reuse = 1;
// setsockopt( m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ) );
addfd( m_epollfd, sockfd, true );
init();
show_sys_time();
printf("new connection: ");
show_addr(addr);
user_count++;//C++11
printf("user_count: %d\n",(int)user_count);
if(user_count.load()>max_user_online.load())
{
max_user_online=user_count.load();
}
}
void http_conn::init()
{
m_check_state = CHECK_STATE_REQUESTLINE;
m_linger = false;
m_method = GET;
m_url = 0;
m_version = 0;
m_content_length = 0;
m_host = 0;
m_start_line = 0;
m_checked_idx = 0;
m_read_idx = 0;
m_write_idx = 0;
memset( m_read_buf, '\0', READ_BUFFER_SIZE );
memset( m_write_buf, '\0', WRITE_BUFFER_SIZE );
memset( m_real_file, '\0', FILENAME_LEN );
}
http_conn::LINE_STATUS http_conn::parse_line()
{
char temp;
for ( ; m_checked_idx < m_read_idx; ++m_checked_idx )
{
temp = m_read_buf[ m_checked_idx ];
if ( temp == '\r' )
{
if ( ( m_checked_idx + 1 ) == m_read_idx )
{
return LINE_OPEN;
}
else if ( m_read_buf[ m_checked_idx + 1 ] == '\n' )
{
m_read_buf[ m_checked_idx++ ] = '\0';
m_read_buf[ m_checked_idx++ ] = '\0';
return LINE_OK;
}
return LINE_BAD;
}
else if( temp == '\n' )
{
if( ( m_checked_idx > 1 ) && ( m_read_buf[ m_checked_idx - 1 ] == '\r' ) )
{
m_read_buf[ m_checked_idx-1 ] = '\0';
m_read_buf[ m_checked_idx++ ] = '\0';
return LINE_OK;
}
return LINE_BAD;
}
}
return LINE_OPEN;
}
bool http_conn::read()
{
if( m_read_idx >= READ_BUFFER_SIZE )
{
return false;
}
int bytes_read = 0;
while( true )
{
bytes_read = recv( m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0 );
if ( bytes_read == -1 )
{
if( errno == EAGAIN || errno == EWOULDBLOCK )
{
break;
}
return false;
}
else if ( bytes_read == 0 )
{
return false;
}
m_read_idx += bytes_read;
}
return true;
}
http_conn::HTTP_CODE http_conn::parse_request_line( char* text )
{
m_url = strpbrk( text, " \t" );
if ( ! m_url )
{
return BAD_REQUEST;
}
*m_url++ = '\0';
char* method = text;
if ( strcasecmp( method, "GET" ) == 0 )
{
m_method = GET;
}
else
{
return BAD_REQUEST;
}
m_url += strspn( m_url, " \t" );
m_version = strpbrk( m_url, " \t" );
if ( ! m_version )
{
return BAD_REQUEST;
}
*m_version++ = '\0';
m_version += strspn( m_version, " \t" );
if ( strcasecmp( m_version, "HTTP/1.1" ) != 0 )
{
return BAD_REQUEST;
}
if ( strncasecmp( m_url, "http://", 7 ) == 0 )
{
m_url += 7;
m_url = strchr( m_url, '/' );
}
if ( ! m_url || m_url[ 0 ] != '/' )
{
return BAD_REQUEST;
}
m_check_state = CHECK_STATE_HEADER;
return NO_REQUEST;
}
http_conn::HTTP_CODE http_conn::parse_headers( char* text )
{
if( text[ 0 ] == '\0' )
{
if ( m_method == HEAD )
{
return GET_REQUEST;
}
if ( m_content_length != 0 )
{
m_check_state = CHECK_STATE_CONTENT;
return NO_REQUEST;
}
return GET_REQUEST;
}
else if ( strncasecmp( text, "Connection:", 11 ) == 0 )
{
text += 11;
text += strspn( text, " \t" );
if ( strcasecmp( text, "keep-alive" ) == 0 )
{
m_linger = true;
}
}
else if ( strncasecmp( text, "Content-Length:", 15 ) == 0 )
{
text += 15;
text += strspn( text, " \t" );
m_content_length = atol( text );
}
else if ( strncasecmp( text, "Host:", 5 ) == 0 )
{
text += 5;
text += strspn( text, " \t" );
m_host = text;
}
else
{
printf( "oop! unknow header %s\n", text );
}
return NO_REQUEST;
}
http_conn::HTTP_CODE http_conn::parse_content( char* text )
{
if ( m_read_idx >= ( m_content_length + m_checked_idx ) )
{
text[ m_content_length ] = '\0';
return GET_REQUEST;
}
return NO_REQUEST;
}
http_conn::HTTP_CODE http_conn::process_read()
{
LINE_STATUS line_status = LINE_OK;
HTTP_CODE ret = NO_REQUEST;
char* text = 0;
printf("get data from: ");
show_addr(m_address);
while ( ( ( m_check_state == CHECK_STATE_CONTENT ) && ( line_status == LINE_OK ) )
|| ( ( line_status = parse_line() ) == LINE_OK ) )
{
text = get_line();
m_start_line = m_checked_idx;
printf( "got 1 http line: %s\n", text );
switch ( m_check_state )
{
case CHECK_STATE_REQUESTLINE:
{
ret = parse_request_line( text );
if ( ret == BAD_REQUEST )
{
return BAD_REQUEST;
}
break;
}
case CHECK_STATE_HEADER:
{
ret = parse_headers( text );
if ( ret == BAD_REQUEST )
{
return BAD_REQUEST;
}
else if ( ret == GET_REQUEST )
{
return do_request();
}
break;
}
case CHECK_STATE_CONTENT:
{
ret = parse_content( text );
if ( ret == GET_REQUEST )
{
return do_request();
}
line_status = LINE_OPEN;
break;
}
default:
{
printf("INTERNAL_ERROR.\n");
return INTERNAL_ERROR;
}
}
}
return NO_REQUEST;
}
http_conn::HTTP_CODE http_conn::do_request()
{
getcwd(doc_root,256);
strcpy( m_real_file, doc_root );
int len = strlen( doc_root );
strncpy( m_real_file + len, m_url, FILENAME_LEN - len - 1 );
if ( stat( m_real_file, &m_file_stat ) < 0 )
{
return NO_RESOURCE;
}
if ( ! ( m_file_stat.st_mode & S_IROTH ) )
{
return FORBIDDEN_REQUEST;
}
if ( S_ISDIR( m_file_stat.st_mode ) )
{
return BAD_REQUEST;
}
int fd = open( m_real_file, O_RDONLY );
m_file_address = ( char* )mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
close( fd );
return FILE_REQUEST;
}
void http_conn::unmap()
{
if( m_file_address )
{
munmap( m_file_address, m_file_stat.st_size );
m_file_address = 0;
}
}
bool http_conn::write()
{
int temp = 0;
int bytes_have_send = 0;
int bytes_to_send = m_write_idx;
if ( bytes_to_send == 0 )
{
modfd( m_epollfd, m_sockfd, EPOLLIN );
init();
return true;
}
while( 1 )
{
temp = writev( m_sockfd, m_iv, m_iv_count );
if ( temp <= -1 )
{
if( errno == EAGAIN )
{
modfd( m_epollfd, m_sockfd, EPOLLOUT );
return true;
}
unmap();
return false;
}
bytes_to_send -= temp;
bytes_have_send += temp;
if ( bytes_to_send <= bytes_have_send )
{
unmap();
if( m_linger )
{
init();
modfd( m_epollfd, m_sockfd, EPOLLIN );
return true;
}
else
{
modfd( m_epollfd, m_sockfd, EPOLLIN );
return false;
}
}
}
}
bool http_conn::add_response( const char* format, ... )
{
if( m_write_idx >= WRITE_BUFFER_SIZE )
{
return false;
}
va_list arg_list;
va_start( arg_list, format );
int len = vsnprintf( m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list );
if( len >= ( WRITE_BUFFER_SIZE - 1 - m_write_idx ) )
{
return false;
}
m_write_idx += len;
va_end( arg_list );
return true;
}
bool http_conn::add_status_line( int status, const char* title )
{
return add_response( "%s %d %s\r\n", "HTTP/1.1", status, title );
}
bool http_conn::add_headers( int content_len )
{
add_content_length( content_len );
add_linger();
add_blank_line();
}
bool http_conn::add_content_length( int content_len )
{
return add_response( "Content-Length: %d\r\n", content_len );
}
bool http_conn::add_linger()
{
return add_response( "Connection: %s\r\n", ( m_linger == true ) ? "keep-alive" : "close" );
}
bool http_conn::add_blank_line()
{
return add_response( "%s", "\r\n" );
}
bool http_conn::add_content( const char* content )
{
return add_response( "%s", content );
}
bool http_conn::process_write( HTTP_CODE ret )
{
switch ( ret )
{
case INTERNAL_ERROR:
{
add_status_line( 500, error_500_title );
add_headers( strlen( error_500_form ) );
if ( ! add_content( error_500_form ) )
{
return false;
}
break;
}
case BAD_REQUEST:
{
add_status_line( 400, error_400_title );
add_headers( strlen( error_400_form ) );
if ( ! add_content( error_400_form ) )
{
return false;
}
break;
}
case NO_RESOURCE:
{
add_status_line( 404, error_404_title );
add_headers( strlen( error_404_form ) );
if ( ! add_content( error_404_form ) )
{
return false;
}
break;
}
case FORBIDDEN_REQUEST:
{
add_status_line( 403, error_403_title );
add_headers( strlen( error_403_form ) );
if ( ! add_content( error_403_form ) )
{
return false;
}
break;
}
case FILE_REQUEST:
{
add_status_line( 200, ok_200_title );
if ( m_file_stat.st_size != 0 )
{
add_headers( m_file_stat.st_size );
m_iv[ 0 ].iov_base = m_write_buf;
m_iv[ 0 ].iov_len = m_write_idx;
m_iv[ 1 ].iov_base = m_file_address;
m_iv[ 1 ].iov_len = m_file_stat.st_size;
m_iv_count = 2;
return true;
}
else
{
const char* ok_string = "<html><body></body></html>";
add_headers( strlen( ok_string ) );
if ( ! add_content( ok_string ) )
{
return false;
}
}
}
default:
{
return false;
}
}
m_iv[ 0 ].iov_base = m_write_buf;
m_iv[ 0 ].iov_len = m_write_idx;
m_iv_count = 1;
return true;
}
void http_conn::process()
{
HTTP_CODE read_ret = process_read();//解析存放在读缓冲区的http报文
if ( read_ret == NO_REQUEST )//如果没有读到完整的报文数据
{
printf("read_data incomplete.\n");
modfd( m_epollfd, m_sockfd, EPOLLIN );//重置m_sockfd的读事件,等待下一次触发读取更多报文数据
return;
}
//读取到完整报文数据或者错误报文数据时开始填充写缓冲区
bool write_ret = process_write( read_ret );
if ( ! write_ret )//如果写的内容超过写缓冲区长度则关闭对应连接
{
printf("write_data overflow.\n");
close_conn();
}
else
{
modfd( m_epollfd, m_sockfd, EPOLLOUT );//修改m_sockfd的读事件为写事件,事件触发时执行写操作
}
}
#endif
================================================
FILE: Server/Codes/main.cpp
================================================
#include"web_thread.h"
#include"web_function.h"
#include"http_conn.h"
using namespace std;
webthread<http_conn>* thread_ptr;//指向自定义线程类的指针,这是一个全局变量,主线程和子线程通过线程类的双向管道来实现通信
int sig_pipefd[2];//系统中断信号处理函数与主函数之间的通信管道,本程序把信号处理放在主函数中进行
void* thread(void* arg)//子线程主函数
{
int* p=(int*)arg;
int index=*p;
delete arg;
thread_ptr[index].work();//对应的子线程开始工作
}
void create_son_thread()//创建子线程
{
pthread_t tid;
printf( "create son threads now\n" );
for(int i=0;i<thread_num;i++)
{
int* index=new int(i);
if(pthread_create(&tid,NULL,thread,index)!=0)//如果线程创建失败
{
delete [] thread_ptr;
unix_error("thread created failed");
}
if(pthread_detach(tid))//如果线程分离失败
{
delete [] thread_ptr;
unix_error("thread detach failed");
}
else
{
thread_ptr[i].tid=tid;
printf("create thread: %ld\n",tid);
}
}
}
void close_son_thread()
{
printf( "kill all the thread now\n" );
char info=CLOSE_THREAD;
for( int i = 0; i < thread_num; ++i )
{
send(thread_ptr[i].pipefd[1],&info,sizeof(info),0);
}
}
static void sig_handler( int sig)//信号发送给主函数
{
int save_errno = errno;
int msg = sig;
send( sig_pipefd[1], ( char* )&msg, 1, 0 );
errno = save_errno;
}
void deal_new_conn(int &thread_index)//主线程分配新到的连接给子线程
{
char info=NEW_CONN;
thread_index=thread_index%thread_num;
send(thread_ptr[thread_index].pipefd[1],&info,sizeof(info),0);//主线程通过对应的管道与子线程通信
thread_index++;
}
void deal_sig_recv(bool& stop)//主线程处理系统信号
{
char infos[1024];
int ret=recv(sig_pipefd[0],infos,sizeof(infos),0);
if( ret <= 0 )
{
return;
}
else
{
for( int i = 0; i < ret; ++i )
{
switch( infos[i] )
{
case SIGTERM:
case SIGINT:
{
close_son_thread();
stop=true;
break;
}
case SIGALRM:
{
int info=CLOSE_DEAD_CONN;
for( int i = 0; i < thread_num; ++i )
{
send(thread_ptr[i].pipefd[1],&info,sizeof(info),0);
}
alarm(ALARM_TIME);//重新设置定时信号
break;
}
default:
{
break;
}
}
}
}
}
int main(int argc, char *argv[])
{
if(argc<=2)
{
fprintf(stderr,"usage: %s ip_address port\n",argv[0]);;
exit(1);
}
char* ip=argv[1];
char* port=argv[2];
// char* ip="127.0.0.1";
// char* port="12345";
int listenfd=open_listenfd(ip,port,LISTENQ);
thread_ptr=new webthread<http_conn>[thread_num];
webthread<http_conn>::listenfd=listenfd;
pthread_mutex_init(&mutex_conn,NULL);
create_son_thread();//创建子线程,子线程开始运行
int epollfd = epoll_create( 5 );
assert( epollfd != -1 );
addfd(epollfd,listenfd,true);
webthread<http_conn>::m_epollfd=epollfd;
int ret = socketpair( PF_UNIX, SOCK_STREAM, 0, sig_pipefd );//创建信号处理函数与主函数之间的通信管道
assert( ret != -1 );
setnonblocking( sig_pipefd[1] );
addfd( epollfd, sig_pipefd[0] );
addsig( SIGTERM, sig_handler );
addsig( SIGINT, sig_handler );
addsig( SIGALRM,sig_handler );
addsig( SIGPIPE, SIG_IGN );
epoll_event events[MAX_EVENT_NUM];
int m_pid=(int)getpid();
printf("PID: %d\n",m_pid);
printf("server start\n");
alarm(ALARM_TIME);//开始定时
bool stop=false;
int thread_index=0;
while(!stop)
{
int number=epoll_wait(epollfd,events,MAX_EVENT_NUM,-1);
if(number<0 && errno!=EINTR)
{
close_son_thread();
unix_error("main thread epoll failed.");
break;
}
for(int i=0;i<number;i++)
{
int sockfd=events[i].data.fd;
if(sockfd==listenfd)//有新连接到来,循环分配给线程
{
deal_new_conn(thread_index);
}
else if( ( sockfd == sig_pipefd[0] ) && ( events[i].events & EPOLLIN ) )//接收到新信号,处理信号事件
{
deal_sig_recv(stop);
}
else
{
continue;
}
}
}
sleep(1);
Close(epollfd);
Close(listenfd);
Close(sig_pipefd[1]);
Close(sig_pipefd[0]);
delete [] thread_ptr;
printf("max_user_online: %d\n",(int)max_user_online);//最大客户端连接数
printf("user_online_now: %d\n",(int)user_count);//C++11,检查当前所有连接是否关闭
show_sys_time();
printf("closed server.\n");
exit(0);
}
================================================
FILE: Server/Codes/server.pro
================================================
QT += core
QT -= gui
TARGET = server
CONFIG += console
CONFIG -= app_bundle
QMAKE_CXXFLAGS += -std=c++0x
TEMPLATE = app
SOURCES += main.cpp
HEADERS += \
http_conn.h \
web_function.h \
web_thread.h
================================================
FILE: Server/Codes/web_function.h
================================================
#ifndef WEB_FUNCTION
#define WEB_FUNCTION
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <syslog.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pthread.h>
#include <netdb.h>
#include <atomic>
#include<unordered_map>
#include <sys/time.h>
const int thread_num=4;//线程数量
#define LISTENQ 1024//listen()函数的参数
#define MAX_EVENT_NUM 65535//epoll_event的事件数
#define EVENT_TABLE_SIZE 4096//epoll_wait()的参数
#define NEW_CONN '0'//发送给子线程的信号,表示有新连接到来
#define CLOSE_DEAD_CONN '1'//发送给子线程的信号,表示关闭空闲的长连接
#define CLOSE_THREAD '2'//发送给子线程的信号,表示结束子线程
#define ALARM_TIME 30//定时时间,也是长连接的超时时间
#define USER_PER_THREAD 500//每个子线程的最大用户数
#define FILENAME_LEN 200//文件名称的最大长度
#define READ_BUFFER_SIZE 256//给每个用户分配的读缓冲区大小
#define WRITE_BUFFER_SIZE 256//给每个用户分配的写缓冲区大小
pthread_mutex_t mutex_conn;
static std::atomic<int> user_count(0);//C++11的原子变量,统计所有线程的当前活跃用户数
static std::atomic<int> max_user_online(0);
void unix_error(char *msg) /* Unix-style error */
{
fprintf(stderr,"%s: %s\n", msg, strerror(errno));
exit(0);
}
void app_error(char *msg) /* Application error */
{
fprintf(stderr, "%s\n", msg);
exit(0);
}
void show_error(char *msg)
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
}
void show_addr(sockaddr_in address)
{
int save_errno=errno;
char host[NI_MAXHOST];
char service[NI_MAXSERV];
socklen_t addrlength = sizeof(address );
int ret=getnameinfo((struct sockaddr *)(&address), sizeof(struct sockaddr),host, sizeof(host), service, sizeof(service),NI_NUMERICHOST|NI_NUMERICSERV);
if(ret!=0)
{
show_error("address changed failed");
}
else
{
printf("(%s: %s)\n",host,service);
}
errno=save_errno;
}
int Open(const char *pathname, int flags, mode_t mode)
{
int rc;
if ((rc = open(pathname, flags, mode)) < 0)
unix_error("Open error");
return rc;
}
ssize_t Read(int fd, void *buf, size_t count)
{
ssize_t rc;
if ((rc = read(fd, buf, count)) < 0)
unix_error("Read error");
return rc;
}
ssize_t Write(int fd, const void *buf, size_t count)
{
ssize_t rc;
if ((rc = write(fd, buf, count)) < 0)
unix_error("Write error");
return rc;
}
void Close(int fd)
{
int rc;
if ((rc = close(fd)) < 0)
unix_error("Close error");
}
static int setnonblocking( int fd )
{
int old_option = fcntl( fd, F_GETFL );
int new_option = old_option | O_NONBLOCK;
fcntl( fd, F_SETFL, new_option );
return old_option;
}
static void removefd( int epollfd, int fd )//删除事件
{
epoll_ctl( epollfd, EPOLL_CTL_DEL, fd, 0 );
Close( fd );
}
static void addfd( int epollfd, int fd )//添加事件
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET;
epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
setnonblocking( fd );
}
void addfd( int epollfd, int fd, bool one_shot )//添加事件
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
if( one_shot )
{
event.events |= EPOLLONESHOT;
}
epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
setnonblocking( fd );
}
void modfd( int epollfd, int fd, int ev )//修改事件
{
epoll_event event;
event.data.fd = fd;
event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
epoll_ctl( epollfd, EPOLL_CTL_MOD, fd, &event );
}
void show_sys_time()
{
time_t timel;
time(&timel);
printf("time: %s",asctime(gmtime(&timel)));
}
static void addsig( int sig, void( handler )(int), bool restart = true )
{
struct sigaction sa;
memset( &sa, '\0', sizeof( sa ) );
sa.sa_handler = handler;
if( restart )
{
sa.sa_flags |= SA_RESTART;
}
sigfillset( &sa.sa_mask );
assert( sigaction( sig, &sa, NULL ) != -1 );
}
int open_clientfd(char *ip, char *_port) {
int port=atoi(_port);
struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
int fd=socket(AF_INET,SOCK_STREAM,0);
int ret=connect(fd,(struct sockaddr*)&address,sizeof(address));
if(ret<0)
{
app_error("server connected failed");
}
else
{
return fd;
}
}
int open_listenfd(char *ip, char *_port,int backlog)//仅ipv4
{
int port=atoi(_port);
struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
int fd=socket(AF_INET,SOCK_STREAM,0);
int ret=bind(fd,(struct sockaddr*)&address,sizeof(address));
assert(ret!=-1);
ret=listen(fd,backlog);
if(ret<0)
{
unix_error("listenfd opened failed.");
}
else
{
return fd;
}
}
int open_listenfd(char *port)//ipv4/ipv6通用
{
struct addrinfo hints, *listp, *p;
int listenfd, rc, optval=1;
/* Get a list of potential server addresses */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; /* Accept connections */
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... on any IP address */
hints.ai_flags |= AI_NUMERICSERV; /* ... using port number */
if ((rc = getaddrinfo(NULL, port, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo failed (port %s): %s\n", port, gai_strerror(rc));
return -2;
}
/* Walk the list for one that we can bind to */
for (p = listp; p; p = p->ai_next) {
/* Create a socket descriptor */
if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue; /* Socket failed, try the next */
/* Eliminates "Address already in use" error from bind */
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, //line:netp:csapp:setsockopt
(const void *)&optval , sizeof(int));
/* Bind the descriptor to the address */
if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
break; /* Success */
if (close(listenfd) < 0) { /* Bind failed, try the next */
fprintf(stderr, "open_listenfd close failed: %s\n", strerror(errno));
return -1;
}
}
/* Clean up */
freeaddrinfo(listp);
if (!p) /* No address worked */
return -1;
/* Make it a listening socket ready to accept connection requests */
if (listen(listenfd, LISTENQ) < 0) {
close(listenfd);
return -1;
}
return listenfd;
}
#endif // WEB_FUNCTION
================================================
FILE: Server/Codes/web_thread.h
================================================
#ifndef WEB_THREAD_H
#define WEB_THREAD_H
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include<time.h>
#include<queue>
#include<map>
#include<stack>
#include<pthread.h>
#include"http_conn.h"
#include"web_function.h"
using namespace std;
const char* server_busy_info="HTTP/1.1 503 Service Unavailable\r\n\r\n";//当子线程的用户数达到上限值时向新连接发送该信息
struct timer_cmp
{
bool operator () (util_timer* a,util_timer* b)
{
return a->expire>b->expire;
}
};
template<typename T>
class webthread
{
public:
webthread() : user_num(0),stop(false)
{
epollfd = epoll_create(EVENT_TABLE_SIZE);
assert( epollfd != -1 );
int ret = socketpair( PF_UNIX, SOCK_STREAM, 0, pipefd);
assert( ret != -1 );
setnonblocking( pipefd[1] );
addfd( epollfd, pipefd[0] );
users=new T[USER_PER_THREAD];//提前分配用户空间
}
~webthread()
{
Close(pipefd[1]);
Close(pipefd[0]);
Close(epollfd);
}
public:
pthread_t tid;
int pipefd[2];//通过该管道实现子线程与主线程的双向通信
static int listenfd;//主线程中的监听描述符
static int m_epollfd;//主线程的epollfd,在工作线程中访问该epollfd来重置listenfd的事件
void work();
private:
int user_num;//当前在线客户端数量
int epollfd;//工作线程的epollfd
T* users;//客户端连接的工作空间
bool stop;//是否退出线程主循环
};
template<typename T>
int webthread<T>::listenfd=-1;
template<typename T>
int webthread<T>::m_epollfd=-1;
template<typename T>
void webthread<T>::work()
{
epoll_event events[MAX_EVENT_NUM];
stack<int> index_free;//初始化空闲队列
for(int i=USER_PER_THREAD-1;i>=0;i--)
{
index_free.push(i);
}
unordered_map<int,int> mp;//用户索引与连接符的匹配
priority_queue<util_timer*,vector<util_timer*>,timer_cmp> timer_queue;//定时器容器
bool timeshot=false;//定时事件
while(!stop)
{
int number=epoll_wait(epollfd,events,MAX_EVENT_NUM,-1);
if(number<0 && errno!=EINTR)
{
unix_error("epoll failed.");
break;
}
for(int i=0;i<number;i++)
{
int sockfd=events[i].data.fd;
if(sockfd==pipefd[0] && ( events[i].events & EPOLLIN ))//如果接收到主线程的消息
{
char infos[1024];
int ret=recv(sockfd,infos,sizeof(infos),0);
if( ( ( ret < 0 ) && ( errno != EAGAIN ) ) || ret == 0 )
{
continue;
}
else
{ for(int j=0;j<ret;j++)
{
switch (infos[j])
{
case NEW_CONN://处理新到来的连接
{
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof( client_address );
int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
int conn_count=0;
while(connfd>0)
{
conn_count++;
if(!index_free.empty())//如果还没有达到本线程的最大用户数,则给新用户分配用户空间
{
int user_index=index_free.top();
index_free.pop();
mp[connfd]=user_index;
util_timer* _timer=new util_timer(time(NULL)+ALARM_TIME);//给每个用户分配一个定时器
_timer->client=&users[user_index];//将定时器与用户地址关联起来,方便后面通过定时器关闭非活动连接
timer_queue.push(_timer);//将定时器放入队列容器中
users[user_index].init( epollfd, connfd, client_address,_timer,user_index,&index_free);//初始化用户空间
}
else//如果用户已满,向新连接发送服务器繁忙消息
{
ret=send(connfd,server_busy_info,strlen(server_busy_info),0);
if(ret<=0)
{
printf("busy_info send failed.\n");
}
Close(connfd);
}
memset((char*)&client_address,0,client_addrlength);
connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
}
modfd(m_epollfd, listenfd, EPOLLIN);//重置listenfd的读事件
if(conn_count==0)
{
printf("user_num_now: %d\n",user_num);
show_error("accept connection failed");
continue;
}
break;
}
case CLOSE_DEAD_CONN:
{
timeshot=true;//定时事件优先级最低
break;
}
case CLOSE_THREAD:
{
printf("thread:%ld get close info\n",tid);
stop=true;//退出线程主循环
break;
}
default:
break;
}
}
}
}
else if( events[i].events & ( EPOLLRDHUP | EPOLLHUP | EPOLLERR ) )//如果有连接描述符出现错误
{
int user_index=mp[sockfd];
users[user_index].close_conn();
}
else if( events[i].events & EPOLLIN )//如果有连接描述符可读
{
int user_index=mp[sockfd];
if( users[user_index].read() )//读取连接描述符的消息到用户的读缓冲区
{
users[user_index].process();//解析用户读缓冲区的http请求,解析后往用户的写缓冲区写http应答,并将该连接描述符的epoll注册事件改为写事件
}
else
{
users[user_index].close_conn();//如果失败则关闭对应描述符,并释放被原用户占用的空间
}
}
else if( events[i].events & EPOLLOUT )//如果有连接描述符可写
{
int user_index=mp[sockfd];
if( !users[user_index].write() )//如果写失败或者是短连接则立即关闭连接
{
users[user_index].close_conn();
}
else//如果是长连接则延长定时器的定时时间,并将该连接描述符的epoll注册事件改为读事件
{
util_timer* timer_temp=users[user_index].timer;
timer_temp->expire=time(NULL)+ALARM_TIME;
}
}
else
{}
if(timeshot)//定时时间到,回收非活动连接
{
while(!timer_queue.empty())
{
util_timer* timer_temp=timer_queue.top();
timer_queue.pop();
timer_queue.push(timer_temp);
timer_temp=timer_queue.top();//由于指针指向的元素值发生改变,需要重排元素
time_t t=time(NULL);
if(timer_temp->free)//如果定时器关联的连接已关闭,则释放定时器占用的空间
{
timer_queue.pop();
delete timer_temp;
}
else if(timer_temp->expire<=t)//关闭定时器关联的连接并释放空间
{
timer_temp->timed_event();
timer_queue.pop();
delete timer_temp;
}
else//剩下的连接还没到超时时间
{
timeshot=false;
break;
}
}
timeshot=false;
}
}
}
//退出线程前先回收堆空间以避免内存泄漏
while (!timer_queue.empty())
{
util_timer* timer_temp=timer_queue.top();
timer_queue.pop();
if(!timer_temp->free)
{
timer_temp->timed_event();
}
delete timer_temp;
}
delete [] users;
printf("closed thread: %ld\n",tid);
}
#endif // WEB_THREAD_H
================================================
FILE: Server/Debug/Makefile
================================================
#############################################################################
# Makefile for building: server
# Generated by qmake (2.01a) (Qt 4.8.7) on: ?? 3? 28 13:48:52 2020
# Project: ../Codes/server.pro
# Template: app
# Command: /usr/lib/x86_64-linux-gnu/qt4/bin/qmake -spec /usr/share/qt4/mkspecs/linux-g++-64 CONFIG+=debug -o Makefile ../Codes/server.pro
#############################################################################
####### Compiler, tools and options
CC = gcc
CXX = g++
DEFINES = -DQT_CORE_LIB -DQT_SHARED
CFLAGS = -m64 -pipe -g -Wall -W -D_REENTRANT $(DEFINES)
CXXFLAGS = -m64 -pipe -std=c++0x -g -Wall -W -D_REENTRANT $(DEFINES)
INCPATH = -I/usr/share/qt4/mkspecs/linux-g++-64 -I../Codes -I/usr/include/qt4/QtCore -I/usr/include/qt4 -I. -I../Codes -I.
LINK = g++
LFLAGS = -m64
LIBS = $(SUBLIBS) -L/usr/lib/x86_64-linux-gnu -lQtCore -lpthread
AR = ar cqs
RANLIB =
QMAKE = /usr/lib/x86_64-linux-gnu/qt4/bin/qmake
TAR = tar -cf
COMPRESS = gzip -9f
COPY = cp -f
SED = sed
COPY_FILE = $(COPY)
COPY_DIR = $(COPY) -r
STRIP = strip
INSTALL_FILE = install -m 644 -p
INSTALL_DIR = $(COPY_DIR)
INSTALL_PROGRAM = install -m 755 -p
DEL_FILE = rm -f
SYMLINK = ln -f -s
DEL_DIR = rmdir
MOVE = mv -f
CHK_DIR_EXISTS= test -d
MKDIR = mkdir -p
####### Output directory
OBJECTS_DIR = ./
####### Files
SOURCES = ../Codes/main.cpp
OBJECTS = main.o
DIST = /usr/share/qt4/mkspecs/common/unix.conf \
/usr/share/qt4/mkspecs/common/linux.conf \
/usr/share/qt4/mkspecs/common/gcc-base.conf \
/usr/share/qt4/mkspecs/common/gcc-base-unix.conf \
/usr/share/qt4/mkspecs/common/g++-base.conf \
/usr/share/qt4/mkspecs/common/g++-unix.conf \
/usr/share/qt4/mkspecs/qconfig.pri \
/usr/share/qt4/mkspecs/features/qt_functions.prf \
/usr/share/qt4/mkspecs/features/qt_config.prf \
/usr/share/qt4/mkspecs/features/exclusive_builds.prf \
/usr/share/qt4/mkspecs/features/default_pre.prf \
/usr/share/qt4/mkspecs/features/debug.prf \
/usr/share/qt4/mkspecs/features/default_post.prf \
/usr/share/qt4/mkspecs/features/shared.prf \
/usr/share/qt4/mkspecs/features/unix/gdb_dwarf_index.prf \
/usr/share/qt4/mkspecs/features/warn_on.prf \
/usr/share/qt4/mkspecs/features/qt.prf \
/usr/share/qt4/mkspecs/features/unix/thread.prf \
/usr/share/qt4/mkspecs/features/moc.prf \
/usr/share/qt4/mkspecs/features/resources.prf \
/usr/share/qt4/mkspecs/features/uic.prf \
/usr/share/qt4/mkspecs/features/yacc.prf \
/usr/share/qt4/mkspecs/features/lex.prf \
/usr/share/qt4/mkspecs/features/include_source_dir.prf \
../Codes/server.pro
QMAKE_TARGET = server
DESTDIR =
TARGET = server
first: all
####### Implicit rules
.SUFFIXES: .o .c .cpp .cc .cxx .C
.cpp.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.cc.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.cxx.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.C.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.c.o:
$(CC) -c $(CFLAGS) $(INCPATH) -o "$@" "$<"
####### Build rules
all: Makefile $(TARGET)
$(TARGET): $(OBJECTS)
$(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJCOMP) $(LIBS)
{ test -n "$(DESTDIR)" && DESTDIR="$(DESTDIR)" || DESTDIR=.; } && test $$(gdb --version | sed -e 's,[^0-9][^0-9]*\([0-9]\)\.\([0-9]\).*,\1\2,;q') -gt 72 && gdb --nx --batch --quiet -ex 'set confirm off' -ex "save gdb-index $$DESTDIR" -ex quit '$(TARGET)' && test -f $(TARGET).gdb-index && objcopy --add-section '.gdb_index=$(TARGET).gdb-index' --set-section-flags '.gdb_index=readonly' '$(TARGET)' '$(TARGET)' && rm -f $(TARGET).gdb-index || true
Makefile: ../Codes/server.pro /usr/share/qt4/mkspecs/linux-g++-64/qmake.conf /usr/share/qt4/mkspecs/common/unix.conf \
/usr/share/qt4/mkspecs/common/linux.conf \
/usr/share/qt4/mkspecs/common/gcc-base.conf \
/usr/share/qt4/mkspecs/common/gcc-base-unix.conf \
/usr/share/qt4/mkspecs/common/g++-base.conf \
/usr/share/qt4/mkspecs/common/g++-unix.conf \
/usr/share/qt4/mkspecs/qconfig.pri \
/usr/share/qt4/mkspecs/features/qt_functions.prf \
/usr/share/qt4/mkspecs/features/qt_config.prf \
/usr/share/qt4/mkspecs/features/exclusive_builds.prf \
/usr/share/qt4/mkspecs/features/default_pre.prf \
/usr/share/qt4/mkspecs/features/debug.prf \
/usr/share/qt4/mkspecs/features/default_post.prf \
/usr/share/qt4/mkspecs/features/shared.prf \
/usr/share/qt4/mkspecs/features/unix/gdb_dwarf_index.prf \
/usr/share/qt4/mkspecs/features/warn_on.prf \
/usr/share/qt4/mkspecs/features/qt.prf \
/usr/share/qt4/mkspecs/features/unix/thread.prf \
/usr/share/qt4/mkspecs/features/moc.prf \
/usr/share/qt4/mkspecs/features/resources.prf \
/usr/share/qt4/mkspecs/features/uic.prf \
/usr/share/qt4/mkspecs/features/yacc.prf \
/usr/share/qt4/mkspecs/features/lex.prf \
/usr/share/qt4/mkspecs/features/include_source_dir.prf \
/usr/lib/x86_64-linux-gnu/libQtCore.prl
$(QMAKE) -spec /usr/share/qt4/mkspecs/linux-g++-64 CONFIG+=debug -o Makefile ../Codes/server.pro
/usr/share/qt4/mkspecs/common/unix.conf:
/usr/share/qt4/mkspecs/common/linux.conf:
/usr/share/qt4/mkspecs/common/gcc-base.conf:
/usr/share/qt4/mkspecs/common/gcc-base-unix.conf:
/usr/share/qt4/mkspecs/common/g++-base.conf:
/usr/share/qt4/mkspecs/common/g++-unix.conf:
/usr/share/qt4/mkspecs/qconfig.pri:
/usr/share/qt4/mkspecs/features/qt_functions.prf:
/usr/share/qt4/mkspecs/features/qt_config.prf:
/usr/share/qt4/mkspecs/features/exclusive_builds.prf:
/usr/share/qt4/mkspecs/features/default_pre.prf:
/usr/share/qt4/mkspecs/features/debug.prf:
/usr/share/qt4/mkspecs/features/default_post.prf:
/usr/share/qt4/mkspecs/features/shared.prf:
/usr/share/qt4/mkspecs/features/unix/gdb_dwarf_index.prf:
/usr/share/qt4/mkspecs/features/warn_on.prf:
/usr/share/qt4/mkspecs/features/qt.prf:
/usr/share/qt4/mkspecs/features/unix/thread.prf:
/usr/share/qt4/mkspecs/features/moc.prf:
/usr/share/qt4/mkspecs/features/resources.prf:
/usr/share/qt4/mkspecs/features/uic.prf:
/usr/share/qt4/mkspecs/features/yacc.prf:
/usr/share/qt4/mkspecs/features/lex.prf:
/usr/share/qt4/mkspecs/features/include_source_dir.prf:
/usr/lib/x86_64-linux-gnu/libQtCore.prl:
qmake: FORCE
@$(QMAKE) -spec /usr/share/qt4/mkspecs/linux-g++-64 CONFIG+=debug -o Makefile ../Codes/server.pro
dist:
@$(CHK_DIR_EXISTS) .tmp/server1.0.0 || $(MKDIR) .tmp/server1.0.0
$(COPY_FILE) --parents $(SOURCES) $(DIST) .tmp/server1.0.0/ && $(COPY_FILE) --parents ../Codes/http_conn.h ../Codes/web_function.h ../Codes/web_thread.h .tmp/server1.0.0/ && $(COPY_FILE) --parents ../Codes/main.cpp .tmp/server1.0.0/ && (cd `dirname .tmp/server1.0.0` && $(TAR) server1.0.0.tar server1.0.0 && $(COMPRESS) server1.0.0.tar) && $(MOVE) `dirname .tmp/server1.0.0`/server1.0.0.tar.gz . && $(DEL_FILE) -r .tmp/server1.0.0
clean:compiler_clean
-$(DEL_FILE) $(OBJECTS)
-$(DEL_FILE) *~ core *.core
####### Sub-libraries
distclean: clean
-$(DEL_FILE) $(TARGET)
-$(DEL_FILE) Makefile
check: first
mocclean: compiler_moc_header_clean compiler_moc_source_clean
mocables: compiler_moc_header_make_all compiler_moc_source_make_all
compiler_moc_header_make_all:
compiler_moc_header_clean:
compiler_rcc_make_all:
compiler_rcc_clean:
compiler_image_collection_make_all: qmake_image_collection.cpp
compiler_image_collection_clean:
-$(DEL_FILE) qmake_image_collection.cpp
compiler_moc_source_make_all:
compiler_moc_source_clean:
compiler_uic_make_all:
compiler_uic_clean:
compiler_yacc_decl_make_all:
compiler_yacc_decl_clean:
compiler_yacc_impl_make_all:
compiler_yacc_impl_clean:
compiler_lex_make_all:
compiler_lex_clean:
compiler_clean:
####### Compile
main.o: ../Codes/main.cpp ../Codes/web_thread.h \
../Codes/http_conn.h \
../Codes/web_function.h
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o main.o ../Codes/main.cpp
####### Install
install: FORCE
uninstall: FORCE
FORCE:
================================================
FILE: Server/Debug/html/index.html
================================================
<html>
<head>
<title>test</title>
</head>
<body>
<p>test</p>
</body>
</html>
gitextract_wdce6jin/
├── Client/
│ ├── Codes/
│ │ ├── README.md
│ │ ├── client.pro
│ │ └── main.cpp
│ └── Debug/
│ ├── Makefile
│ ├── client
│ └── main.o
├── README.md
└── Server/
├── Codes/
│ ├── README.md
│ ├── http_conn.h
│ ├── main.cpp
│ ├── server.pro
│ ├── web_function.h
│ └── web_thread.h
└── Debug/
├── Makefile
├── cgi-bin/
│ └── adder
├── html/
│ └── index.html
├── main.o
└── server
SYMBOL INDEX (51 symbols across 5 files)
FILE: Client/Codes/main.cpp
function setnonblocking (line 15) | int setnonblocking( int fd )
function addfd (line 23) | void addfd( int epoll_fd, int fd )
function write_nbytes (line 32) | bool write_nbytes( int sockfd, const char* buffer, int len )
function read_once (line 57) | bool read_once( int sockfd, char* buffer, int len )
function start_conn (line 75) | void start_conn( int epoll_fd, int num, const char* ip, int port )
function close_conn (line 102) | void close_conn( int epoll_fd, int sockfd )
function main (line 108) | int main( int argc, char* argv[] )
FILE: Server/Codes/http_conn.h
function class (line 42) | class http_conn
function class (line 119) | class util_timer
function close_conn (line 131) | void http_conn::close_conn( bool real_close )
function init (line 148) | void http_conn::init( int epollfd,int sockfd, const sockaddr_in& addr,ut...
function init (line 179) | void http_conn::init()
function read (line 234) | bool http_conn::read()
function unmap (line 456) | void http_conn::unmap()
function write (line 465) | bool http_conn::write()
function add_response (line 511) | bool http_conn::add_response( const char* format, ... )
function add_status_line (line 529) | bool http_conn::add_status_line( int status, const char* title )
function add_headers (line 534) | bool http_conn::add_headers( int content_len )
function add_content_length (line 541) | bool http_conn::add_content_length( int content_len )
function add_linger (line 546) | bool http_conn::add_linger()
function add_blank_line (line 551) | bool http_conn::add_blank_line()
function add_content (line 556) | bool http_conn::add_content( const char* content )
function process_write (line 561) | bool http_conn::process_write( HTTP_CODE ret )
function process (line 641) | void http_conn::process()
FILE: Server/Codes/main.cpp
function create_son_thread (line 22) | void create_son_thread()//创建子线程
function close_son_thread (line 48) | void close_son_thread()
function sig_handler (line 58) | static void sig_handler( int sig)//信号发送给主函数
function deal_new_conn (line 66) | void deal_new_conn(int &thread_index)//主线程分配新到的连接给子线程
function deal_sig_recv (line 73) | void deal_sig_recv(bool& stop)//主线程处理系统信号
function main (line 115) | int main(int argc, char *argv[])
FILE: Server/Codes/web_function.h
function unix_error (line 49) | void unix_error(char *msg) /* Unix-style error */
function app_error (line 55) | void app_error(char *msg) /* Application error */
function show_error (line 61) | void show_error(char *msg)
function show_addr (line 66) | void show_addr(sockaddr_in address)
function Open (line 86) | int Open(const char *pathname, int flags, mode_t mode)
function Read (line 95) | ssize_t Read(int fd, void *buf, size_t count)
function Write (line 104) | ssize_t Write(int fd, const void *buf, size_t count)
function Close (line 114) | void Close(int fd)
function setnonblocking (line 123) | static int setnonblocking( int fd )
function removefd (line 132) | static void removefd( int epollfd, int fd )//删除事件
function addfd (line 138) | static void addfd( int epollfd, int fd )//添加事件
function addfd (line 147) | void addfd( int epollfd, int fd, bool one_shot )//添加事件
function modfd (line 160) | void modfd( int epollfd, int fd, int ev )//修改事件
function show_sys_time (line 168) | void show_sys_time()
function open_clientfd (line 189) | int open_clientfd(char *ip, char *_port) {
function open_listenfd (line 212) | int open_listenfd(char *ip, char *_port,int backlog)//仅ipv4
function open_listenfd (line 239) | int open_listenfd(char *port)//ipv4/ipv6通用
FILE: Server/Codes/web_thread.h
type timer_cmp (line 31) | struct timer_cmp
type sockaddr_in (line 122) | struct sockaddr_in
type sockaddr (line 124) | struct sockaddr
type sockaddr (line 149) | struct sockaddr
Condensed preview — 18 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (66K chars).
[
{
"path": "Client/Codes/README.md",
"chars": 27,
"preview": "一个针对服务器的压力测试程序,可自定义请求客户的数目\n"
},
{
"path": "Client/Codes/client.pro",
"chars": 144,
"preview": "QT += core\nQT -= gui\n\nTARGET = client\nCONFIG += console\nQMAKE_CXXFLAGS += -std=c++11\nCONFIG -= app_bundle\n\nTEMPLATE = ap"
},
{
"path": "Client/Codes/main.cpp",
"chars": 4087,
"preview": "#include <stdlib.h>\n#include <stdio.h>\n#include <assert.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/epol"
},
{
"path": "Client/Debug/Makefile",
"chars": 7794,
"preview": "#############################################################################\n# Makefile for building: client\n# Generate"
},
{
"path": "README.md",
"chars": 752,
"preview": "* **前言**\n\t\n\t本项目是基于C++线程池的轻量级Web并发服务器,事件处理模式采用Reactor模式,主线程只负责监听文件描述符是否有事件发生,读写数据、接收新的连接、以及处理客户请求均在工作线程中实现;使用半同步/半异步模式,每个"
},
{
"path": "Server/Codes/README.md",
"chars": 740,
"preview": "* **web_thread.h**\n\t\n\t(1). 定义了webthread类;\n\t\n\t(2). 该类是主线程与子线程通信的媒介,本程序对于每个子线程建立对应的wedthread全局对象,主线程通过该对象来与子线程通信,子线程通过该对象接"
},
{
"path": "Server/Codes/http_conn.h",
"chars": 16303,
"preview": "#ifndef HTTPCONNECTION_H\n#define HTTPCONNECTION_H\n\n#include <unistd.h>\n#include <signal.h>\n#include <sys/types.h>\n#inclu"
},
{
"path": "Server/Codes/main.cpp",
"chars": 4760,
"preview": "#include\"web_thread.h\"\n#include\"web_function.h\"\n#include\"http_conn.h\"\n\nusing namespace std;\n\nwebthread<http_conn>* threa"
},
{
"path": "Server/Codes/server.pro",
"chars": 214,
"preview": "QT += core\nQT -= gui\n\nTARGET = server\nCONFIG += console\nCONFIG -= app_bundle\nQMAKE_CXXFLAGS += -std=c++0x\n\nTEMPLATE = ap"
},
{
"path": "Server/Codes/web_function.h",
"chars": 6791,
"preview": "#ifndef WEB_FUNCTION\n#define WEB_FUNCTION\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#inclu"
},
{
"path": "Server/Codes/web_thread.h",
"chars": 8661,
"preview": "#ifndef WEB_THREAD_H\n#define WEB_THREAD_H\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#inclu"
},
{
"path": "Server/Debug/Makefile",
"chars": 7979,
"preview": "#############################################################################\n# Makefile for building: server\n# Generate"
},
{
"path": "Server/Debug/html/index.html",
"chars": 81,
"preview": "<html>\n<head>\n<title>test</title>\n</head>\n<body>\n <p>test</p>\n</body>\n</html>\n"
}
]
// ... and 5 more files (download for full content)
About this extraction
This page contains the full source code of the Buerzhu/TinyWeb GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 18 files (57.0 KB), approximately 17.5k tokens, and a symbol index with 51 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.