47. web 客戶程序


前面鋪墊了非阻塞 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 非阻塞編程.
  • 該程序也可以改爲多線程.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章