UNIX Network Programming笔记之第六章下篇

    select函数在client和server上的应用。

void str_cli(FILE *fp, int sockfd)   //client中的应用,不过有些不正确
{
    int maxfdp1;
    fd_set rset;
    char sendline[MAXLINE], recvline[MAXLINE];
    maxfdp1 = sockfd + 1;
    FD_ZERO(&rset);
    FD_SET(sockfd, &rset);
    FD_SET(fileno(fp), &rset);
    for(;;){
        Select(maxfdp1, &rset, NULL, NULL, NULL); //select调用,只需要知道套接字是否可读
        if(FD_ISSET(sockfd, &rset)){   //套接字可读
            if(Readline(sockfd, recvline, MAXLINE) == 0)
                err_quit("str_cli: server teminated prematurely");
            Fputs(recvline, stdout);
        }
	if(FD_ISSET(fileno(fp), &rset)){
		if(Fgets(fp, sendline, MAXLINE) == NULL)
			return; //all done
		Writen(sockfd, sendline, strlen(sendline));
        }
    }
}

    有两个错误:1. 在批量处理时,可能会出现问题,标准输入中的EOF不意味着我们也完成了对套接字的读入,可能仍有请求在去往服务器的路上,或仍有应答在返回的客户路上。我们需要的是一种关闭TCP连接一般的方法,给服务器发送FIN,告诉服务器已经完成了数据的发送,但是仍然保持套接字描述符打开以便读取。shutdown函数可以完成任务。

    2. 为提升性能而引入缓存机制增加了网络应用程序的复杂性。从fgets中读取输入,可能仍有数据在标准输入的缓冲区中,并且还有封装的Readline函数,select不可见的数据在Readline缓冲区中。

    shutdown函数

#include <sys/socket.h>
int shutdown(int sockfd, int howto);
//返回:成功则为0,出错则为-1
    howto参数的值:

    SHUT_RD:关闭连接的读这一半

    SHUT_WR:关闭连接的写这一半

    SHUT_RDWR:关闭连接的读写部分

    shutdown函数与close函数的区别:close只是将描述符的引用计数减一,仅在为0时才关闭套接字,而shutdown可以不管引用计数就激发正常的连接终止序列;close是终止读写两个方向的数据传输,shutdown可以控制关闭连接的方向。

    从这也能看出TCP连接是全双工的,并且关闭连接需要四组分节,以下是修订版。

void str_cli(FILE *fp, int sockfd)   //client中的应用,不过有些不正确
{
    int stdineof = 0;    //判断标准输入是否不再输入,为1则不在输入
    int maxfdp1, n;
    fd_set rset;
    char sendline[MAXLINE], recvline[MAXLINE];
    maxfdp1 = sockfd + 1;
    FD_ZERO(&rset);
    FD_SET(sockfd, &rset);
    FD_SET(fileno(fp), &rset);
    for(;;){
        Select(maxfdp1, &rset, NULL, NULL, NULL); //select调用,只需要知道套接字是否可读
        if(FD_ISSET(sockfd, &rset)){   //套接字可读
            if( (n = Read(sockfd, recvline, MAXLINE)) == 0)
                   err_quit("str_cli: server teminated prematurely");
            Write(fileno(stdout),recvline, n);
        }
	if(FD_ISSET(fileno(fp), &rset)){
		if((n = Read(fileno(fp), sendline, MAXLINE)) == 0){
			stdineof = 1;
			Shutdown(sockfd, SHUT_WR);
			FD_CLR(fileno(fp), &rset);   //将标准输入描述符从select的读集合中移出
			continue;
                }			
		Writen(sockfd, sendline, n);
        }
    }

}

    在server上的应用,使用select函数来处理任意个client的单进程程序,而不是为每个client产生一个子进程。

#include "unp.h"

int main()
{
	char buf[MAXLINE];
	fd_set rset, allset;
	int client[FD_SETSIZE];   //用来保存和client建立连接的套接字描述符
	int listenfd, connfd, sockfd, maxfd;
	struct sockaddr_in servaddr;
	int i, maxi, nready;
	ssize_t n;
	
	if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		err_sys("socket error");
	}
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(9877);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0){
		err_sys("socket error");
	}
	if(listen(listenfd, LISTENQ) < 0){
		err_sys("listen error");
	}
	for(i = 0; i < FD_SETSIZE; i++)
		client[i] = -1;
	maxfd = listenfd;
	maxi = -1;
	nready = 0;
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);  //监听套接字listenfd来初始化读集合
	for(;;){
		rset = allset;
		nready = select(maxfd + 1, &rset, NULL, NULL, NULL);  //select 调用
		if(nready < 0) 
			err_sys("select error");
		if(FD_ISSET(listenfd, &rset)){        //用于监听新连接,一旦有client连接,则生成一个新的连接套接字描述符
			if((connfd = accept(listenfd, NULL, NULL)) < 0){
				err_sys("accept error");
			}
			for(i = 0; i < FD_SETSIZE; i++){
				if(client[i] < 0){
					client[i] = connfd;  //保存连接套接字描述符
					break;
				}
			}
			if(i == FD_SETSIZE) err_quit("too many clients");  //连接数超过限制
			FD_SET(connfd, &allset);      //将连接套接字描述符加入集合中
			if(connfd > maxfd) maxfd = connfd;
			if(i > maxi) maxi = i;
			if(--nready <= 0) continue;	//已没有可用的套接字

		}

		for(i = 0; i <= maxi; i++){
			if((sockfd = client[i]) < 0)
				continue;
			if(FD_ISSET(sockfd, &rset)){
				if((n=read(sockfd, buf, MAXLINE)) == 0){
					//client主动关闭了连接
					close(sockfd);
					FD_CLR(sockfd, &allset);
					client[i] = -1;
				}
				else
					Writen(sockfd, buf, n);
				if(--nready <= 0) break;    //已没有可用的套接字
			}
		}
	}
}
    poll函数
#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
//返回:若有就绪描述符则返回其数目,若超时返回0,出错返回-1

struct pollfd{
	int fd;			//要检查的描述符
	short events;		//fd传入的events
	short revents;		//发生在fd上的events
};

要测试的条件由events成员指定, 而函数在相应的revents成员返回该描述符的状态。


在server上的应用

#include     "unp.h"
#include     <limits.h>         /* for OPEN_MAX */

int
main(int argc, char **argv)
{
    int     i, maxi, listenfd, connfd, sockfd;
    int     nready;
    ssize_t n;
    char    buf[MAXLINE];
    socklen_t clilen;
    struct pollfd client[OPEN_MAX];
    struct sockaddr_in cliaddr, servaddr;
    listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ);

    client[0].fd = listenfd;
    client[0].events = POLLRDNORM;
    for (i = 1; i < OPEN_MAX; i++)
        client[i].fd = -1;      /* -1 indicates available entry */
    	maxi = 0;                   /* max index into client[] array */

	for ( ; ; ) {
        nready = Poll(client, maxi + 1, INFTIM);

        if (client[0].revents & POLLRDNORM) {  /* new client connection */
            clilen = sizeof(cliaddr);
            connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);

            for (i = 1; i < OPEN_MAX; i++)
                if (client[i].fd < 0) {
                    client[i].fd = connfd;  /* save descriptor */
                    break;
                }
            if (i == OPEN_MAX)
                err_quit("too many clients");
            client[i].events = POLLRDNORM;
            if (i > maxi)
                maxi = i;       /* max index in client[] array */
             if (--nready <= 0)
                continue;       /* no more readable descriptors */
        }

        for (i = 1; i <= maxi; i++) {       /* check all clients for data */
            if ( (sockfd = client[i].fd) < 0)
                continue;
            if (client[i].revents & (POLLRDNORM | POLLERR)) {
                if ( (n = read(sockfd, buf, MAXLINE)) < 0) {
                    if (errno == ECONNRESET) {
                        /* connection reset by client */
                        Close(sockfd);
                        client[i].fd = -1;
                    } else
                        err_sys("read error");
                } else if (n == 0) {
                    /* connection closed by client */
                    Close(sockfd);
                    client[i].fd = -1;
                } else
                   Writen(sockfd, buf, n);
                if (--nready <= 0)
                        break;               /* no more readable descriptors */
            }
        }
    }
}






    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值