用 c++ 的 thread 寫一個 http 伺服器:
// tcpserver.cpp
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <thread>
using namespace std;
template<class T>
class superBuffer {
private:
unsigned msize;
T* address;
public:
const unsigned &length = msize;
T* const &array = address;
~superBuffer() { if (address != nullptr) delete [ ] address; }
superBuffer(unsigned len){ msize = len; address = new T [msize+1]; }
};
class strBuffer: public superBuffer<char> {
public:
unsigned size;
void flush() {size = 0; array[0] = 0;}
strBuffer(int len): superBuffer(len) {flush() ; }
strBuffer & operator ~ (void) {flush() ; return *this;}
strBuffer & operator - (unsigned len) {if (size >= len) size -= len; array[size]=0; return *this;}
strBuffer & operator + (const char *src){ return (*this)(src); }
strBuffer & operator + (strBuffer &msg) { return (*this)(msg.array); }
strBuffer & operator ( )(const char *fmt,...){ // object can be a callable function
va_list args;
va_start(args, fmt);
size += vsprintf(array + size, fmt, args);
va_end(args);
return *this;
}
};
bool telnet(char * buffer, const char *cmd) {
if (buffer[0]==cmd[0] && buffer[1]==cmd[1] && buffer[2]==cmd[2] && buffer[3]==cmd[3]) return true;
return false;
}
char *httpGET(char * buffer, int bufsize) {
auto src = buffer;
if (bufsize >=15 && src[0]=='G' && src[1]=='E' && src[2]=='T' && src[3]==' ') {
char *header = strstr(src,"HTTP/1.");
if (header == nullptr) return nullptr;
src += 4;
int len = header - src - 1;
for (int i=0; i<len; i++) buffer[i] = src[i];
buffer[len] = 0;
bufsize = len;
return header;
}
return nullptr;
}
bool httpSendDoc(int fd, bool ok = true) {
auto msg = strBuffer(32);
if (ok) msg("HTTP/1.0 200 OK\n");
else msg("HTTP/1.0 404 Not Found\n");
auto str = strBuffer(256);
str + msg;
str + "Server: Welcome\n";
str + "Content-Type: text/html\n";
str ( "Content-Length: %11ld\n", msg.size);
str + "Connection: close\n\n";
str + msg;
write(fd, str.array, str.size);
return ok;
}
bool httpSendfile(int fd, char *pathsrc) {
auto str = strBuffer(2000);
str + "." + pathsrc;
FILE *fin = fopen(str.array, "rb");
if (fin == nullptr) return httpSendDoc(fd, false);
fseek(fin,0, SEEK_END);
long fsize = ftell(fin);
fseek(fin,0, SEEK_SET);
(~str)+ "HTTP/1.0 200 OK\n";
str + "Server: Welcome\n";
str + "Cache-Control: must-revalidate, post-check=0, pre-check=0\n";
str + "Expires: 0\n";
str + "Content-Type: application/octet-stream\n";
str ( "Content-Length: %11ld\n", fsize);//bug: change %11d to %11ld
str + "Content-Disposition: attachment; filename=tempxxxx\n\n";
// bug: need double \n\n at the end of header to start file transfer
write(fd, str.array, str.size);
do {
str.size = fread(str.array, 1, 2000, fin);
if( str.size > 0 ) write(fd, str.array, str.size);
} while (str.size == 2000);
fclose(fin);
return true;
}
class tcpServer {
private:
static bool serverRunning;
static int sessions;
static void tcpSession(int fd);
static bool readEvent(int fd, int timeout);
int tcpPort;
public:
tcpServer(int port) { tcpPort = port; }
void operator ( ) ( ) { // this is a functor
int servicefd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in host;
host.sin_family = AF_INET; // IPv4
host.sin_port = htons(tcpPort);
inet_pton(AF_INET, "127.0.0.1", &host.sin_addr) ;
socklen_t hostsize = sizeof(host);
int ok = 1;
setsockopt(servicefd, SOL_SOCKET, SO_REUSEADDR, &ok, sizeof(ok));
bind(servicefd, (sockaddr *) &host, sizeof(host));
listen(servicefd, 10);
while (serverRunning) try {
if (readEvent(servicefd, 2) && sessions<10) {
thread(tcpSession, accept(servicefd, (sockaddr *) &host, &hostsize)).detach();
}
} catch(const char *msg) { printf("%s\n",msg); break; }
while (sessions > 0) sleep(1);
close(servicefd);
}
};
int tcpServer::sessions = 0;
bool tcpServer::serverRunning = true;
bool tcpServer::readEvent(int fd, int timeout) {
fd_set rdset;
FD_ZERO(&rdset);
FD_SET(fd, &rdset);
timeval timer;
timer.tv_sec = timeout;
timer.tv_usec = 0; // + 0u second
if (select(fd+1, &rdset, 0, 0, &timer) < 0) throw "SOCKET ERROR!";
if (FD_ISSET(fd, &rdset)) return true; // socket read
return false;// socket timeout
}
void tcpServer::tcpSession(int fd) {
sessions++;
auto const buffer = new char[2000];
while (serverRunning) try {
if (readEvent(fd, 5)) {
int size = read(fd, buffer, 1999);
if (size==0) throw "Read size=0";
buffer[size] =0;
if (telnet(buffer, "exit")) serverRunning = false;
else if (telnet(buffer, "quit")) httpSendDoc(fd);
else if (httpGET(buffer, size)) {
if( buffer[0]=='/' && buffer[1]==0 ) httpSendDoc(fd);
else httpSendfile(fd, buffer);
}
throw "Connection: close";
} else throw "Time is up";;
} catch (const char *msg) { printf("%s\n",msg); break; }
delete [] buffer;
close(fd);
sessions--;
}
int main( ){
auto A = tcpServer(5000); // 構建 port = 5000 的 functor
auto tA = thread(A); // 啟動 functor 的 thread
tA.join( );// 等待 thread 結束
}
用 g++ tcpserver.cpp -pthread && ./a.out 編譯並執行