闲来无事,不如写一个 HTTP 服务器练练手?(详见博客,作品页面)
实现思路 Linux 环境下写 Socket 代码有一段时间了,一直给别人的代码做修修补补,感觉没有太多踏实感。
闲来无事,不如写一个 HTTP 服务器练练手?(顺便检测一下 linux 环境编程的能力)
整理下思路:
客户端请求 –> 服务端(返回 http 协议报文)
思路非常简单,就是简单的代码读写? 在处理 Socket 的代码时涉及了诸多内容,根本不是组包发包这么简单:
用面向对象的方式(继承,封装,多态)处理
linux 进程,管道,线程
网络编程 epoll (因为限制了最大处理请求数量大概 50 个左右)
客户端 keep alive 逻辑
配置文件处理, pid运行权限绑定
等等…
其实还好, 基本上完整的代码已经全部在下面贴出来了。 (如果想要完整的工程,请发我邮件)没有华丽的算法!所以,算法狗,请走开。
具体实现 封装 Socket 让 ServerSocket 继承 Socket, 完成相关的操作.
具体代码如下:Socket.h
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 #ifndef Socket_class #define Socket_class #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <unistd.h> #include <string> #include <arpa/inet.h> const int MAXHOSTNAME = 200 ;const int MAXCONNECTIONS = 50 ; const int MAXRECV = 500 ; const int MAXSEND = 5000 ; class Socket { private : int m_sock; sockaddr_in m_addr; public : Socket(); virtual ~Socket(); bool create () ; bool bind ( const int port ) ; bool listen () const ; int accept ( Socket& ) const ; bool connect ( const std ::string host, const int port ) ; int send ( const char *, int , int & ) const ; int recv ( char *, int & ) const ; void set_non_blocking ( const bool ) ; int getFd () const { return m_sock; } bool is_valid () const { return m_sock != -1 ; } }; #endif
然后逐个实现一下:Socket.cpp
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 #include "Socket.h" #include "string.h" #include <string.h> #include <errno.h> #include <fcntl.h> #include <iostream> Socket::Socket() : m_sock(-1 ) { memset (&m_addr, 0 , sizeof (m_addr)); } Socket::~Socket() { if (is_valid()) { ::close(m_sock); } } bool Socket::create(){ m_sock = socket(AF_INET, SOCK_STREAM,0 ); if (!is_valid()) { return false ; } int on = 1 ; if (setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof (on)) == -1 ) return false ; return true ; } bool Socket::bind(const int port){ if (!is_valid()) { return false ; } m_addr.sin_family = AF_INET; m_addr.sin_addr.s_addr = INADDR_ANY; m_addr.sin_port = htons(port); int bind_return = ::bind(m_sock, (struct sockaddr *)&m_addr, sizeof (m_addr)); if (bind_return == -1 ) { return false ; } return true ; } bool Socket::listen() const { if (!is_valid()) { return false ; } int listen_return = ::listen(m_sock, MAXCONNECTIONS); if (listen_return == -1 ) { return false ; } return true ; } int Socket::accept(Socket &new_socket) const { int addr_length = sizeof (m_addr); new_socket.m_sock = ::accept(m_sock, (sockaddr *)&m_addr, (socklen_t *)&addr_length); return new_socket.m_sock; } int Socket::send(const char *data, int size, int &sentbytes) const { int status = sentbytes = ::send(m_sock, data, size, SO_NOSIGPIPE); if (status == -1 ) { return errno; } else { return 9999 ; } } int Socket::recv(char *buf, int &readbytes) const { int status = readbytes = ::recv(m_sock, buf, MAXRECV, 0 ); if (status == -1 ) { return errno; } else if (status == 0 ) { return ECONNRESET; } else { return 9999 ; } } bool Socket::connect(const std ::string host, const int port){ if (!is_valid()) return false ; m_addr.sin_family = AF_INET; m_addr.sin_port = htons(port); int status = inet_pton(AF_INET, host.c_str(), &m_addr.sin_addr); if (errno == EAFNOSUPPORT) return false ; status = ::connect(m_sock, (sockaddr *)&m_addr, sizeof (m_addr)); if (status == 0 ) return true ; else return false ; } void Socket::set_non_blocking(const bool b){ int opts; opts = fcntl(m_sock, F_GETFL); if (opts < 0 ) { return ; } if (b) opts = (opts | O_NONBLOCK); else opts = (opts & ~O_NONBLOCK); fcntl(m_sock, F_SETFL, opts); }
基本的 Socket 差不多就这样吧,基本满足要求了。 但是为了用起来方面,可以用 面向对象的方式,还要进行封装。
此次封装,针对服务器端,不在关心bind, listen之类的具体细节,主要关心读写操作。 (面向对象的方式封装,并且重载一下读写方法; 同时需要处理异常,自定义一个异常类 SocketException
)
声明如下: ServerSocket.h
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 #ifndef ServerSocket_class #define ServerSocket_class #include "Socket.h" class ServerSocket : private Socket{ public : ServerSocket ( int port ); ServerSocket (){ }; virtual ~ServerSocket(); const ServerSocket& operator << ( const std ::string & ); const ServerSocket& operator >> ( std ::string & ); void send ( const char *, int ) ; void recv ( char *) ; int accept ( ServerSocket& ) ; int getLastIOResult () ; int getTransferredBytes () ; int getFd () ; private : int m_lastIOResult; int m_TransferredBytes; }; #endif
具体实现,如下: ServerSocket.cpp
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 #include "ServerSocket.h" #include "SocketException.h" #include <iostream> ServerSocket::ServerSocket ( int port ) { if ( ! Socket::create() ) { throw SocketException ( "Could not create server socket." ); } if ( ! Socket::bind ( port ) ) { throw SocketException ( "Could not bind to port." ); } if ( ! Socket::listen() ) { throw SocketException ( "Could not listen to socket." ); } } ServerSocket::~ServerSocket() { } const ServerSocket& ServerSocket::operator << ( const std ::string & s ){ m_lastIOResult = Socket::send( s.c_str(), s.size(), m_TransferredBytes ); return *this ; } void ServerSocket::send( const char * data, int size ) { m_lastIOResult = Socket::send( data, size, m_TransferredBytes ); } void ServerSocket::recv( char * data ) { m_lastIOResult = Socket::recv( data, m_TransferredBytes ); } const ServerSocket& ServerSocket::operator >> ( std ::string & s ){ char *buf = new char [MAXRECV]; m_lastIOResult = Socket::recv( buf, m_TransferredBytes ); s = buf; return *this ; } int ServerSocket::accept ( ServerSocket& sock ){ return Socket::accept ( sock ); } int ServerSocket::getLastIOResult() { return m_lastIOResult; } int ServerSocket::getTransferredBytes() { return m_TransferredBytes; } int ServerSocket::getFd() { return Socket::getFd(); }
异常类 至于异常类,代码如下: SocketException.h
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 #ifndef SocketException_class #define SocketException_class #include <string> class SocketException { public : SocketException ( std ::string s ) : m_s ( s ) {}; SocketException (){}; std ::string description () { return m_s; } private : std ::string m_s; }; #endif
上面就是核心代码了, 其实第一次封装完毕基本 Socket 代码之后, 第二次封装专注于 Server 端读写的 Socket就会非常简单.
日之类 为了后面核心逻辑处理起来,调试方便,还是要一个工具类 Logger.h
, 懒汉式实现,不能跨线程。(用宏代替也可以, 懒得说)
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 #ifndef Logger_class #define Logger_class #include <iostream> #include <sstream> using namespace std ;class Logger { private : static Logger* m_instance; int m_debugmode; public : ostringstream debug, out, err, log , errlog; void flush () ; static Logger* getInstance () ; private : Logger(): m_debugmode(0 ) {} Logger() {} }; #define Log Logger::getInstance() #endif
现实如下: Logger.cpp
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 #include "Logger.h" #include <iostream> #include <sstream> #include <stdio.h> using namespace std ;Logger* Logger::m_instance = NULL ; Logger* Logger::getInstance() { if (m_instance == NULL ) { m_instance = new Logger(); } return m_instance; } void Logger::flush() { if (m_debugmode) { cout << debug.str(); } debug.str("" ); cout << out.str(); ::flush(cout ); out.str("" ); cerr << err.str(); ::flush(cerr ); err.str("" ); }
使用大致如下:
1 2 Log->err << "\nException in main:\n" << endl ; Log->flush();
HTTPd核心逻辑 开始以为很简单,就是服务端 socket 写回 HTTP协议 的HTML响应报文。 写起来完全不是那么回事儿,好多细节要处理
处理配置文件
处理响应数据传输
处理字符串拼装
处理多线程 (poll + 多线程)
处理守护进程方式
处理非root权限运行
处理 keep alive
具体解释,请看注释吧: (要求你有 APUE的基础,即 linux 系统编程基础)
main.cpp :
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 int main ( int argc, char ** argv ) { try { Log->out << "\nStartup Merlin HTTPd... " ; string running_user="root" ; int port = 80 ; config.htdocs = "./htdocs" ; ifstream cfg ("httpd.ini" , ios::in) ; if (cfg.is_open()) { char buf[512 ]; string s; while (!cfg.eof()) { cfg.getline(buf, 512 ); if (cfg.gcount() > 0 ) { if (buf[0 ] == '#' ) continue ; stringstream ss((string)buf, stringstream::in | stringstream::out); string key, value; ss >> key; if (key == "User" ) ss >> running_user; if (key == "Port" ) ss >> port; if (key == "Htdocs" ) ss >> config.htdocs; } } cfg.close(); } stat.bytes_sent = 0 ; stat.bytes_accepted = 0 ; stat.all_conns = 1 ; stat.active_conns = 0 ; vector < clientConn* > conns; ServerSocket server ( port ) ; ServerSocket* new_client = new ServerSocket(); Log->out << " [OK]\n" ; Log->out << "Dropping root privileges... " ; struct passwd *pw = getpwnam (running_user .c_str ()); if (pw == NULL ) { throw SocketException("Cannot find user" ); } setuid(pw->pw_uid); setgid(pw->pw_gid); Log->out << " [OK]\n" ; Log->out << "Entering daemon mode... " ; int i=fork(); if (i<0 ) { throw SocketException("Cannot fork proccess. Maybe already running?!" ); } if (i<=0 ) { Log->out << " [OK]\n\n" ; } else { exit (0 ); } writePidFile(); pollfd fds[500 ]; fds[0 ].fd = server.getFd(); fds[0 ].events = POLLIN; fds[0 ].revents = 0 ; int allconn = 1 ; while ( true ) { Log->flush(); for (int i=0 ; i<allconn; ++i) { fds[i].revents = 0 ; } int status; do { status = poll(fds, allconn, -1 ); } while (status < 0 && errno == EAGAIN); if (status <= 0 ) { Log->errlog << "Error during poll(). errno = " << errno << endl ; Log->flush(); continue ; } if (fds[0 ].revents & POLLIN) { int result = server.accept ( *new_client ); if (result == -1 ) { Log->errlog << "Accept failed with errno: " << errno << endl ; Log->flush(); } clientConn* new_conn = new clientConn(); new_conn->busy = false ; new_conn->sock = new_client; new_conn->destroy = false ; conns.push_back(new_conn); new_client = new ServerSocket(); fds[allconn].fd = result; fds[allconn].events = POLLIN; fds[allconn].revents = 0 ; ++allconn; ++stat.all_conns; } stat.active_conns = allconn; cycleConns(conns, fds, allconn); } } catch ( SocketException& e ) { Log->err << "\nException in main:\n" << e.description() << endl ; Log->flush(); } return 0 ; }
其实实现细节:
处理请求的代码:
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 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 struct stat_def { long bytes_accepted, bytes_sent, active_conns, all_conns; } stat; struct clientConn { ServerSocket *sock; map < string , string > headers; stringstream readBuf; bool keepAlive, busy, destroy; }; void *handleRequest (void *_conn) { clientConn *conn = (clientConn*)_conn; try { conn->busy = true ; string command, url, proto; conn->readBuf.seekg(ios_base::beg); conn->readBuf >> command >> url >> proto; conn->keepAlive = true ; string reqfile = url; size_t found = reqfile.find("?" ); string querystring; if (found != string ::npos) { querystring = reqfile.substr(found+1 , reqfile.length()-found-1 ); reqfile = reqfile.substr(0 , found); } if (reqfile == "/_stat_" ) { send_Stat(conn); pthread_exit(NULL ); } find_and_replace(reqfile, "\\" , "/" ); if (url.find("../" ) != string ::npos) { send_NotFound(conn); pthread_exit(NULL ); } string full_file = reqfile; found = reqfile.rfind("/" ); if (found != string ::npos) reqfile = reqfile.substr(found+1 , reqfile.length()-found-1 ); if (reqfile == "" ) { reqfile = "index.html" ; full_file = full_file+reqfile; } string extn = reqfile; found = extn.rfind("." ); if (found != string ::npos) extn = extn.substr(found+1 , extn.length()-found-1 ); string path = config.htdocs + full_file; if (extn == "php" || extn == "php5" ) { pid_t nPid; int pipeto[2 ]; int pipefrom[2 ]; if ( pipe( pipeto ) != 0 ) { perror( "pipe() to" ); exit (255 ); } if ( pipe( pipefrom ) != 0 ) { perror( "pipe() from" ); exit (255 ); } nPid = fork(); if ( nPid < 0 ) { perror( "fork() 1" ); exit (255 ); } else if ( nPid == 0 ) { dup2( pipeto[0 ], STDIN_FILENO ); dup2( pipefrom[1 ], STDOUT_FILENO ); close( pipeto[0 ] ); close( pipeto[1 ] ); close( pipefrom[0 ] ); close( pipefrom[1 ] ); string param1 = "SCRIPT_FILENAME=" + path; querystring = "QUERY_STRING=" + querystring; const char * envp[4 ] = {"REQUEST_METHOD=GET" , param1.c_str(), querystring.c_str(), NULL }; execle("/usr/bin/php5-cgi" , "/usr/bin/php5-cgi" , NULL , envp); perror( "execle()" ); pthread_exit(NULL ); } else { pid_t nPid2; close( pipeto[0 ] ); close( pipefrom[1 ] ); { close( pipeto[1 ] ); FILE *stream; int ch; if ( (stream = fdopen( pipefrom[0 ], "r" )) == NULL ) { perror( "fdopen() r" ); exit (255 ); } stringstream ss; string end = "\r\n\r\n" ; char last[4 ] = {'a' , 'a' , 'a' , 'a' }; long size = 0 , header_l; while ( (ch = getc( stream )) != EOF ) { ++size; ss.write( (char *)&ch, 1 ); last[0 ] = last[1 ]; last[1 ] = last[2 ]; last[2 ] = last[3 ]; last[3 ] = ch; if (end == (string )last) header_l = size; } fclose( stream ); ss.flush(); size = ss.str().length() - header_l; stringstream header; header << "HTTP/1.0 200 OK\r\n" ; if (conn->keepAlive) header << "Connection: Keep-Alive\r\n" ; else header << "Connection: Close\r\n" ; header << "Content-Length:" << size << "\r\n" ; header << ss.str(); header.flush(); header.seekg(ios_base::beg); char memblock[MAXSEND+5 ]; long trans = 0 ; while (size > trans) { header.read(memblock, MAXSEND); long read = header.gcount(); long tr = 0 ; do { conn->sock->send(memblock+tr, read-tr); int result = conn->sock->getLastIOResult(); if (result == EINTR) continue ; if (result == 9999 ) tr += conn->sock->getTransferredBytes(); } while (read != tr); trans += read; } stat.bytes_sent += size; } } } string content_type; if (extn == "htm" || extn == "html" ) { content_type = "text/html" ; } else if (extn == "jpeg" ) { content_type = "image/jpeg" ; } else if (extn == "png" ) { content_type = "image/x-png" ; } else if (extn == "tiff" ) { content_type = "image/tiff" ; } else content_type = "text/plain" ; if (command == "GET" ) { ifstream file(path.c_str(), ios::in|ios::binary|ios::ate); if (file.is_open()) { ifstream::pos_type size = file.tellg(); ifstream::pos_type trans = 0 ; ostringstream header; header << "HTTP/1.0 200 OK\r\n" ; if (conn->keepAlive) header << "Connection: Keep-Alive\r\n" ; else header << "Connection: Close\r\n" ; header << "Content-Type: " << content_type << "\r\n" ; header << "Content-Length:" << size << "\r\n" ; header << "\r\n" ; *conn->sock << header.str(); char memblock[MAXSEND+5 ]; file.seekg (0 , ios::beg); while (size > trans) { file.read (memblock, MAXSEND); long read = file.gcount(); long tr = 0 ; do { conn->sock->send(memblock+tr, read-tr); int result = conn->sock->getLastIOResult(); if (result == EINTR) continue ; if (result == 9999 ) tr += conn->sock->getTransferredBytes(); } while (read != tr); trans += read; } file.close(); stat.bytes_sent += size; } else { send_NotFound(conn); pthread_exit(NULL ); } } conn->busy = false ; conn->readBuf.str("" ); pthread_exit(NULL ); } catch ( SocketException& e ) { Log->err << "Exception in respone worker thread:\n" << e.description() << endl ; Log->flush(); conn->busy = false ; conn->readBuf.str("" ); pthread_exit(NULL ); } }
轮询客户端连接的代码:
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 void cycleConns (vector < clientConn* > &conns, pollfd fds[], int &allconn) { try { vector < clientConn* >::iterator itConn; int connIndex = 1 ; for (itConn = conns.begin(); itConn != conns.end();) { clientConn *conn = *itConn; if (fds[connIndex].revents & POLLHUP) { delete conn->sock; itConn = conns.erase(itConn); for (int i=connIndex; i<allconn-1 ; ++i) fds[i] = fds[i+1 ]; --allconn; continue ; } if (!(fds[connIndex].revents & POLLIN)) { ++itConn; ++connIndex; continue ; } if (conn->busy) { ++itConn; continue ; } if (conn->destroy) { itConn = conns.erase(itConn); continue ; } char *data = new char [MAXRECV+5 ]; conn->sock->recv(data); int len = conn->sock->getTransferredBytes(); if (len <= 0 ) { ++itConn; ++connIndex; continue ; } conn->readBuf.write(data, len); conn->readBuf.flush(); stat.bytes_accepted += len; conn->readBuf.seekg(ios_base::beg); pthread_t t; if (conn->readBuf.str().find("\r\n\r\n" ) != string ::npos) int rc = pthread_create(&t, NULL , handleRequest, (void *)conn); ++itConn; ++connIndex; } } catch ( SocketException& e ) { Log->err << "Error in connection cycle.\n" << e.description() << endl ; Log->flush(); } }
组包逻辑:
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 void send_NotFound (clientConn *conn) { ostringstream header; header << "HTTP/1.0 200 OK\r\n" ; if (conn->keepAlive) header << "Connection: Keep-Alive\r\n" ; else header << "Connection: Close\r\n" ; header << "Content-length: 40\r\n\r\n404 - Not Found\r\nPowered by merlin_httpd" ; *conn->sock << header.str(); stat.bytes_sent += header.str().length(); conn->busy = false ; if (!conn->keepAlive) { delete conn->sock; conn->destroy = true ; } conn->readBuf.str("" ); } void send_Stat (clientConn *conn) { ostringstream content; content << "<h2>httpd v0.5b</h2><br/>" << endl ; content << "Actice connections: " << stat.active_conns << "<br/>" ; content << "<h3>Overall statistics:</h3>" ; content << "All connections: " << stat.all_conns << "<br/>" ; content << "Bytes received: " << stat.bytes_accepted << "<br/>" ; content << "Bytes sent: " << stat.bytes_sent << "<br/>" ; ostringstream header; header << "HTTP/1.0 200 OK\r\n" ; if (conn->keepAlive) header << "Connection: Keep-Alive\r\n" ; else header << "Connection: Close\r\n" ; header << "Content-Type: " << "text/html" << "\r\n" ; header << "Content-length: " << content.str().length() <<"\r\n" ; header << "\r\n" ; *conn->sock << header.str(); *conn->sock << content.str(); stat.bytes_sent += content.str().length() + header.str().length(); conn->busy = false ; if (!conn->keepAlive) { delete conn->sock; conn->destroy = true ; } conn->readBuf.str("" ); }
附加一个写 pid 文件的代码:
1 2 3 4 5 6 7 8 9 10 11 void writePidFile () { pid_t pid = getpid(); ofstream file ("httpd.pid" ) ; if (file.is_open()) { file << pid << endl ; } else { Log->errlog << "Cannot write pid file 'httpd.pid'" << endl ; Log->flush(); } file.close(); }
配置文件 核心逻辑处理的时候,如果提供了外部配置文件,就会根据配置文件的要求进行配置:
httpd.ini :
1 2 3 4 5 6 7 # htppd config file # Which port to bind to Port 8090 # User to run as, after binding to port User merlin
运行效果 编译 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # Makefile for httpd # main_objects = Logger.o ServerSocket.o Socket.o main.o all : httpd CXXFLAGS = -DDEBUG -O0 httpd: $(main_objects) g++ -pthread -DDEBUG -O0 -o main_httpd $(main_objects) Logger: Logger.cpp Socket: Socket.cpp ServerSocket: ServerSocket.cpp main: main.cpp clean: rm -f *.o main
运行 : 由于是以守护进程, 所以跑起来立即返回:
浏览器访问效果 :
杀死 server 的话 kill -9 <pid>
, 其中 pid 可以查看同级目录下的 httpd.pid 文件.
小结 其实这个小程序里面,HTTP服务器并不是重点,重点在于对于基本代码的封装,重用。 其次,C++ 网络编程的代码,受限于最开始设计者设计的结构体,想简洁都不行,只能自己封装。
至于 linux 下的多进程 fork, pthread,管道,epoll 这属于平台范畴内的东西,不算重点。
慢慢一点点写下来,收获还是不小的;网络编程,就是繁琐了一点,其实还是蛮有趣的。
btw: 如果是初学者,只要先看了 APUE, UNP volume 1即可。::苦笑::