前面鋪墊了非阻塞 connect 以及 HTTP 請求, 本節就已 unp 書上的例子實現一個簡單的 web 客戶端程序.
客戶程序
使用非阻塞式connect 保證同時能建立多個 TCP 連接.
如果使用阻塞式 connect, 每次都必須等待上一個連接成功後才能建立下一個連接; 而我們所寫的客戶程序只是將第一個連接單獨執行, 之後的多個連接再並行連接.
完整代碼 : client_web.c
#include "web.h"
#define MAXFILES 64
#define PORT "80"
/* connect 連接的狀態 */
#define F_CONNECTING 1 /* 連接中 */
#define F_READING 2 /* 已經連接, 正在讀 */
#define F_DONE 4 /* 已完成 */
#define GET_CMD "GET %s HTTP/1.1\r\n\r\n"
struct file{
char *f_name;
char *f_host;
int fd;
int f_flags;
}file[MAXFILES];
int nonblock(int);
void home_page(const char *, const char *);
void start_connect(struct file *);
void write_get_cmd(struct file *);
int tcp_connect(const char *, const char *);
struct addrinfo * host_serv(const char *, const char *, int, int);
/*
* nconn : 當前創建連接的 TCP 個數
* nlefttoconn : 沒有連接的 TCP 個數
* nlefttoread : 在準備讀取的 TCP 個數
* maxfd : 打開的最大文件描述符
*/
int nconn, nfiles, nlefttoconn, nlefttoread, maxfd;
fd_set rset, wset;
#include "client_web.h"
#include <time.h>
int main(int argc, char *argv[]){
...
// 獲取需要連接的網頁
nfiles = argc - 4 > MAXFILES ? MAXFILES : argc - 4;
for(int i = 0; i < nfiles; ++i){
file[i].f_name = argv[i+4];
file[i].f_host = argv[2];
file[i].f_flags = 0;
}
home_start = clock();
home_page(argv[2], argv[3]); /* 與主頁先保持連接 */
home_end = clock();
...
connectnum_start = clock();
while(nlefttoread > 0){
/* 同時能建立的連接個數 */
while(nconn < maxnconn && nlefttoconn > 0){
for(i = 0; i < nfiles; ++i)
if(file[i].f_flags == 0)
break;
if(i == nfiles)
errPrint("nlefttoconn but nothing found");
start_connect(&file[i]);
nconn++;
nlefttoconn--;
}
rs = rset;
ws = wset;
n = select(maxfd + 1, &rs, &ws, NULL, NULL);
// 向服務端發送要獲取的信息, 然後關閉寫監聽後在來監聽讀
for(i = 0; i < nfiles; ++i){
flags = file[i].f_flags;
if(flags == 0 || (flags & F_DONE))
continue;
fd = file[i].fd;
if(flags & F_CONNECTING && (FD_ISSET(fd, &rs) || FD_ISSET(fd, &ws))){
n = sizeof(err);
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &n) < 0 || err != 0){
error("connect error");
}
/* 寫完後, 關閉寫監聽, 打開讀監聽 */
printf("connect established for %s\n", file[i].f_name);
FD_CLR(fd, &wset);
write_get_cmd(&file[i]);
}
else if(flags & F_READING && FD_ISSET(fd, &rs)){
if((n = read(fd, buf, sizeof(buf))) == 0){
printf("end-of-file on %s\n", file[i].f_name);
close(fd);
file[i].f_flags = F_DONE;
FD_CLR(fd, &rset);
nconn--;
nlefttoread--;
}
else
printf("read %d bytes from %s\n", n, file[i].f_name);
}
}
}
connectnum_end = clock();
printf("\n\nhome connet time : %f\n", (double)(home_end - home_start)/CLOCKS_PER_SEC);
printf("num connect time : %f\n", (double)(connectnum_end - connectnum_start)/CLOCKS_PER_SEC);
printf("sum time : %f\n", (double)(connectnum_end - home_start)/CLOCKS_PER_SEC);
return 0;
}
程序運行 :
make
./web 5 www.foobar.com / image1.gif image2.gif image2.gif image3.gif image4.gif image5.gif image6.gif image7.gif
程序傳參含義
./web 同時允許最大連接數 主頁 文件名 文件路徑 ...
多連接性能驗證
現在我們改變最大連接數來驗證同時多個連接的性能 :
./web 1 www.foobar.com / image1.gif image2.gif image2.gif image3.gif image4.gif image5.gif image6.gif image7.gif
./web 3 www.foobar.com / image1.gif image2.gif image2.gif image3.gif image4.gif image5.gif image6.gif image7.gif
./web 7 www.foobar.com / image1.gif image2.gif image2.gif image3.gif image4.gif image5.gif image6.gif image7.gif
雖然總時間的結果會因爲網絡而有差異, 但基本上多連接執行的時間是越來越少, 性能也就越來越好.
注意 : 並非客戶端併發連接數越多, 帶來的性能提升就更大; 相反, 可能會導致性能下降, 特別是在網絡狀況差的時候.
小結
併發連接能適當的提升性能, 但不是絕對的.
- 掌握 connect 非阻塞編程.
- 該程序也可以改爲多線程.