/* 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; //设置地址类型为ipv4;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//转换ip地址字节序,网络地址为INADDR_ANY,这个宏表示本地任意IP地址
servaddr.sin_port = htons(SERV_PORT);//转换端口的字节序。
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中添加监听到的端口;
for ( ; ; ) { //for 用于循环接受有数据请求,要与服务器交互的client的端口;
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\n",
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\n", stderr);
exit(1);
}
FD_SET(connfd, &allset);//向allset套接字描述符集中添加与服务器建立连接并有数据请求的客户端端口;
if (connfd > maxfd)
maxfd = connfd; //若此时建立并有数据请求的客户端已大于原来的套接字描述符最大值则用connfd从置maxfd;
if (i > maxi) //把maxi赋值为当前最大的放置建立连接并有数据请求客户端描述符的索引,以在下面的for循环语句中作为处理客户端请求个数的上限;
maxi = i; /* max index in client[] array */
if (--nready == 0) //若--nready为0,则表示当前的套接字描述符集中只有listenfd这个监听的描述符,没有客户端的数据请求端口,则进行下一轮的select循环;
continue; /* no more readable descriptors */
}
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 */
}
}
}
}
流程: