/* server.c */
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE]; //nready存放有数据请求的客户端的个数;FD_SETSIZE是tcp最大连接数,client[FD_SETSIZE] 存放有数据请求的客户端;
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE]; //开辟一块缓冲区,用于存放客户端与服务器端交互时的数据;
char str[INET_ADDRSTRLEN];
socklen_t cliaddr_len;
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0); //socket()打开一个网络通讯端口,返回一个套接字描述符给listenfd;
bzero(&servaddr, sizeof(servaddr)); //首先将结构体清零;
servaddr.sin_family = AF_INET; //设置地址类型为AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//转换ip地址字节序,网络地址为INADDR_ANY,这个宏表示本地任意IP地址
servaddr.sin_port = htons(SERV_PORT);//转换端口的字节序。到目前为止,结构体servaddr的设置已经完毕;
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //将所监听的端口号与服务器的地址、端口绑定;
Listen(listenfd, 20); //listen()声明服务器处于监听状态,并且最多允许有20个客户端处于连接待状态;
maxfd = listenfd; /* initialize */ //将所监听的最大的套接字描述符赋给maxfd;
maxi = -1; /* index into client[] array */
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* -1 indicates available entry */ //FD_SETSIZE的最大值为1024,for循环将client[i]的值设为-1,client[i]在下文中用来保存最小的套接字描述符,这样可以把建立数据请求的端口赋给最前面的client[i];
FD_ZERO(&allset); //将allset套接字描述符集清空。
FD_SET(listenfd, &allset);//向allset套接字描述符集中添加服务器所监听到的端口(即listenfd所接受到的请求);
for ( ; ; ) { //for 用于循环等待接受有数据请求的客户端(用select这个系统调用);
rset = allset; /* structure assignment */ // 把allset套接字描述符集的内容赋给rset;
nready = select(maxfd+1, &rset, NULL, NULL, NULL);//select用于监听多个阻塞的文件描述符,当这些被阻塞的端口有数据请求或要与服务器交互时,select就会返回请求客户端的个数给nready,若没有,则返回0,若select调用出错,则会返回一个负值。由于最后一个关于时间的参数值是null,所以select会一直阻塞等待着client的请求,直到有数据交互的客户端产生。maxfd+1表示集合中描述符的范围即所有描述符的最大值加1,rset则是在关注哪个客户端有数据可读了,就把客户端的套接字描述符添加在rset集合中。
if (nready < 0) //select调用出错时,会返回一个负数给nready。该语句判断select是否调用成功。
perr_exit("select error");
if (FD_ISSET(listenfd, &rset)) { /* new client connection */ //判断listenfd所接受到的客户端的请求是否在rset集合中,这是一个监听到的客户端与所监听客户端中有数据请求的客户端的一个比对,测试该数据请求的客户端是否在监听的队列中。
cliaddr_len = sizeof(cliaddr); //把cliaddr结构体的长度赋给cliaddr_len,作为缓冲区的长度。
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);//接受客户端的连接请求,与客户端建立连接。
printf("received from %s at PORT %d ",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port)); //打印客户端的ip地址和端口号。
for (i = 0; i < FD_SETSIZE; i++) //for循环,i作为client的下标,表示放有数据请求的客户端的最小索引
if (client[i] < 0) {
client[i] = connfd; /* save descriptor */
break; //把有数据请求的客户端的套接字描述符放置到client[i]中最靠前的位置。
}
if (i == FD_SETSIZE) { //若i以达到最大值FD_SETSIZE,则表示有数据请求的客户端已达到FD_SETSIZE
fputs("too many clients ", stderr);
exit(1);
}
FD_SET(connfd, &allset);//向allset套接字描述符集中添加与服务器建立连接并有数据请求的客户端端口;
if (connfd > maxfd)
maxfd = connfd; //若此时建立并有数据请求的客户端已大于原来的套接字描述符最大值则用connfd从置maxfd;
if (i > maxi)
maxi = i; /* max index in client[] array */ //把maxi赋值为当前最大的放置建立连接并有数据请求客户端描述符的索引,以在下面的for循环语句中作为处理客户端请求个数的上限;
if (--nready == 0)
continue; /* no more readable descriptors */ //若--nready为0,则表示当前的套接字描述符集中只有listenfd这个监听的描述符,没有客户端的数据请求端口,则进行下一轮的select循环;
}
for (i = 0; i <= maxi; i++) { //for循环处理有数据请求的客户端;
if ( (sockfd = client[i]) < 0) //把client[i]中存放的客户端的套接字描述符赋给sockfd。若小于0,表示这个client[i]中没有放置套接字描述符。
continue; //继续执行for循环,查找数据请求的客户端;
if (FD_ISSET(sockfd, &rset)) { //测试sockfd是否在rset这个描述符集中;
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) { // 读入客户端的数据,若n等于0表示客户端已经关闭了连接;
/* connection closed by client */
Close(sockfd); //客户端关闭连接了,服务器也关闭与客户端相应的连接;
FD_CLR(sockfd, &allset); //清空与客户端连接的套接字描述符在allset集中;
client[i] = -1; //同时将放置这个客户端套接字的数组位置设为-1,用来存放下一次的客户端数据请求的描述符;
} else { //若n不为0,则处理客户端的数据请求;
int j;
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]); //把客户端发来的数据变为大写;
Write(sockfd, buf, n); //将数据发回客户端;
}
if (--nready == 0) //再次判断nready,若--nready为0,则表示当前的套接字描述符集中只有listenfd这个监听的描述符,没有客户端的数据请求端口,则进行下一轮的select循环;
break; /* no more readable descriptors */
}
}
}
}
这个程序是用select实现服务器与多个客户端进行数据交互的过程。
1、 服务器监听客户端的请求,将这些请求都放在一个队列中。这些客户端套接字描述符赋给allset集、rset集。rset在select中作为一个参数监测客户端是否有数据可读。allset起到一个保存的作用,在每次查找是否有客户端数据请求时,就用allset先初始化rset。
2、 使用select这个系统调用,监听这些阻塞的文件描述符。当这些被阻塞的端口有数据请求或要与服务器交互时,select就会返回请求客户端的个数给nready,若没有,则返回0,若select调用出错,则会返回一个负值。由于最后一个关于时间的参数值是null,所以select会一直阻塞等待着client的请求,直到有数据交互的客户端产生。
与这些有数据请求的客户端连接,connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
用client[i]用来保存用select“过滤”过的客户端的套接字描述符,用-1初始化client[i],可以保证每次放的套接字描述符都是在数组的前面;
(1)for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1;
(2)for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) {
client[i] = connfd; /* save descriptor */
break;
3、用maxi和maxfd分别用来保存数组中放置最大描述符的下标、最大的套接字描述符。
FD_SET(connfd, &allset);
if (connfd > maxfd)
maxfd = connfd; //在select中作为第一个参数用;
if (i > maxi)
maxi = i; //在下文中作为循环处理客户端请求个数的上限;
4、得知共有多少个数据请求端后,并且在client[i]中存放着这些客户端的套接字描述符。对这些客户端的数据进行处理
for (i = 0; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
/* connection closed by client */
Close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
} else {
int j;
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Write(sockfd, buf, n);
}
if (--nready == 0)
break; /* no more readable descriptors */
}
}
跳出for (i = 0; i <= maxi; i++) 循环后,回到地40行的for ( ; ; )循环处,继续用select阻塞接收到的请求,有数据到达的则接收;
相关热门文章
给主人留下些什么吧!~~
评论热议