下文实现的程序内容是一个web客户端
这个客户端向web一次性请求多个图片
这些图片并非串行传输,而是同时建立多个连接,在进行传输。
这里利用了非负责的connect, 每次connect并进行三次握手时,先直接返回, 然后一系列设置。
在main的for循环中, 用select检测那个链接是否可写或可读, 可写时三次握手结束,需要发送“GET HTTP”请求。
可读时,指需要读该文件。而且每次只读一次,而不是用 while循环进行阻塞IO。
每当一个文件读完了,就关闭那个连接。
#include "web.h"
int main(int argc, char **argv){
int i, fd, n, maxconn, flags, error;
char buf[MAXLINE];
fd_set rs, ws;
if(argc < 5)
err_quit("usage: web <#conns><hostn><homepage><file1>...");
maxconn = atoi(argv[1]); //获取最大连接数
nfiles = min(argc - 4, MAXFILES); //传递的文件数量,最多为20,为何减四?agc0,连接数,hostname,"/"
for(i = 0; i < nfiles; i++){
file[i].f_name = argv[i + 4];
file[i].f_host = argv[2]; //每个文件的主机名都是一样的
file[i].f_flags = 0; //=0即为尚未处理的意思
}
printf("nfiles = %d\n", nfiles);
home_page(argv[2], argv[3]); //发出读取主页的命令
FD_ZERO(&rset);
FD_ZERO(&wset);
maxfd = -1;
//nlefttoconn是尚未有TCP连接的文件数, nleftread是尚未读取的TCP连接数, nconn是当前打开着的连接数。
nlefttoread = nlefttoconn = nfiles; //nfiles是总共需要读的文件数
//当前打开的连接数
nconn = 0;
while(nlefttoread > 0){
while(nconn < maxnconn && nlefttoconn > 0){
for(i = 0;i < nfiles;i++){
if(file[i].f_flags == 0) //找到一个尚未处理的文件
break;
}
//i等于nfiles,说明没找到,显然有错,即有文件未连接,但是却找不到尚未处理的文件
if(i == nfiles)
err_quit("nlefttoconn = %d but nothing found", nelfttoconn);
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].f_fd;
//如果这个描述符是已连接,却说可读,说明可以发GET请求头了
if(flags & F_CONNECTING && (FD_ISSET(fd, &rs)|| FD_ISSET(fd, &ws))){
n = sizeof(error);
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &n) < 0 || error != 0){
err_ret("nonblocking connect failed for %s", file[i].f_name);
}
printf("connection established for %s\n", file[o].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(bug))) == 0){
pritnf("end-of-file on %s\n", file[i].f_name);
Close(fd); //有文件读完了
file[i].f_flags = FDONE;
FD_CLR(fd, &rest);
nconn--;
nlefttoread--;
}else{
printf("read %d bytes from %s\n", n , file[i].f_name);
}
}
}
}
}
void home_page(const char *host, const char *fname){
int fd, n;
char line[MAXLINE];
fd = Tcp_connect(host, SERV)
n = snprintf(line, sizeof(line), GET_CMD, fname);
Writen(fd, line, n); //发送Get请求
for(;;){
if( (n = Read(fd, line, MAXLINE)) == 0)
break;
printf("read %d bytes of home page\n", n);
}
printf("end-of-file on home page\n");
Close(fd);
}
void start_connect(struct file *fptr){
int fd, flags, n;
struct addrinfo *ai;
//转换主机名为和服务名
ai = Host_serv(fptr->f_host, SERV, 0, SOCK_STREAM);
//创建一个套接字
fd = Socket(ai->ai_family, ai->ai_socktype, ai->protocol);
fptr->f_fd = fd;
printf("start_connect for %s, fd %d\n", fptr->f_name, fd);
//先置零,然后设置为非阻塞
flags = Fcntl(fd, F_GETFL, 0);
Fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if( (n = connect(fd, ai->ai_addr, ai->ai_addrlen)) < 0){
//发起非阻塞connect, 在还未完全建立连接时,就自动返回负数
//这时候我们可以其他操作,例如设置flag,设置FD等
if(errno != EINPROGRESS)
err_sys("nonblocking connect error");
fptr->f_flags = F_CONNECTING;
FD_SET(fd, &rset);
FD_SET(fd, &wset);
if(fd > maxfd)
maxfd = fd;
}else if(n >= 0) //若连接早已经建立过了,即之前没关掉,那么我们直接发出Get命令即可
write_get_cmd(fptr);
}
void write_get_cmd(struct file *fptr){
int n;
char line[MAXLINE];
n = snprintf(line, sizeof(line), GET_CMD, fptr->f_name);
Writen(fptr->f_fd, line, n);
printf("wrote %d bytes for %s\n", n, fptr->f_name);
//发完Get后,应该进行读取了,故修改状态为reading
fptr->f_flags = F_READING;
//设置读,即如果该描述符可读,则对应select返回
FD_SET(fptr->f_fd, &rset);
if(fptr->f_fd > maxfd)
maxfd = fptr->f_fd;
}