讓我們停下來,回想一下之前的內(nèi)容
listen
),開啟事務(wù)循環(huán)handle_loop
prepare_worker
,開啟兩種線程就真正開始工作了string_t
不打算詳細(xì)講解,因為并不是什么好的設(shè)計,但是只需要將接口,改成C風(fēng)格的就不錯,但是有一個致命的缺點,就是這不是二進(jìn)制字符串
\0
這個字符,實際上要設(shè)計就需要重新設(shè)計。2016-08-28 修復(fù)上述問題,具體可以參看源碼,現(xiàn)在支持二進(jìn)制數(shù)據(jù)
萬事開頭難,當(dāng)你在鍵盤上打下第一句代碼的時候你就成功了。看永遠(yuǎn)都只能是談?wù)劚?,雖說談兵也需要技術(shù)
F12
后自己查看交互報文,再專業(yè)一些使用Wireshark
這類專業(yè)抓包軟件也未嘗不可,以瀏覽器為例:Response Headers
HTTP/1.1 200 OK
Content-Length: 377710
那在C語言中,或者說在任何語言中,都沒什么特別好的辦法,就是用字符串構(gòu)造報文了。作為一個標(biāo)準(zhǔn)庫比較貧瘠的語言,這就要我們多做一點工作,這也是為什么要自己寫一個字符串結(jié)構(gòu)體的原因所在。
Linux
提供的擴(kuò)展gnu99
字符串函數(shù),原因是因為C-Style字符串是以\0
作為結(jié)束符的。現(xiàn)在我們規(guī)定一下,我們這個服務(wù)器的響應(yīng)報文會包含的部分
HTTP/VER STATUS_CODE STATUS_MESSAGE\r\n
Date: xxx\r\n
Content-Type: xxx\r\n
Content-Length: xxx\r\
Connection: xxx\r\n
\r\n
make_response
中的write_to_buf
也就是構(gòu)造報文階段
__thread char local_write_buf[CONN_BUF_SIZE] = {0};
static int write_to_buf(conn_client * restrict client, // connection client message
const char * const * restrict status, int rsource_size) {
#define STATUS_CODE 0
#define STATUS_TITLE 1
#define STATUS_CONTENT 2
char * write_buf = &local_write_buf[0]; /* Local write buffer */
string_t resource = client->conn_res.requ_res_path; /* Resource that peer request */
string_t w_buf = client->w_buf; /* Real data buffer */
int w_count = 0;
struct tm * utc; /* Get GMT time Format */
time_t now;
time(&now);
utc = gmtime(&now);/* Same As before */
utc
此時并不是標(biāo)準(zhǔn)的格式字符串,但這個變量里面有我們需要的資源
/* Construct the HTTP head */
w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "%s %s %s\r\n",
http_ver[client->conn_res.request_http_v],
status[STATUS_CODE], status[STATUS_TITLE]);
w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "Date: %s, %02d %s %d %02d:%02d:%02d GMT\r\n",
date_week[utc->tm_wday], utc->tm_mday,
date_month[utc->tm_mon], 1900+utc->tm_year,
utc->tm_hour, utc->tm_min, utc->tm_sec);
w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "Content-Type: %s\r\n", content_type[client->conn_res.content_type]);
w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "Content-Length: %u\r\n", 0 == rsource_size
? (unsigned int)strlen(status[2]):(unsigned int)rsource_size);
w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "Connection: close\r\n");
w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "\r\n");
write_buf[w_count] = '\0';
從上往下依次是剛才我在上面介紹的順序,使用的是snprintf
函數(shù),其實此處可以將這些語句合并起來寫,而不是分別調(diào)用,十分浪費。但這么寫比較清晰
其中在生成時間的時候,我使用的是預(yù)定義好的靜態(tài)字符串?dāng)?shù)組來幫助我,可很好的猜到這些date_xxx
數(shù)組里放的都是些什么,無非就是一些時間的縮寫。
/* 寫入緩沖區(qū) */
append_string(w_buf, STRING(write_buf));
client->w_buf_offset = w_count;
/* If Server do not wanna to sent local file */
if (0 == rsource_size) { /* GET Method */
append_string(w_buf, STRING(status[STATUS_CONTENT]));
snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, status[2]);
return 0;
} else if (-1 == rsource_size) { /* HEAD Method */
return 0;
}
/* 如果需要服務(wù)器上的實體資源,那就找到它 */
int fd = open(resource->str, O_RDONLY);
if (fd < 0) {
return -1; /* Write again */
}
/* 將資源文件映射到內(nèi)存里,這樣就能很好的操作 */
char *file_map = mmap(NULL, (size_t)rsource_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (NULL == file_map) {
assert(file_map != NULL);
}
close(fd);
/* 存入緩沖區(qū) */
append_string(w_buf, file_map, rsource_size);
client->w_buf_offset += rsource_size;
munmap(file_map, (unsigned int)rsource_size);
return 0;
}
open
, mmap
, munmap
,學(xué)過Linux
系統(tǒng)編程的人肯定知道,這是共享內(nèi)存的一種最簡單高效的方式。看不太懂的可以去查詢APUE
或者網(wǎng)上資源很多,這是很重要的一個知識點。大致的功能就是將一個文件打開,并映射到內(nèi)存中,這個內(nèi)存可以在多個進(jìn)程間共享MAP_SHARED
也可以不共享MAP_PRIVATE
,這樣我們就能像數(shù)組一樣對其進(jìn)行讀取操作了。
至于make_response_page
的代碼就不貼源碼了,因為代碼幾乎都是在做檢測的工作,例如安全之類的事情,以及方法分配,只需要掃一眼就能夠很清楚的理解了。
worker_thread
中去那這個就簡單很多了,直接貼上代碼
HANDLE_STATUS handle_write(conn_client * client) {
/* String Version */
char* w_buf = client->w_buf->str;
int w_offset = client->w_buf_offset;
int nbyte = w_offset;
int count = 0;
int fd = client->file_dsp;
while (nbyte > 0) {
w_buf += count;
count = write(fd, w_buf, nbyte);
if (count < 0) {
if (EAGAIN == errno || EWOULDBLOCK == errno) {
/* 如果發(fā)送緩沖區(qū)不夠容納所有的,那就下次再發(fā) */
memcpy(client->w_buf->str, w_buf, strlen(w_buf));
client->w_buf_offset = nbyte;
return HANDLE_WRITE_AGAIN;
}
/* 在這個地方就是前面所說的那個EPIPE錯誤 */
else /* if (EPIPE == errno) */
/* 對端關(guān)閉了連接 */
return HANDLE_WRITE_FAILURE;
}
else if (0 == count)
return HANDLE_WRITE_FAILURE;
nbyte -= count;
}
return HANDLE_WRITE_SUCCESS;
}
epoll
很容易就實現(xiàn)了。