讓我們停下來(lái),回想一下之前的內(nèi)容
listen),開(kāi)啟事務(wù)循環(huán)handle_loopprepare_worker,開(kāi)啟兩種線程就真正開(kāi)始工作了string_t 不打算詳細(xì)講解,因?yàn)椴⒉皇鞘裁春玫脑O(shè)計(jì),但是只需要將接口,改成C風(fēng)格的就不錯(cuò),但是有一個(gè)致命的缺點(diǎn),就是這不是二進(jìn)制字符串
\0這個(gè)字符,實(shí)際上要設(shè)計(jì)就需要重新設(shè)計(jì)。2016-08-28 修復(fù)上述問(wèn)題,具體可以參看源碼,現(xiàn)在支持二進(jìn)制數(shù)據(jù)
萬(wàn)事開(kāi)頭難,當(dāng)你在鍵盤上打下第一句代碼的時(shí)候你就成功了。看永遠(yuǎn)都只能是談?wù)劚?,雖說(shuō)談兵也需要技術(shù)
F12后自己查看交互報(bào)文,再專業(yè)一些使用Wireshark這類專業(yè)抓包軟件也未嘗不可,以瀏覽器為例:
Response HeadersHTTP/1.1 200 OKContent-Length: 377710那在C語(yǔ)言中,或者說(shuō)在任何語(yǔ)言中,都沒(méi)什么特別好的辦法,就是用字符串構(gòu)造報(bào)文了。作為一個(gè)標(biāo)準(zhǔn)庫(kù)比較貧瘠的語(yǔ)言,這就要我們多做一點(diǎn)工作,這也是為什么要自己寫一個(gè)字符串結(jié)構(gòu)體的原因所在。
Linux提供的擴(kuò)展gnu99字符串函數(shù),原因是因?yàn)镃-Style字符串是以\0作為結(jié)束符的。現(xiàn)在我們規(guī)定一下,我們這個(gè)服務(wù)器的響應(yīng)報(bào)文會(huì)包含的部分
HTTP/VER STATUS_CODE STATUS_MESSAGE\r\nDate: xxx\r\n
Content-Type: xxx\r\nContent-Length: xxx\r\Connection: xxx\r\n\r\nmake_response中的write_to_buf也就是構(gòu)造報(bào)文階段
__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此時(shí)并不是標(biāo)準(zhǔn)的格式字符串,但這個(gè)變量里面有我們需要的資源
/* 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ù),其實(shí)此處可以將這些語(yǔ)句合并起來(lái)寫,而不是分別調(diào)用,十分浪費(fèi)。但這么寫比較清晰
其中在生成時(shí)間的時(shí)候,我使用的是預(yù)定義好的靜態(tài)字符串?dāng)?shù)組來(lái)幫助我,可很好的猜到這些date_xxx數(shù)組里放的都是些什么,無(wú)非就是一些時(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ù)器上的實(shí)體資源,那就找到它 */
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é)過(guò)Linux系統(tǒng)編程的人肯定知道,這是共享內(nèi)存的一種最簡(jiǎn)單高效的方式。看不太懂的可以去查詢APUE或者網(wǎng)上資源很多,這是很重要的一個(gè)知識(shí)點(diǎn)。大致的功能就是將一個(gè)文件打開(kāi),并映射到內(nèi)存中,這個(gè)內(nèi)存可以在多個(gè)進(jìn)程間共享MAP_SHARED也可以不共享MAP_PRIVATE,這樣我們就能像數(shù)組一樣對(duì)其進(jìn)行讀取操作了。
至于make_response_page的代碼就不貼源碼了,因?yàn)榇a幾乎都是在做檢測(cè)的工作,例如安全之類的事情,以及方法分配,只需要掃一眼就能夠很清楚的理解了。
worker_thread中去那這個(gè)就簡(jiǎn)單很多了,直接貼上代碼
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;
}
/* 在這個(gè)地方就是前面所說(shuō)的那個(gè)EPIPE錯(cuò)誤 */
else /* if (EPIPE == errno) */
/* 對(duì)端關(guān)閉了連接 */
return HANDLE_WRITE_FAILURE;
}
else if (0 == count)
return HANDLE_WRITE_FAILURE;
nbyte -= count;
}
return HANDLE_WRITE_SUCCESS;
}
epoll很容易就實(shí)現(xiàn)了。