任务分发-epoll
每次添加事件、修改事件、删除事件都是差不多的流程,可以封装一下,并且epollfd的create和close可以变成RAII的管理模式。
因为上层调用时,事件的类型是不一样的(读转读、读转写、写转写等),为了接口易用与通用,让上层传入events描述事件类型。events是一个uinit32_t,也即32位无符号整数类型。
封装很简单,只需要支持添加、修改、删除、调用epoll_wait、设置非阻塞(这个也让上层决定,默认非阻塞)即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #ifndef EPOLLER_H #define EPOLLER_H #include <sys/epoll.h> #include <fcntl.h> #include <unistd.h> #include <cassert> #include <vector> class Epoller { private : int eventSize; const bool nonBlock; int epollFd; std::vector<struct epoll_event> events; public : Epoller (const int eventsize = 1024 , const bool ifNonBlock = true ): eventSize (eventsize),nonBlock (ifNonBlock),epollFd (epoll_create (5 )),events (eventsize) { assert (epollFd>=0 ); } ~Epoller () { close (epollFd); } int setFdNonblock (int fd) { return fcntl (fd, F_SETFL, fcntl (fd, F_GETFL, 0 ) | O_NONBLOCK); } bool addFd (int fd, uint32_t events) { if (fd < 0 ) return false ; if (nonBlock && (setFdNonblock (fd)==-1 )) return false ; epoll_event ev = {0 }; ev.data.fd = fd; ev.events = events; return 0 == epoll_ctl (epollFd, EPOLL_CTL_ADD, fd, &ev); } bool modFd (int fd, uint32_t events) { if (fd < 0 ) return false ; epoll_event ev = {0 }; ev.data.fd = fd; ev.events = events; return 0 == epoll_ctl (epollFd, EPOLL_CTL_MOD, fd, &ev); } bool delFd (int fd) { if (fd < 0 ) return false ; return 0 == epoll_ctl (epollFd, EPOLL_CTL_DEL, fd, 0 ); } int wait (int timeoutMs = 0 ) { return epoll_wait (epollFd, &events[0 ], eventSize, timeoutMs); } int getEventFd (size_t i) { return events[i].data.fd; } uint32_t getEvents (size_t i) { return events[i].events; } }; #endif
调用epoller伪代码:
简单说明一下异常事件:
EPOLLRDHUP:对方调用close()正常断开连接,服务器会得知该信息;或者直接终止进程,操作系统会自动向服务器发FIN,也会得知。
EPOLLHUP:异常断开,当socket的一端认为对方发来了一个不存在的4元组请求的时候,会回复一个RST响应,在epoll上会响应为EPOLLHUP事件。
RST:表示重置连接、复位连接。一般有两种场景:
对于客户端:当客户端向一个没有在listen的服务器端口发送的connect的时候,服务器会返回一个RST,因为服务器根本不知道这个4元组的存在。
对于服务器:当客户端的系统突然崩溃(kill pid或者正常关机不行的,因为操作系统会发送FIN给对方),这时服务器从4元组发到客户端的数据就发回一个RST。
使用shutdown、close 关闭套接字,发送的是FIN,不是RST 。
套接字关闭前,使用sleep 。对运行的程序Ctrl+C ,会发送FIN,不是RST 。
套接字关闭前,执行return、exit(0)、exit(1),会发送FIN、 不是RST 。
以上后两个是操作系统干的。
EPOLLERR:上面两个异常事件都是服务器被动从客户端返回的(FIN或RST)信息得知的,EPOLLERR是服务器主动采取动作 ,如read和write时,发现对方出问题了,就出现这个异常。
关于两个监听端口:
之前在客户端中简单描述为“两个监听线程”,本来想用两个线程、两个epoll池的。但实际上可以只在主线程里根据epoll响应两个端口上的连接请求
因此实际上两个监听端口都在主线程accept,通过epoll_wait判断是listenfd1还是listenfd2即可。
这是因为发送线程不会接收信息,它唯一的作用就是用于判断连接这个端口的是客户端的接收线程。那么两个监听端口就可以共用一个epoll池,因为epoll池响应到的connfd一定是listenfd1上的,就不需要两个epoll池分别响应connfd了。
不过因为这样叫方便,还是叫做什么什么线程来区分这两个东西,但其实都在主线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include "epoller.h" #include "threadpool.h" const int MAX_EVENT = 20 ;const int timeout = 0 ;unique_ptr<Epoller> epoller (1024 ) ;void start () { threadpool threadp (20 ) ; while (true ) { int eventCnt = epoller.wait (timeout); for (int i=0 ; i<eventCnt; i++) { int fd = epoller.getEventFd (i); uint32_t events = epoller.getEvents (i) if (fd == listenfd1) { listen_1 (); } else if (fd == listenfd2) { listen_2 (); } else if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) { if (usermap.fvalue_conn1_ip (fd) != "" ) exit_ (fd); else { epoller.delFd (fd); close (fd); } } else if (events & EPOLLIN) { threadp.addTask (bind (task,fd)); } } } }
recv处理 task中的接收,只有recv才被epoll响应,send是直接工作的,所以不受ET影响,也不用重新注册
非阻塞情况下,recv返回大于0是字节数,返回等于0 是网络断开了或copy出错,这是严重错误,不会设置errno;小于0(**-1**)是其他错误,保存在errno
注意:EPOLLHUP、EPOLLERR 这两个在add和mod时不需要手动注册这两个事件,内核会自动添加 这两个事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <errno.h> const uint32_t CONNEVENT = EPOLLONESHOT | EPOLLRDHUP | EPOLLET | EPOLLIN;string recv_str (int conn1) { char recvbuf[256 ]; string recvstr; while (true ) { memset (recvbuf, 0 , sizeof (recvbuf)); size_t len = recv (conn1, recvbuf, sizeof (recvbuf), 0 ); if (len == 0 ) { string myuser = usermap.fvalue_conn1_user (conn1); LOG_ERROR ("%s [recv] error!" , myuser.c_str ()); uint32_t connEvent = CONNEVENT; epoller.modFd (conn1, connEvent); return "" ; } else if (len == -1 ) { if (errno == EAGAIN || errno == EWOULDBLOCK) { uint32_t connEvent = CONNEVENT; epoller.modFd (conn1, connEvent); break ; } else if (errno == EINTR) continue ; else { string myuser = usermap.fvalue_conn1_user (conn1); LOG_ERROR ("%s [recv] error!" , myuser.c_str ()); uint32_t connEvent = CONNEVENT; epoller.modFd (conn1, connEvent); return "" ; } } else { recvstr += string (recvbuf); } } return recvstr; }
listen处理 简单一点的返回就accept返回-1就直接return了,但详细一点可以继续细分:
说明一下ECONNABORTED,这涉及TCP三次握手:
首先客户端在connect时,发出第一次握手SYN
此时epoll中listenfd收到响应,进入accept处理,返回SYN+ACK第二次握手,并等待第三次握手
如果客户端返回ACK则accept成功,但如果客户端此时返回的是RST,就是这个异常事件了,但这个异常事件会被内核处理,上层直接跳过就好了。
前面说到RST是很偶然的情况,所以一般不进行处理也行。包括被系统中断其实也不常见。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void listen_1 () { while (true ) { struct sockaddr_in client_addr; socklen_t len = sizeof (client_addr); int conn1 = accept (listenfd1, (struct sockaddr*)&client_addr, &len); if (conn1 == -1 ) { if (errno == EAGAIN || errno == EWOULDBLOCK) return ; else if (errno == ECONNABORTED || errno == EINTR) continue ; else { LOG_DEBUG ("server accept error" ); continue ; } } uint32_t connEvent = CONNEVENT; epoller.addFd (conn1, connEvent); string ip = string (inet_ntoa (client_addr.sin_addr)); usermap.ins_conn1_ip (conn1,ip); LOG_DEBUG ("server accept ip-%s" ,ip.c_str ()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void listen_2 () { while (true ) { struct sockaddr_in client_addr; socklen_t len = sizeof (client_addr); int conn2 = accept (listenfd2, (struct sockaddr*)&client_addr, &len); if (conn2 == -1 ) { if (errno == EAGAIN || errno == EWOULDBLOCK) return ; else if (errno == ECONNABORTED || errno == EINTR) continue ; else { LOG_DEBUG ("server accept error" ); continue ; } } uint32_t connEvent = CONNEVENT; epoller.addFd (conn2, connEvent); string ip = string (inet_ntoa (client_addr.sin_addr)); usermap.ins_ip_conn2 (ip,conn2); LOG_DEBUG ("server accept [2] ip-%s" ,ip.c_str ()); } }
初始化监听端口 命令主线程端口为9000,发送线程端口为8000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 const int port1 = 9000 ;const int port2 = 8000 ;int listenfd1;int listenfd2;const uint32_t LISTENEVENT = EPOLLRDHUP | EPOLLET | EPOLLIN;void init_all_Socket () { init_one_Socket (listenfd1,port1); init_one_Socket (listenfd2,port2); } void init_one_Socket (int & listenfd, const int port) { listenfd = socket (AF_INET,SOCK_STREAM,IPPROTO_TCP); if (listenfd < 0 ) { LOG_ERROR ("create listen socket error, port-%d" ,port); exit (1 ); } struct sockaddr_in socketaddr; socketaddr.sin_family = AF_INET; socketaddr.sin_port = htons (port); socketaddr.sin_addr.s_addr = htonl (INADDR_ANY); int optval = 1 ; int ret = setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof (int )); if (ret == -1 ) { LOG_ERROR ("set socket setsockopt error !" ); close (listenfd); exit (1 ); } if (bind (listenfd,(struct sockaddr *)&socketaddr,sizeof (socketaddr))==-1 ) { LOG_ERROR ("bind port-%d error !" ,port); close (listenfd); exit (1 ); } if (listen (listenfd,SOMAXCONN) == -1 ) { LOG_ERROR ("listen port-%d error!" , port); close (listenfd); exit (1 ); } uint32_t listenEvent = LISTENEVENT; if (!epoller.addFd (listenfd, listenEvent)) { LOG_ERROR ("add listen to epoll error!" ); close (listenfd); exit (1 ); } LOG_INFO ("server listenning to port-%d" , port); }
说明一下SO_REUSEADDR
SO_REUSEADDR:
端口复用
一般来说,一个端口释放后会等待一会之后才能再被使用,因为主动关闭会time_wait
SO_REUSEADDR只有针对time-wait连接,确保server重启成功的这一个作用
linux系统time-wait连接持续时间为1min。
SOL_SOCKET表示在套接字级别上设置选项
optval=1:不等于0打开,等于0关闭
SO_REUSEADDR 是让端口释放后立即就可以被再次使用。
SO_REUSEPORT 是让多个进程可以绑定相同端口,并发性更好,可扩展性强
日志系统 之前的博客有完整的从头编写的步骤,可以完整地学习:WebServer模块单元测试 | JySama ,在日志系统
部分。
阻塞队列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 #ifndef BLOCKQUEUE_H #define BLOCKQUEUE_H #include <mutex> #include <queue> #include <condition_variable> #include <cassert> #include <chrono> template <class T >class blockqueue { private : std::queue<T> que; std::mutex mux; std::condition_variable condprod; std::condition_variable condcons; size_t size; bool isclose; public : blockqueue (int maxsize = 1024 ):size (maxsize),isclose (false ) { assert (maxsize>0 ); } ~blockqueue () { } void close () ; void clear () ; bool empty () ; bool full () ; void push (const T &task) ; bool pop (T& task) ; bool pop (T& task, int timeout) ; }; template <class T >bool blockqueue<T>::empty (){ std::lock_guard<std::mutex> locker (mux) ; return que.empty (); } template <class T >bool blockqueue<T>::full (){ std::lock_guard<std::mutex> locker (mux) ; return que.size ()>=size; } template <class T >void blockqueue<T>::push (const T &task){ std::unique_lock<std::mutex> locker (mux) ; while (que.size ()>=size) condprod.wait (locker); if (isclose) return ; que.push (task); condcons.notify_one (); } template <class T >bool blockqueue<T>::pop (T& task){ std::unique_lock<std::mutex> locker (mux) ; while (que.empty () and !isclose) condcons.wait (locker); if (isclose) return false ; task = que.front (); que.pop (); condprod.notify_one (); return true ; } template <class T >bool blockqueue<T>::pop (T& task, int timeout){ std::unique_lock<std::mutex> locker (mux) ; while (que.empty () and !isclose) if (condcons.wait_for (locker,std::chrono::seconds (timeout)) == std::cv_status::timeout) return false ; if (isclose) return false ; task = que.front (); que.pop (); condprod.notify_one (); return true ; } template <class T >void blockqueue<T>::close (){ std::lock_guard<std::mutex> locker (mux) ; clear (); isclose = true ; condprod.notify_all (); condcons.notify_all (); } template <class T >void blockqueue<T>::clear (){ std::queue<T> empty; std::swap (empty, que); } #endif
上层日志系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #ifndef LOG_H #define LOG_H #include <mutex> #include "time.h" #include <stdarg.h> #include <string> #include <stdio.h> #include <cassert> #include <thread> #include <dirent.h> #include <sys/stat.h> #include "blockqueue.h" using namespace std;class Log { private : Log (); public : Log (Log const &) = delete ; Log& operator =(Log const &) = delete ; ~Log (); static Log* instance () ; void init (int level=1 , const char * fpath = "./log" ,int maxqueue_size=1024 ,int threadnum=1 ) ; void setlevel (int level) {loglevel = level;} int getlevel () {return loglevel;} bool isopen () {return logisopen;} void createthread (int threadnum) ; static void logthread () ; void write (int level, const char *format,...) ; void close () ; private : void asyncwrite () ; void changefile (struct tm *nowtime) ; struct tm * gettime (); private : static const int maxlines = 52000 ; FILE *logfp; int linecounts; int filenum; const char * path; int logday; bool isasync; bool logisopen; int loglevel; unique_ptr<blockqueue<string>> blockque; mutex mux; }; #define LOG_BASE(level, format, ...) \ do {\ Log* log = Log::instance();\ if (log->isopen() && log->getlevel() <= level) {\ log->write(level, format, ##__VA_ARGS__); \ }\ } while(0); #define LOG_DEBUG(format, ...) do {LOG_BASE(0, format, ##__VA_ARGS__)} while(0); #define LOG_INFO(format, ...) do {LOG_BASE(1, format, ##__VA_ARGS__)} while(0); #define LOG_WARN(format, ...) do {LOG_BASE(2, format, ##__VA_ARGS__)} while(0); #define LOG_ERROR(format, ...) do {LOG_BASE(3, format, ##__VA_ARGS__)} while(0); #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 #include "log.h" using namespace std;Log* Log::instance () { static Log instance; return &instance; } struct tm * Log::gettime (){ struct tm *nowtime; time_t t; t = time (NULL ); nowtime = localtime (&t); return nowtime; } void Log::changefile (struct tm *nowtime) { linecounts++; if (logday != nowtime->tm_mday || linecounts == maxlines) { char newname[48 ]; if (logday != nowtime->tm_mday) { logday = nowtime->tm_mday; linecounts = 0 ; filenum = 0 ; snprintf (newname, 48 , "%s/%d-%02d-%02d_log%05d.txt" , path, nowtime->tm_year+1900 , nowtime->tm_mon+1 , nowtime->tm_mday, filenum); } else { linecounts = 0 ; filenum++; snprintf (newname, 48 , "%s/%d-%02d-%02d_log%05d.txt" , path, nowtime->tm_year+1900 , nowtime->tm_mon+1 , nowtime->tm_mday, filenum); } fflush (logfp); fclose (logfp); logfp = fopen (newname,"w" ); assert (logfp != nullptr ); } } void Log::write (int level, const char *format,...) { struct tm *nowtime = gettime (); char infobuffer[128 ]; char timebuffer[36 ]; string allinfo; switch (level) { case 0 : allinfo += "[debug]" ; break ; case 1 : allinfo += "[info]" ; break ; case 2 : allinfo += "[warning]" ; break ; case 3 : allinfo += "[error]" ; break ; default : allinfo += "[info]" ; break ; } snprintf (timebuffer,36 , "%d-%02d-%02d_%02d:%02d:%02d:" , nowtime->tm_year+1900 , nowtime->tm_mon+1 , nowtime->tm_mday, nowtime->tm_hour,nowtime->tm_min,nowtime->tm_sec); allinfo += string (timebuffer); va_list vaList; va_start (vaList,format); vsnprintf (infobuffer,128 ,format,vaList); va_end (vaList); allinfo += string (infobuffer)+"\n" ; if (isasync && !blockque->full ()) blockque->push (allinfo); else { lock_guard<mutex> locker (mux); changefile (nowtime); fputs (allinfo.c_str (),logfp); fflush (logfp); } } Log::Log () { linecounts = -1 ; filenum = 0 ; isasync = false ; blockque = nullptr ; logday = 0 ; logfp = nullptr ; logisopen = false ; } void Log::close () { logisopen = false ; if (isasync) { while (!blockque->empty ()); blockque->close (); } if (logfp) { lock_guard<mutex> locker (mux) ; fflush (logfp); fclose (logfp); logfp = nullptr ; } } Log::~Log () { close (); } void Log::logthread () { Log::instance ()->asyncwrite (); } void Log::init (int level, const char * fpath,int maxqueue_size,int threadnum) { if (logisopen == true ) return ; logisopen = true ; loglevel = level; if (maxqueue_size>0 ) { isasync = true ; unique_ptr<blockqueue<string>> que (new blockqueue <string>(maxqueue_size)); blockque = move (que); createthread (threadnum); } else isasync = false ; struct tm *nowtime = gettime (); logday = nowtime->tm_mday; path = fpath; char filename[48 ]; snprintf (filename, 48 , "%s/%d-%02d-%02d_log%05d.txt" , path, nowtime->tm_year+1900 , nowtime->tm_mon+1 , nowtime->tm_mday, filenum); if (opendir (path) == NULL ) mkdir (path,0777 ); logfp = fopen (filename,"w" ); assert (logfp!=nullptr ); } void Log::asyncwrite () { string str; while (blockque->pop (str)) { struct tm * nowtime = gettime (); lock_guard<mutex> locker (mux) ; changefile (nowtime); fputs (str.c_str (),logfp); fflush (logfp); } } void Log::createthread (int threadnum) { for (int i=0 ;i<threadnum;i++) { thread (logthread).detach (); } }
聊天记录存储 有了日志系统,要不要也设置一个聊天记录系统呢?实际上聊天记录要比日志系统简单很多,因为日志系统要把多个线程的日志记录写到一个文件,而聊天记录的存储实际上只是单线程记录的。因为设置了oneshot,在一次响应的过程中不会被其他线程响应,因此一个user的聊天记录存储工作就在这个线程里。
其次,聊天记录是同步的还是异步的,这也需要考量一番。我习惯把日志系统设置为异步的,是因为日志系统可以看成“汇聚所有应写的日志”,放入阻塞队列里用工作线程慢慢写入日志即可,这里面还有一个要点就是写入的文件在一段时间永远是同一个。
而聊天记录不同,一个user的聊天记录应该保存在一个文件里(目前先只考虑一个文件,而不考虑文件行数过多切换文件的情况),这样多个user在操作时就有多个文件的打开关闭。这样子用一个阻塞队列把这些记录汇集起来从逻辑上来说是有点奇怪的:
一方面工作线程在获取阻塞队列的信息时把文件切换来切换去
一方面这个系统(如果真设计出来)保存的行数信息、天数信息都没什么用。
以及,因为聊天记录的字节大小不像一般的log记录那么精简,如果用阻塞队列很可能会有一段延时,导致记录的时间不对
因此,聊天记录同步存储即可,就在task内完成记录,因为task本身就是一个线程,这也该是线程完成的事情。并且每次只需要userid即可知道写哪个文件,所以实际上写一个函数即可。
每记录一次都打开、关闭文件,而不是当用户时登录时打开文件并记录映射,等用户退出时关闭文件,因为经验不足还无法预知用户会干什么事情以及退出代码写的对不对,所以先这样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> #include <string> #include "time.h" bool msg_log (string userid, string& msglog) { string filename = userid+".txt" ; FILE *fp = fopen (filename.c_str (),"a" ); if (fp == nullptr ) return false ; struct tm *nowtime; time_t t; t = time (NULL ); nowtime = localtime (&t); char timebuffer[36 ]; snprintf (timebuffer,36 , "%d-%02d-%02d_%02d:%02d:%02d:" , nowtime->tm_year+1900 , nowtime->tm_mon+1 , nowtime->tm_mday, nowtime->tm_hour,nowtime->tm_min,nowtime->tm_sec); string allmsg = string (timebuffer)+msglog; fputs (allmsg.c_str (),fp); fclose (fp); return true ; }
退出函数 写一个退出函数,在服务器开启时用一个线程接收退出信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const string exitpwd = "jysama" ;void deal_close () { char buf[64 ]; const string exitstr = "exit -p" +exitpwd+"\n" ; const string quitstr = "quit -p" +exitpwd+"\n" ; while (fgets (buf, sizeof (buf), stdin) != NULL ) { if (strcmp (buf,exitstr.c_str ())==0 ) break ; else if (strcmp (buf,quitstr.c_str ())==0 ) break ; else cout << "error" << endl; bzero (buf,sizeof (buf)); } }
顶层 所有工作已经完成,现在进行封装。
比较复杂,不放代码了。。。
数据库建立 1 2 3 4 5 6 7 8 9 // 建立yourdb库 create database yourdb; // 创建user表 USE yourdb; CREATE TABLE user( userid char(50) NULL, password char(50) NULL )ENGINE=InnoDB;
makefile g++ -o:
-O
: 同时减少代码的长度和执行时间,其效果等价于 -O1
-O0
: 表示不做优化
-O1
: 表示默认优化
-O2
: 告诉 g++ 产生尽可能小和尽可能快的代码。除了完成-O1
的优化之外,还进行一些额外的调整工作,如指令调整等
-O3
: 包括循环展开和其他一些与处理性相关的优化工作,选项将使编译的速度比 -O
慢,但通常产生的代码执行速度会更快。
g++ -Wall:-Wall
打印警告信息
1 2 3 4 5 目标...: 依赖... 命令1 命令2 ... 注意每条命令前必须有且仅有一个 tab 保持缩进,这是语法要求。
1 2 3 4 5 6 7 8 9 CXX = g++ CFLAGS = -std=c++14 -O2 -Wall TARGET = server OBJS = global_variables.cpp log.cpp usermap.cpp sqlconnpool.cpp udp_hole_punch.cpp server.cpp main.cpp server: $(OBJS) $(CXX) $(CFLAGS) $(OBJS) -o $(TARGET) -lpthread -lmysqlclient clean: rm -f $(TARGET)
编译 激动人心的时刻到了,因为一直是在markdown手写的,没有编写边测试,所以也不知道有多少错。
没什么大的bug,接下来还有一些问题要调。