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 */
}
}
}
}