int main()
{
int maxfd,s_ret,i;
int client_fd;
int sockfd;//socket返回值
struct sockaddr_in server_sockaddr,client_sockaddr;
int sin_size, recvbytes;
char ipstr[16];
char cmd[BUFFER_SIZE];
sin_size=sizeof(struct sockaddr);
fd_set r_set,listen_set;//定义一个集合
printf("\t\t****************author by sastar****************\n");
/*建立socket连接*/
if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)
{
perror("socket");
exit(1);
}
printf("Socket id = %d\n",sockfd);
/*设置sockaddr_in 结构体中相关参数,设置套接字属性*/
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
//inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr)//单独设置可接受的ip
server_sockaddr.sin_addr.s_addr = INADDR_ANY;
//bzero(&(server_sockaddr.sin_zero), 8);
memset(&(server_sockaddr.sin_zero),0,8);
int j = 1;/* 使得重复使用本地地址与套接字进行绑定 */
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));
/*绑定函数bind*/
if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr))== -1)//将server_sockaddr结构体的ip和端口号公布
{
perror("bind 1");
exit(1);
}
printf("Bind success!\n");
/*调用listen函数*/
if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
{
perror("listen");
exit(1);
}
printf("listening...\n");
FD_ZERO(&r_set);
FD_SET(sockfd,&r_set);//将socket放入可读集合中
maxfd=sockfd+1;//描述符的最大值
while(1)
{
listen_set=r_set;
s_ret=select(maxfd,&listen_set,NULL,NULL,NULL);
printf("%d is select detected\n", s_ret);//检测到的就绪fd数量
if(s_ret<0)
{
perror("select error");
exit(-1);
}
else
{
for(i=0;iif(FD_ISSET(i,&listen_set))//判断i是否是在可读\可写集合中
{
printf("%d is ready\n", i);//哪个fd就绪
if(i==sockfd)
{
/*调用accept函数,等待客户端的连接*/
if ((client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, &sin_size)) == -1)//将客户端传入的值传入client_sockaddr,长度为struct sockaddr
{
perror("accept");
exit(1);
}
else
{
inet_ntop(AF_INET,(char *)&client_sockaddr.sin_addr.s_addr,ipstr,16);
printf("\nnew client id=%d\nip:%s\nsrc port:%d\n",client_fd,ipstr,client_sockaddr.sin_port);
FD_SET(client_fd,&r_set);//将描述符添加到读写集合中
//FD_SET(client_fd,&write_set);
maxfd=(maxfd>(client_fd+1)) ? maxfd : (client_fd+1);
}
}
else
{
memset(cmd,0,sizeof(cmd));
if ((recvbytes = recv(i,cmd,sizeof(cmd),0)) == -1)//接受客户端发送的命令
{
perror("recv");
close(i);
FD_CLR(i,&r_set);//将描述符从读写集合中清除
}
//printf("%s\n", buf);
if(strncmp(cmd,"servls",6)==0)
{
lsfile(i,cmd);
}
else if(strncmp(cmd,"down ",5)==0)
{
sendfile(i,cmd);
}
else if(strncmp(cmd,"up ",3)==0)
{
recvfile(i,cmd);
}
else if(strncmp(cmd,"exit",4)==0)
{
close(i);
FD_CLR(i,&r_set);//将描述符从读写集合中清除
}
}
}
else{
printf("%d is not ready\n", i);//未就绪的是哪些fd
}
}
}
}
close(sockfd);
exit(0);
}
在测试过程中,发现每次客户端connect时,select总是检测到fd为3的文件描述符就绪。如下图:左侧上下两个终端窗口为客户端,右侧终端窗口为服务器端
可以看到描述符4/5是新连接的客户端,那3是什么呢?每次有新的客户端连接select就会检测到。于是通过查看服务器进程的fd(命令:lsof -p <服务器PID号>),如下图:
可以看到,FD那一列中FD0、1、2是系统自带的stdin、stdout、stderr描述符,FD3(即每次新的客户端连接会触发该FD)是LISTEN描述符,端口号是上述代码中设置的,FD4、FD5是新的客户端与服务器建立的文件描述符(通过accept函数),以后的客户端与服务器的数据传输都通过FD4/FD5传输。所以每次客户端传数据到服务器,都是FD4或者FD5被select检测到,如下图:
可以看到此时打印的消息是“FD5 is ready”。