技术: 一个HTTPd的简单实现

闲来无事,不如写一个 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
/**
* basic HTTP server
*
* (C) 2017 Merlin <wizardmerlin945@gmail.com>
* http://www.merlinblog.site
*
*/

#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();

// Server端连接套接字
bool create();
bool bind ( const int port );
bool listen() const;
int accept ( Socket& ) const;

// Client 连接套接字
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
/**
* basic HTTP server
*
* (C) 2017 Merlin <wizardmerlin945@gmail.com>
* http://www.merlinblog.site
*
*/

#include "Socket.h"
#include "string.h"
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <iostream>

Socket::Socket() : m_sock(-1)
{
//用 bzero 也可以
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;
}

// TIME_WAIT - argh (避免 2MSL时间 端口不可重用)
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
{
// Replace SO_NOSIGPIPE with MSG_NOSIGNAL if not found
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) //参考 errno.h
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)
{
//exit(1);
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
/**
* basic HTTP server
*
* (C) 2017 Merlin <wizardmerlin945@gmail.com>
* http://www.merlinblog.site
*
*/

#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
/**
* basic HTTP server
*
* (C) 2017 Merlin <wizardmerlin945@gmail.com>
* http://www.merlinblog.site
*
*/

#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()
{
//std::cout <<"destroying socket\n";
}


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; //直接用 c 串初始化 std::string

return *this;
}

int ServerSocket::accept ( ServerSocket& sock )
{
return Socket::accept ( sock );
//throw SocketException ( "Could not accept socket." );
}

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
/**
* basic HTTP server
*
* (C) 2017 Merlin <wizardmerlin945@gmail.com>
* http://www.merlinblog.site
*
*/

#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
/**
* basic HTTP server
*
* (C) 2017 Merlin <wizardmerlin945@gmail.com>
* http://www.merlinblog.site
*
*/

#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; //5个等级的日志
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
/**
* basic HTTP server
*
* (C) 2017 Merlin <wizardmerlin945@gmail.com>
* http://www.merlinblog.site
*
*/

#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;
// Create the socket
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(); //开始 守护进程 的代码

// fork error
if (i<0) {
throw SocketException("Cannot fork proccess. Maybe already running?!");
}

// parent exits
if (i<=0)
{
Log->out << " [OK]\n\n";
} else {
exit(0);
}

writePidFile(); //把 pid 写到文件

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;
}

/** 下面进入 epoll 逻辑 */
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;
}

// accept incoming connection
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;

// cycle through connections
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;

// parse request
string command, url, proto;
conn->readBuf.seekg(ios_base::beg);
conn->readBuf >> command >> url >> proto;

conn->keepAlive = true;

// find out the extension of file requested!
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);
}

// Self statistics
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;

// Call php !
if (extn == "php" || extn == "php5") {

pid_t nPid;
int pipeto[2]; /* pipe to feed the exec'ed program input */
int pipefrom[2]; /* pipe to get the exec'ed program output */

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 )
{
/* dup pipe read/write to stdin/stdout */
dup2( pipeto[0], STDIN_FILENO );
dup2( pipefrom[1], STDOUT_FILENO );
/* close unnecessary pipe descriptors for a clean environment */
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 unused pipe ends. This is especially important for the
* pipefrom[1] write descriptor, otherwise readFromPipe will never
* get an EOF. */
close( pipeto[0] );
close( pipefrom[1] );

//nPid2 = fork();
//if ( nPid2 < 0 )
//{
//perror( "fork() 2" );
//exit(255);
//}
///else if ( nPid2 == 0 )
{
/* Close pipe write descriptor, or we will never know when the
* writer process closes its end of the pipe and stops feeding the
* exec'ed program. */
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";
//*conn->sock << header.str();

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);
}
}

// worker finishes
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 it works in another thread
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;
}

// Add to read buffer if read was successful
conn->readBuf.write(data, len);
conn->readBuf.flush();
stat.bytes_accepted += len;

// handle request and generate response, if header is arrived
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即可。::苦笑::

文章目录
  1. 1. 实现思路
  2. 2. 具体实现
    1. 2.1. 封装 Socket
    2. 2.2. 异常类
    3. 2.3. 日之类
    4. 2.4. HTTPd核心逻辑
    5. 2.5. 配置文件
  3. 3. 运行效果
  4. 4. 小结
|