目录
makefile的编写
gdb程序调试记录
调试记录1:
调试记录2:
调试记录3:
webserver的压测
之前
最近
总结:
之前写的那个简单聊天室真的太简陋了,毫无技术含量呀。。
写一个epoll+线程池实现的webserver,加入了对http请求的处理,还有定时器,用来处理长时间不活跃的连接。
代码不好放,还是github好管理一些,这里就写一点心得吧。。。
两个版本的代码如下:
https://github.com/cja416/simple_server
https://github.com/cja416/imitate_muduo_server
:如果要用gdb调试,一定要加上-g
一开始的最简单版本
CFLAGS :=-std=c++11 -Wall -O3 CXXFLAGS :=$(CFLAGS) cc := g++ webserver: epoll.o requestData.o threadpool.o util.o main.o g++ $(CXXFLAGS) epoll.o requestData.o threadpool.o util.o main.o -o webserver -lpthread epoll.o:epoll.cpp g++ -c $(CXXFLAGS) epoll.cpp -o epoll.o requestData.o:requestData.cpp g++ -c $(CXXFLAGS) requestData.cpp -o requestData.o threadpool.o:threadpool.cpp g++ -c $(CXXFLAGS) threadpool.cpp -o threadpool.o -lpthread util.o:util.cpp g++ -c $(CXXFLAGS) util.cpp -o util.o main.o:main.cpp g++ -c $(CXXFLAGS) main.cpp -o main.o .PHONY: clean: rm -rf epoll.o requestData.o threadpool.o util.o main.o cleanall: clean rm -rf webserver简化一下:
CFLAGS :=-std=c++11 -Wall -O3 CXXFLAGS :=$(CFLAGS) cc := g++ TARGET := webserver OBJ := epoll.o requestData.o threadpool.o util.o main.o $(TARGET): $(OBJ) $(cc) $(CXXFLAGS) $(OBJ) -o $(TARGET) -lpthread #%.o: %.cpp 这样写找不到%.cpp文件,直接让他隐含规则从.cpp推导出.o文件 # $(cc) $(CXXFLAGS) -c %.cpp -o %.o .PHONY: clean: rm -rf *.o cleanall: clean rm -rf $(TARGET)再简化一下:
CFLAGS :=-std=c++11 -Wall -O3 CXXFLAGS :=$(CFLAGS) cc := g++ SOURCE :=$(wildcard *.cpp) OBJ := $(patsubst %.cpp,%.o, $(SOURCE)) TARGET := webserver #OBJ := epoll.o requestData.o threadpool.o util.o main.o $(TARGET): $(OBJ) $(cc) $(CXXFLAGS) $(OBJ) -o $(TARGET) -lpthread .PHONY: clean: rm -rf *.o cleanall: clean rm -rf $(TARGET)
这个是epoll+线程池版本的调试记录,不是仿照muduo的版本
调试的时候一直有这个问题:
用
telnet 127.0.0.1 80 然后 ctrl+],再 GET /home/user/old_version1/index.html HTTP/1.1 一直会 Connection closed by foreign host.同时服务器端:
1 error event ~requestData() ~mytimer同理,用curl去连也会出现这个问题
curl "http://127.0.0.1:80/home/user/old_version1/index.html" curl: (56) Recv failure: 连接被对方重设然后我又用了webbench:连接全是failed。wtf?
webbench -c 3 -t 5 http://127.0.0.1/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://127.0.0.1/ 3 clients, running 5 sec. Speed=222240 pages/min, 0 bytes/sec. Requests: 0 susceed, 18520 failed.在服务端程序里面,是这样写的:
if(fd == listen_fd){ //cout << "This is listen_fd" << endl; acceptConnection(listen_fd,epoll_fd,path); } else{ // 排除错误事件 if((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (events[i].events & EPOLLIN)) { printf("error event\n"); delete request; continue; } // 将请求任务加入到线程池中 // 加入线程池之前将Timer和request分离 request->seperateTimer(); }现在就是出现了这个情况,可是为啥来的读写事件会是错误的呢?明明在acceptConnection里面已经将accept_fd的事件设置为__uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT;如下
accept_fd=accept(listen_fd,(struct sockaddr*)&client_addr,&client_addr_len) __uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT; epoll_add(epoll_fd,accept_fd,static_cast<void*>(req_info),_epo_event); //其中 int epoll_add(int epoll_fd, int fd, void *request, __uint32_t events){ struct epoll_event event; event.data.ptr=request; //指针用->,实体用. event.events=events; if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,fd,&event)<0){ perror("epoll_add error"); return -1; } return 0; }想到一个调试的方法:我查到了events各种事件的位标识,可以在程序中看看来读写请求的时候这个event[i].events有哪几位被置1了,就可以判断他到底触发是啥事件(gdb打个断点)
注意:gdb要attach一个正在运行的进程,需要用sudo来启动gdb : sudo gdb , 查看进程号:ps -elf | grep 进程名
如果出现No source file named 或者 没有符号表被读取。请使用 "file" 命令,说明是编译时没有加上-g选项,要再Makefile里面加上,并且make clean ,再重新编译一下,会发现可执行文件变大了许多。
(gdb) attach 2350 #绑定正在运行的进程 Attaching to process 2350 [New LWP 2351] [New LWP 2352] [New LWP 2353] [New LWP 2354] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 0x00007ff25efb6a13 in epoll_wait () at ../sysdeps/unix/syscall-template.S:84 84 ../sysdeps/unix/syscall-template.S: 没有那个文件或目录. (gdb) l 79 in ../sysdeps/unix/syscall-template.S (gdb) b main.cpp:139 #在main.cpp函数第139行打一个断点 Breakpoint 1 at 0x40254a: main.cpp:139. (2 locations) (gdb) c #在另一个会话telnet后,继续运行 Continuing. Thread 1 "webserver" hit Breakpoint 1, handle_events (path="/", tp=0x13348f0, events_num=<optimized out>, events=<optimized out>, listen_fd=4, epoll_fd=3) at main.cpp:139 139 int fd=request->getFd(); (gdb) l #显示源代码 134 void handle_events(int epoll_fd, int listen_fd, struct epoll_event* events, int events_num, const string &path, threadpool_t* tp) 135 { 136 //获取有事件产生的描述符 137 for(int i=0;i<events_num;++i){ 138 requestData* request = (requestData*)(events[i].data.ptr); //装有活跃事件的数组 139 int fd=request->getFd(); 140 141 142 // 有事件发生的描述符为监听描述符 143 if(fd == listen_fd){上面那个是接受新连接的,接下来处理收到请求:
(gdb) b main.cpp:147 #再在下面打一个断点 Breakpoint 2 at 0x402557: main.cpp:147. (2 locations) (gdb) c Continuing. #telnet发送具体请求 Thread 1 "webserver" hit Breakpoint 1, handle_events (path="/", tp=0x13348f0, events_num=<optimized out>, events=<optimized out>, listen_fd=4, epoll_fd=3) at main.cpp:139 139 int fd=request->getFd(); (gdb) s #单步往下走 requestData::getFd (this=this@entry=0x1335350) at requestData.cpp:117 117 return fd; (gdb) s 118 } (gdb) handle_events (path="/", tp=0x13348f0, events_num=<optimized out>, events=<optimized out>, listen_fd=4, epoll_fd=3) at main.cpp:143 143 if(fd == listen_fd){ (gdb) #重点来了 Thread 1 "webserver" hit Breakpoint 2, handle_events (path="/", tp=0x13348f0, events_num=<optimized out>, events=<optimized out>, listen_fd=4, epoll_fd=3) at main.cpp:150 150 || (events[i].events & EPOLLIN)) (gdb) l 145 acceptConnection(listen_fd,epoll_fd,path); 146 } 147 else{ 148 // 排除错误事件 149 if((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) 150 || (events[i].events & EPOLLIN)) 151 { 152 printf("error event\n"); 153 delete request; 154 continue; (gdb) print events[i].events value has been optimized out # wtf???????????????????在使用gdb过程中出现value optimized out,上述情况是由于gcc在编译过程中默认使用-O2优化选项。
以上情况在循环语句中经常出现,对于希望进行单步跟踪调试时,应使用-O0选项。
当场爆炸,把优化级别改成O0 重新编译 再来一次:
(gdb) b main.cpp:147 Breakpoint 1 at 0x40cd4e: file main.cpp, line 147. (gdb) c Continuing. Thread 1 "webserver" hit Breakpoint 1, handle_events (epoll_fd=3, listen_fd=4, events=0x11e0e80, events_num=1, path="/", tp=0x11ef8f0) at main.cpp:149 149 if((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) (gdb) l 144 //cout << "This is listen_fd" << endl; 145 acceptConnection(listen_fd,epoll_fd,path); 146 } 147 else{ 148 // 排除错误事件 149 if((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) 150 || (events[i].events & EPOLLIN)) 151 { 152 printf("error event\n"); 153 delete request; (gdb) print events[i].events $1 = 1 #check it out bro我们看看:
EPOLLIN = 0x001, EPOLLPRI = 0x002, EPOLLOUT = 0x004, EPOLLHUP = 0x010,这样来说应该是EPOLLIN啊
emmm,代码少敲了一个!号
应该是 !(events[i].events & EPOLLIN)
看看代码里面:
$1 = 1 (gdb) s 150 || (events[i].events & EPOLLIN)) (gdb) s 152 printf("error event\n");蓝瘦。
vim main.cpp 147G 加上一个! :wq make #只会对有改变的文件进行重新编译链接这样一来就对了:
客户端
user@ubuntu:~$ telnet 127.0.0.1 80 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. ^] telnet> GET /home/user/old_version1/index.html HTTP/1.1 Accept-Encoding: gzip, deflate, br HTTP/1.1 404 Not Found! #响应成功了 Content-type: text/html Connection: close Content-length: 116 <html><title>TKeed Error</title><body bgcolor="ffffff">404 Not Found!<hr><em> Web Server</em> </body></html>Connection closed by foreign host.服务端也正确地打出了我加上的调试打印信息:
this time i read: GET /home/user/old_version1/index.html HTTP/1.1 Accept-Encoding: gzip, deflate, br parse_uri finished try to analysisRequest ~requestData()ok。其实也要感谢粗心的自己,才能多多积攒调试经验。。。
现在关机都用 sudo init 0 简单快捷
emmm,在muduo版本的webserver上面,用如下请求成功了:
user@ubuntu:~/build/release/WebServer$ telnet 127.0.0.1 80 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. ^] telnet> GET /hello HTTP/1.1 Accept-Encoding: gzip, deflate, br HTTP/1.1 200 OK #响应报文 Content-type: text/plain Hello World #对请求进行处理的程序中写了,遇到请求文件名为hello的话,就返回这个
telnet时常有错误,也不显示请求失败,直接就Connection closed by foreign host.
sudo gdb ./webserver因为是处理http请求有问题,直接看处理代码(展示部分):
if (state == STATE_PARSE_URI) { int flag = this->parse_URI(); if (flag == PARSE_URI_AGAIN) { break; } else if (flag == PARSE_URI_ERROR) { perror("2"); isError = true; break; } } if (state == STATE_PARSE_HEADERS) { cout<<"parse_uri finished"<<endl; //197行 int flag = this->parse_Headers(); //获得一对对的key: value ,装在headers这个map里面 if (flag == PARSE_HEADER_AGAIN) { break; } else if (flag == PARSE_HEADER_ERROR) { perror("3"); isError = true; break; } if(method == METHOD_POST) { state = STATE_RECV_BODY; } else { state = STATE_ANALYSIS; } } if (state == STATE_RECV_BODY) //处理post请求,要多处理Content-length { cout<<"this is a POST request, and parse_headers finished"<<endl; int content_length = -1; if (headers.find("Content-length") != headers.end()) { content_length = stoi(headers["Content-length"]); } else { isError = true; break; } if (content.size() < content_length) //还没读完本次请求 continue; state = STATE_ANALYSIS; }首部已经验证没问题了,所以直接在这里打一个断点。
b requestData.cpp:197然后在另一个窗口telnet一下:
dl123@hust:~$ telnet 127.0.0.1 80 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. ^] telnet> GET /data/chenjinan/old_version/index.html HTTP/1.1接着在本窗口继续运行程序(c或者r):就停在断点上了
(gdb) b requestData.cpp:197 Breakpoint 2 at 0x404c34: file requestData.cpp, line 197. (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /data/chenjinan/old_version/webserver [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff6f4e700 (LWP 24580)] [New Thread 0x7ffff674d700 (LWP 24581)] [New Thread 0x7ffff5f4c700 (LWP 24582)] [New Thread 0x7ffff574b700 (LWP 24583)] requestData constructed ! 1 1 ~mytimer this time i read: GET /data/chenjinan/old_version/index.html HTTP/1.1 request_line find, method = 2 file_name: data/chenjinan/old_version/index.html http version: 2 [Switching to Thread 0x7ffff6f4e700 (LWP 24580)] Thread 2 "webserver" hit Breakpoint 2, requestData::handleRequest (this=0x635350) at requestData.cpp:197 197 cout<<"parse_uri finished"<<endl;用s(执行一步并进入函数)或者n(执行一步但不进入函数)来跟踪执行,过程中用p + 变量名 查看变量值,发现问题
在解析headers的时候,部分逻辑是这样的:
for (int i = 0; i < str.size() && notFinish; ++i) { switch(h_state) { case h_start: { if (str[i] == '\n' || str[i] == '\r') break; //这个地方直接break了 h_state = h_key; key_start = i; now_read_line_begin = i; break; } case h_key: { if (str[i] == ':') { key_end = i; if (key_end - key_start <= 0) return PARSE_HEADER_ERROR; h_state = h_colon; } else if (str[i] == '\n' || str[i] == '\r') return PARSE_HEADER_ERROR; break; }由于我用telnet请求的时候一直没有发送header部分(为空行),只有请求行部分,因此进入requestData::parse_Headers()函数的时候h_state 始终为 h_start ,这就导致了结束部分的返回逻辑:
if (h_state == h_end_LF) //只有正常走到h_state == h_end_LF才会返回成功 { str = str.substr(now_read_line_begin); return PARSE_HEADER_SUCCESS; } str = str.substr(now_read_line_begin); return PARSE_HEADER_AGAIN;只有当h_state == h_end_LF时才会 return PARSE_HEADER_SUCCESS; 否则一直 return PARSE_HEADER_AGAIN;,所以请求一直处理不了,这就是解析的逻辑问题了,改为这样:
case h_start: 404 { 405 if (str[i] == '\n' || str[i] == '\r'){ 406 h_state = h_end_LF; //请求headers为空行的情况也能继续 407 break; 408 } 409 h_state = h_key; 410 key_start = i; 411 now_read_line_begin = i; 412 break; 413 }就ok。
但是仍有问题:
telnet> #注意一定要先敲一个回车才能输入请求,否则会报命令无效 GET /data/chenjinan/old_version/index.html HTTP/1.1 HTTP/1.1 404 Not Found! Content-type: text/html Connection: close Content-length: 114 <html><title>TKeed Error</title><body bgcolor="ffffff">404 Not Found!<hr><em> cja's Web Server</em> </body></html>一直找不到index.html文件。一样,打断点看看是啥问题
(gdb) b requestData.cpp:563 Breakpoint 3 at 0x4063f1: file requestData.cpp, line 563. (gdb) c Continuing. 。。。省略。。。 [Switching to Thread 0x7ffff5f4c700 (LWP 24582)] Thread 4 "webserver" hit Breakpoint 3, requestData::analysisRequest (this=0x635350) at requestData.cpp:563 563 int dot_pos = file_name.find('.'); #报这样的错误 572 perror("stat file fail."); (gdb) epoll wait error: Interrupted system call -1 stat file fail.: No such file or directory把index.html拷贝到 /目录下,再执行 GET /index.html HTTP/1.1就可以了
dl123@hust:~$ telnet 127.0.0.1 80 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. ^] telnet> GET /index.html HTTP/1.1 HTTP/1.1 200 OK #正确的响应报文 Content-type: Content-length: 13 Hello World !那么问题来了,为啥放在/data/chenjinan/old_version/index.html 里面的就 stat 不到?是权限问题吗
查看错误码的位置:
首先在自己的程序中#include<errno.h> 添加打印errno的语句 printf("errno is: %d\n",errno); 根据errno的值查错。 errno的不同值的含义: 在linux 2.4.20-18的内核代码中的/usr/include/asm/errno.h
跑程序总是卡在这个assert上:其中newBuffer1 是指向buffer对象的shared_ptr
assert(newBuffer1 && newBuffer1->length() == 0); assert(newBuffer2 && newBuffer2->length() == 0);表现如下
2: Resource temporarily unavailable HTTP/1.1 400 Bad Request Content-Type: text/html Connection: Close Content-Length: 107 Server: My Web Server <html><title>title><body bgcolor="ffffff">400 Bad Request<hr><em> My Web Server</em></body></html> webserver: AsyncLogging.cpp:54: void AsyncLogging::threadFunc(): Assertion `newBuffer1 && newBuffer1->length() == 0' failed. Thread 6 "Logging" received signal SIGABRT, Aborted.不是很科学,每次都把newBuffer1的内容move走了啊:为啥还会length==0报错
{ MutexLockGuard lock(mutex_); if (buffers_.empty()) { cond_.waitForSeconds(flushInterval_); } buffers_.push_back(currentBuffer_); currentBuffer_.reset(); currentBuffer_ = std::move(newBuffer1); //这里 buffersToWrite.swap(buffers_); if (!nextBuffer_) { nextBuffer_ = std::move(newBuffer2); } }
------------------------------------------------------------------------
插一段:std::move之后指针会变成啥:
A a; shared_ptr<A>s1 = make_shared<A>(a); cout << "s1: " << s1 << ", use_count: "<< s1.use_count() << endl; shared_ptr<A>s2 = move(s1); cout << "s1: " << s1 << ", use_count: " << s1.use_count() << endl; cout << "s2: " << s2 << ", use_count: " << s2.use_count() << endl;可以看到,s1就变成空指针了,指向NULL
-------------------------------------------------------------------------
看看:经过一轮写日志之后,有这样一个操作
对他变空指针的情况做了处理:
if (!newBuffer1) { assert(!buffersToWrite.empty()); newBuffer1 = buffersToWrite.back(); buffersToWrite.pop_back(); newBuffer1->reset(); } 99 newBuffer1 = buffersToWrite.back(); (gdb) n 100 buffersToWrite.pop_back(); (gdb) p newBuffer1 $3 = std::shared_ptr<FixedBuffer<4000000>> (use count 2, weak count 0) = {get() = <optimized out>} (gdb) n 101 newBuffer1.reset(); //reset-1 ,0 (gdb) p newBuffer1 $4 = std::shared_ptr<FixedBuffer<4000000>> (empty) = {get() = 0x0}newBuffer1.reset()之前是有的,reset之后就变为empty了。
其中
void reset() { cur_ = data_; } char data_[SIZE]; char* cur_; //在LogStream.h中
第二次进入循环来到这里,发现newBuffer1和newBuffer2不一样了,newBuffer1指针是空的
assert(newBuffer1 && newBuffer1->length() == 0); assert(newBuffer2 && newBuffer2->length() == 0); assert(buffersToWrite.empty()); (gdb) p newBuffer1 $6 = std::shared_ptr<FixedBuffer<4000000>> (empty) = {get() = 0x0} (gdb) p newBuffer1->length() Attempt to take address of value not located in memory. (gdb) p newBuffer2 $7 = std::shared_ptr<FixedBuffer<4000000>> (use count 1, weak count 0) = {get() = <optimized out>} (gdb) p newBuffer1 $8 = std::shared_ptr<FixedBuffer<4000000>> (empty) = {get() = 0x0}此时再assert的话:发现问题出在哪了。
webserver: AsyncLogging.cpp:54: void AsyncLogging::threadFunc(): Assertion `newBuffer1 && newBuffer1->length() == 0' failed. Thread 6 "Logging" received signal SIGABRT, Aborted.
猜测问题: 上面说到newBuffer1.reset()之前是有的,reset之后就变为empty了。
但是我自己写的reset是:(buffer类中函数)
void reset() { cur_ = data_; }会不会是调成了shared_ptr的reset了(标准库函数):
void reset() noexcept { // release resource and convert to empty shared_ptr object shared_ptr().swap(*this); }导致reset之后并不是起到将newBuffer1->length设置为0,而是将newBuffer1指针置NULL。导致每次重进循环的时候都abort。
--------------------------------------------------------
插播小实验: 用智能指针调用reset会发生啥
void reset() { //A类中新增一个reset函数 cout << "A's reset" << endl; } A a; shared_ptr<A>s1 = make_shared<A>(a); cout << "s1: " << s1 << ", use_count: "<< s1.use_count() << endl; shared_ptr<A>s2 = move(s1); cout << "s1: " << s1 << ", use_count: " << s1.use_count() << endl; cout << "s2: " << s2 << ", use_count: " << s2.use_count() << endl; s2->reset(); cout << "s2: " << s2 << ", use_count: " << s2.use_count() << endl; s2.reset(); cout << "s2: " << s2 << ", use_count: " << s2.use_count() << endl;结果: 走的是shared_ptr的reset。
找到问题后:
--------------------------------------------------------
我发现问题了:仔细看
if (!newBuffer1) { assert(!buffersToWrite.empty()); newBuffer1 = buffersToWrite.back(); buffersToWrite.pop_back(); newBuffer1.reset(); //reset是将此对象的引用计数-1 ,若引用计数为0,则调用其析构函数 } if (!newBuffer2) { assert(!buffersToWrite.empty()); newBuffer2 = buffersToWrite.back(); buffersToWrite.pop_back(); newBuffer2->reset(); }之所以每次出错的都是newBuffer1,是因为newBuffer1调用reset是用.reset()形式,这样调用的是智能指针自己的reset,而newBuffer2是用的->reset()来调用的,这样调用的是buffer对象的reset,才是正确的。
ok
客户端向服务器发送请求得不到响应的排查步骤:
消息接收分为几个步骤,即客户端是否发送请求、服务器是否发送消息、客户端是否收到消息、客户端是否分发了消息、是否有消息处理函数以及其是否被正确调用。
ping一下网络、查看代码中返回值和是否抛出异常、抓包、查看端口数是否用完、trace route排查路由、是否进行端口映射等
类muduo版本的压测:
top -Hp pid 可以查看指定进程的线程
用webbench开1000客户端,跑30s
user@ubuntu:~$ webbench -c 1000 -t 30 http://127.0.0.1:80/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://127.0.0.1:80/ 1000 clients, running 30 sec. Speed=1180672 pages/min, 4683328 bytes/sec. Requests: 590336 susceed, 0 failed.request全部成功。我们看到,居然是log占用最多的cpu。
其实一开始是这样的
只是在一段时间后,业务处理完了,就都开始往后端写日志,才会出现log占用大量cpu的情况。
2.客户端加到2000居然也还没崩
3.有内存泄露
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1064 0.0 0.0 17676 1644 tty1 Ss+ 19:33 0:00 /sbin/agetty --noclear tty1 linux root 1080 0.0 1.8 237028 40092 tty7 Ss+ 19:33 0:00 /usr/lib/xorg/Xorg -core :0 -seat seat0 -auth user 1772 0.0 0.2 24576 5740 pts/8 Ss 19:34 0:00 -bash user 3066 0.0 0.2 24252 5288 pts/9 Ss 19:46 0:00 -bash user 3166 0.0 0.2 24252 5276 pts/10 Ss 19:55 0:00 -bash root 3254 0.0 0.1 56608 4152 pts/8 S+ 20:27 0:00 sudo ./WebServer -l /home/user/build/release/l root 3255 24.7 2.4 405716 53892 pts/8 Sl+ 20:27 6:14 ./WebServer -l /home/user/build/release/log.lo user 4504 0.0 0.1 43408 3732 pts/10 S+ 20:41 0:00 top -Hp 3255 user 26886 0.0 0.1 39104 3512 pts/9 R+ 20:52 0:00 ps -au而且现在不管怎么webbench他,都不动了
失败的情况:
user@ubuntu:~$ webbench -c 50 -t 40 http://127.0.0.1:80/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://127.0.0.1:80/ 50 clients, running 40 sec. Speed=0 pages/min, 0 bytes/sec. Requests: 0 susceed, 0 failed.包括telnet也没反应了。。。应该是文件描述符被用光了,或者是timewait端口太多了?或者是webbench创建进程数达到上限了?要好好查一查啊
就很气,一直是这样
dl123@hust:~$ telnet 127.0.0.1 80 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. GET /data/index.html HTTP/1.1 //往里面放一层就不行了 HTTP/1.1 404 Not Found! Content-type: text/html Connection: close Content-length: 114 <html><title>TKeed Error</title><body bgcolor="ffffff">404 Not Found!<hr><em> cja's Web Server</em> </body></html>Connection closed by foreign host. dl123@hust:~$ telnet 127.0.0.1 80 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. GET /index.html HTTP/1.1 //根目录就可以 HTTP/1.1 200 OK Content-type: Content-length: 13 Hello World !
上方是很早之前在普通机器上跑的时候做的测试。
下方是在服务器机器上跑的。
查看当前系统使用的打开文件符数
$ cat /proc/sys/fs/file-nr
5664 0 186405
其中第一个数表示当前系统已分配使用的打开文件描述符数,第二个数为分配后已释放的(目前已不再使用),第三个数等于 file-max
查看已连接数量:
netstat -nat|grep ESTABLISHED|wc -l
开始:
webbench命令:
dl123@hust:/data/chenjinan/webbench-1.5$ webbench -c 10 -t 30 http://127.0.0.1:80/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://127.0.0.1:80/ 10 clients, running 30 sec. Speed=4022696 pages/min, 15956690 bytes/sec. Requests: 2011348 susceed, 0 failed.cpu使用情况:
这个时候,有很多time-wait状态的连接。过了2MSL就会消失的,即大概1.5min。
我们知道,time-wait状态是tcp四次挥手中主动断开连接的一方才会有的状态,由于我跑webbench也是在本机,所以才会有,否则是在客户端机器上才会有的。
dl123@hust:~$ netstat -nat|grep TIME | wc -l 14112
逐渐增大压力:
dl123@hust:/data/chenjinan/webbench-1.5$ webbench -c 100 -t 30 http://127.0.0.1:80/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://127.0.0.1:80/ 100 clients, running 30 sec. Speed=4272254 pages/min, 16946612 bytes/sec. Requests: 2136127 susceed, 0 failed.还是没有失败的请求。
再多一点:
可以看到,已经开始有失败的出现了
dl123@hust:/data/chenjinan/webbench-1.5$ webbench -c 1000 -t 30 http://127.0.0.1:80/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://127.0.0.1:80/ 1000 clients, running 30 sec. Speed=4153054 pages/min, 16473741 bytes/sec. Requests: 2076525 susceed, 2 failed.cpu使用情况:
timewait状态:
dl123@hust:~$ netstat -nat|grep TIME | wc -l 13199这个时候,日志文件已经很大了:达到了2G,因为每次新连接和http请求都会打日志,导致日志打的真的猛,其实应该只打一些错误信息的哦
-rw-r--r-- 1 root root 2118862159 8月 4 11:11 WebServer.log
这次,把时间调大一些:有7个请求fail了
dl123@hust:/data/chenjinan/webbench-1.5$ webbench -c 1000 -t 60 http://127.0.0.1:80/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://127.0.0.1:80/ 1000 clients, running 60 sec. Speed=4158905 pages/min, 16496914 bytes/sec. Requests: 4158898 susceed, 7 failed.cpu使用情况都差不多
最后跑一个:
dl123@hust:/data/chenjinan/webbench-1.5$ webbench -c 2000 -t 60 http://127.0.0.1:80/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://127.0.0.1:80/ 2000 clients, running 60 sec. Speed=4002167 pages/min, 15875150 bytes/sec. Requests: 4002157 susceed, 10 failed.有10个请求failed
为了搞清楚请求fail的原因,今天又做了一次实验(在另外一台机器上,性能可能不同但是不影响)
ubuntu@VM-0-12-ubuntu:~$ webbench -t 60 -c 1000 http://127.0.0.1:80/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://127.0.0.1:80/ 1000 clients, running 60 sec. Speed=540189 pages/min, 2142495 bytes/sec. Requests: 540126 susceed, 63 failed.复现了失败的场景,结果日志里没有打错误的信息。。。
当我加了好多各种情况下的错误信息并重新编译运行测试,却怎么也复现不了上面那个场景了。。。各种参数都试过了
日志都打了2G还是没有复现场景
-rw-r--r-- 1 root root 2.1G Oct 13 11:01 log.log
ubuntu@VM-0-12-ubuntu:~$ webbench -c 1000 -t 60 http://127.0.0.1:80/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://127.0.0.1:80/ 1000 clients, running 60 sec. Speed=534417 pages/min, 1941711 bytes/sec. Requests: 534417 susceed, 0 failed.在webbench期间,查看Webserver打开的文件数如下(启动1000个客户端的情况,若是启动1500个,大概是1515。因为空载时打开的文件数就是15):
ubuntu@VM-0-12-ubuntu:~$ sudo ls -l /proc/31923/fd | wc -l 1013而我设置的进程打开最大文件数是65535,所以问题应该不在文件描述符:
ubuntu@VM-0-12-ubuntu:~$ ulimit -a core file size (blocks, -c) unlimited data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 7080 max locked memory (kbytes, -l) 16384 max memory size (kbytes, -m) unlimited open files (-n) 65535 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 7080 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited此过程中tcp各种状态:
ubuntu@VM-0-12-ubuntu:~$ netstat -ant|awk '/^tcp/ {++S[$NF]} END {for(a in S) print (a,S[a])}' LISTEN 4 ESTABLISHED 3905 TIME_WAIT 8310TIME-WAIT始终保持在8000出头的数量,应该也不会占光端口数65535(之所以会占端口数,是因为我客户端也是在本机跑的,创建客户端socket时要随机分配一个端口号给他)(tcp udp的端口号是用short表示的,因此就是65535个)
这里就引出一个小问题:6万多个端口的服务器怎么承接百万级别的客户端连接呢?我们知道服务器与每个客户端连接是会新建一个可读写的socket的,会占用一个端口
解决方法:
我们知道一个tcp连接socket对可用4元组表示:{源IP地址,源端口,目的IP地址,目的端口}
目的ip地址、目的端口我们无法改变(客户端侧),那么我们只能从源侧下手。而端口号不足正是我们要解决的问题,所以只能从源IP下手:
增加IP地址,一般假设本机网卡名称为 eth0,那么手动再添加几个虚拟的IP:
ifconfig eth0:1 192.168.190.151 ifconfig eth0:2 192.168.190.152 ......
假设系统可以使用的端口范围为(1024~65535),那么可以使用的大致端口为64000个,系统添加了10个IP地址,那么可以对外发出的数量为 64000 * 10 = 640000,数量很可观。
整体来说性能还是很不错的:
在2000个客户端,60s请求的情况下,成功的请求数4M,失败请求数10, 15M字节每秒的传输速率cpu使用率也较为平衡,1个accept线程+4个工作线程占用率都在90%左右,有点高了日志系统cpu占用率不高,只有2%左右内存占用总共0.3%左右,应该没有内存泄漏的情况发生之前在普通机器上测过一次,发现连续两次 1000 client + 30s 就再也不能接受新的请求了,现在想想应该是忘记调用户的最大打开文件描述符个数了,默认1024个,这次调为65535个,再没出现那种情况。端口用尽?1024~65535
过程中用到的命令:
#启动server sudo ./WebServer -t 4 -p 80 -l /data/chenjinan/WebServer/build/release/WebServer/weblog.log #使用webbench webbench -c 5000 -t 60 http://127.0.0.1:80/ #查看进程的情况 ps -elf | grep Web top -H -p 7460 #查看连接的情况 netstat -nat|grep TIME | wc -l sudo netstat -anpt sudo netstat -pltn | grep pid #查找文件 find -name log #查看当前系统使用的打开文件符数 cat /proc/sys/fs/file-nr #查看系统各个限制 ulimit -a #查看进程打开的文件数 sudo ls -l /proc/30035/fd | wc -l #其中30035是pid #或者用lsof lsof -c pname lspf -u username #抓包 tcpdump -i eth0 ip地址 #查看各种tcp连接状态数量 netstat -ant|awk '/^tcp/ {++S[$NF]} END {for(a in S) print (a,S[a])}' #监控机器 vmstat dstat
git管理代码
首先将本机的公钥放到github的账户上
ssh-keygen -t rsa -C "youremail@example.com"你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。
如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。
第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面:
然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容
一开始在本地机器创建文件夹simple_server
cd 到文件夹里,git init 将他设为git仓库
接着, 将文件夹下的文件都 git add 到仓库里
git commit -m “描述” 可以提交到分支
git remote add origin git@server-name:path/repo-name.git(自己的github的一个仓库地址) 将本地仓库与远程仓库绑定
git push origin master 提交到远程仓库去,要输入帐号和密码的
还有,
git checkout 分支名 可以切换到分支
git checkout -b 分支名 新建分支并切换到分支
git branch 查看分支情况
git merge dev 将分支dev合并到当前分支
git clone git@github.com:michaelliao/gitskills.git 从远程库克隆
使用命令修改git的用户名和提交的邮箱
如果你要修改当前全局的用户名和邮箱时,需要在上面的两条命令中添加一个参数,--global,代表的是全局。
命令分别为:git config --global user.name 你的目标用户名;
git config --global user.email 你的目标邮箱名;
当push时
提示:更新被拒绝,因为远程版本库包含您本地尚不存在的提交。这通常是因为另外提示:一个版本库已向该引用进行了推送。再次推送前,您可能需要先整合远程变更提示:(如 'git pull ...')。提示:详见 'Git push --help' 中的 'Note about fast-forwards' 小节。
原因可能是之前上传时创建的.git文件被删除或更改,或者其他人在github上提交过代码. 解决方案如下:1.强行上传 git push -u origin +master 2. 尽量先同步github上的代码到本地,在上面更改之后再上传