非阻塞connect:Web客户程序
//web.h头文件
#include "unp.h"
#define MAXFILES 20
#define SERV "80" /* port number or service name */
struct file
{
char * f_name; /* filename */
char * f_host; /* hostname or IPv4/IPv6 address */
int f_fd; /* descriptor */
int f_flags; /* F_xxx below */
} file[MAXFILES];
#define F_CONNECTIONG 1 /* connect() in progress */
#define F_READING 2 /* connect() complete; no reading */
#define F_DONE 4 /* all done */
#define GET_CMD "GET %s HTTP/1.0 \r\n\r\n"
/* globals */
int nconn, nfiles, nlefttoconn, nlefttoread, maxfd;
fd_set rset, wset;
/* function prototypes */
void home_page(const char *, const char *);
void start_connect(struct file *);
void write_get_cmd(struct file *);
main函数
#include "web.h"
int main(int argc, char * * argv)
{
int i, fd, n, maxnconn, flags, error;
char buf[MAXLINE];
fd_set rs, ws;
if(argc < 5)
err_quit("usage: web < # conns> < hostname> < homepage > < file1 > ...");
maxnconn = atoi(argv[1]);
nfiles = min(argc-4, MAXFILES); //用从命令行参数来的相关信息填写file结构
for( i = 0; i < nfiles; i++)
{
file[i].f_name = argv[i+4];
file[i].f_host = argv[2];
file[i].f_flags = 0;
}
printf("nfiles = %d \n", nfiles);
home_page(argv[2], argv[3]);//home_page函数创建一个TCP连接,向服务器发出一个命令,然后读主页,这是第一个连接,在开始并行建立多条连接之前自己
FD_ZERO(&rset);//初始化读描述字集
FD_ZERO(&wset);//初始化写描述字集
maxfd = -1;//maxfd是select使用的最大描述字,初始化成-1,因为描述字是非负的
nlefttoread = nlefttoconn = nfiles; //nlefttoread是剩余要读的文件数(当它到0时程序就结束), nlefttoconn是还需要一个TCP连接的文件数
nconn = 0; //当前打开的连接数(它不能超过第一个命令行参数)
while(nlefttoread > 0)
{
while(nconn < maxnconn && nlefttoconn > 0)
{ /* find a file to read */
for( i = 0; i < nfiles; i++ )
if(file[i].f_flags == 0)
break;
if(i == nfiles)
err_quit("nlefttoconn = %d but nothing found", nlefttoconn);
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;
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);
}
/* connection established */
printf("connection established for %s \n", file[i].f_name);
FD_CLR(fd, &wset); /* no more writeability test */
write_get_cmd(&file[i]); /* write() the GET command */
}
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; /* clears F_READING */
FD_CLR(fd, &rset);
nconn--;
nlefttoread--;
}
else
{
printf("read %d bytes from %s \n", n, file[i].f_name);
}
}
}
}
exit(0);
}
下面是在main函数开始时调用了一次的home_page函数
#include "web.h"
void home_page(const char * host, const char * fname)
{
int fd, n;
char line[MAXLINE];
fd = Tcp_connect(host, SERV); /* blocking connect() */
n = snprintf(line, sizeof(line), GET_CMD, fname); /* 发出一个HTTP GET命令以获取主页 */
Writen(fd, line, n);
for( ; ; )
{
if((n = Read(fd, line, MAXLINE)) == 0)
break; /* server closed connection */
printf("read %d bytes of home page \n", n);
/* do whatever with data */
}
printf("end-of-file on home page \n");
Close(fd);
}
创建套接口,设置非阻塞方式
#include "web.h"
void start_connect(struct file * fptr)
{
int fd, flags, n;
struct addrinfo * ai;
ai = Host_serv(fptr->f_host, SERV, 0, SOCK_STREAM); /* 调用host_serv函数查找和转换主机名和服务名,返回一个指向addrinfo结构的数组的指针 */
fd = Socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); /* 创建TCP套接口 */
fptr->f_fd = fd;
printf("start_connect for %s, fd %d \n", fptr->f_name, fd);
/* Sect socket nonblocking */
flags = Fcntl(fd, F_GETFL, 0);
Fcntl(fd, F_SETFL, flags | O_NONBLOCK); //设置为非阻塞
/* initiate nonblocking connect to the server */
if((n = connect(fd, ai->ai_addr, ai->ai_addrlen)) < 0)
{/* 启动非阻塞connect,并将相应文件的标志置为F_CONNECTING */
if(errno != EINPROGRESS)
err_sys("nonblocking connect error");
fptr->f_flags = F_CONNECTING;
FD_SET(fd, &rset); /* select for reading and writing */
FD_SET(fd, &wset); /* 在读集合和写集合中都打开这个套接口描述字对应的位 */
if(fd > maxfd)
maxfd = fd;
}/* 如果connect返回成功,那么连接已经完成,write_get_cmd函数向服务器发出一个命令 */
else if(n >= 0) /* connect is already done */
write_get_cmd(fptr); /* write() the GET command */
}
我们把connect所用的套接口设置为非阻塞,但永远不恢复它缺省的阻塞模式。这样做没有问题是因为只向该套接口写了少量的数据,从而可以认为这个命令大大小于套接口的发送缓冲区。即使write因为非阻塞标志造成返回的数目小于要写的数目,writen函数也会对此进行处理。让这个套接口非阻塞对后面的read没有什么影响,因此我们总是调用select等待它变为可读。
//函数 write_get_cmd, 它向服务器发出一个HTTP的GET命令
#include "web.h"
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);
fptr->f_flags = F_READING; /* clears F_CONNECTING */
FD_SET(fptr->f_fd, &rset); /* will read server's reply */
if(fptr->f_fd > maxfd)
maxfd = fptr->f_fd;
}
参考:《unix网络编程》卷一