select工作原理:
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
函数说明:
maxfdp:表示要监听的最大个数,
readfds:表示监听可读
writefds:监听可写
errorfds:监听异常错误
timeout: 监听超时时间,NULL表示阻塞
对监听集合的操作:
void FD_CLR(int fd, fd_set *set);//把某fd文件描述符从集合清除
int FD_ISSET(int fd, fd_set *set);//判断某fd文件描述是否留在集合里--->即发出信号是否为该fd
void FD_SET(int fd, fd_set *set);//设置某fd文件描述到监听集合里
void FD_ZERO(fd_set *set);//清除集合里的文件描述符
原理:
select里面有个监听集合,把需要监听的文件描述符写进集合用FD_SET();
当有某文件描述符发出信号,就会自动把其他文件描述符删除掉,只留下该文件描述符
所以我们需要提前把集合里的文件描述符提前保留下来,通过循环找到文件描述符
并发服务器设计:
1.把socket写进监听集合FD_SET(int fd, fd_set *set)
2.如果监听到的是socket文件描述符发来信号 FD_ISSET(int fd, fd_set *set),表示有新的客户端连接
接受该客户端连接accept(),把通信文件描述符写入监听集合,
3.如果监听到的是通信文件描述符,我们还需通过循环判断该文件描述符是哪个客户端的,确定下文件描述符后
就可以通过该文件描述符通信
#include <unistd.h>
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include<stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string.h>
#include <pthread.h>
//#include"node.h"
//#include"list.h"
//#include<json/json.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
//客户端处理函数
int main()
{
int sockfd,confd[1024]={0};
struct sockaddr_in serveraddr_in,clientaddr_in;
//1.创建套接字
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
┊ perror("socket");
┊ exit(1);
}
//2.绑定
memset(&serveraddr_in,0,sizeof(serveraddr_in));
serveraddr_in.sin_family=AF_INET;
serveraddr_in.sin_port=htons(6868);
serveraddr_in.sin_addr.s_addr=inet_addr("172.18.125.34");
int ret=bind(sockfd,(struct sockaddr *)&serveraddr_in,sizeof(serveraddr_in));
if(ret==-1)
{
┊ perror("bind");
┊ exit(1);
}
//3.监听
ret=listen(sockfd,10);
if(ret==-1)
{
┊ perror("listen");
┊ exit(1);
}
int length=sizeof(clientaddr_in);
fd_set readfd,tmpfd; //监听集合
FD_ZERO(&readfd); //清空集合
FD_SET(sockfd,&readfd); //设置集合
int maxfd=sockfd; //设置监听个数
char buf[128]={0};
while(1)
{
┊ tmpfd=readfd;//保存集合,因为当有文件有可读或写异常时,会把其他文件描述符删掉,留下可用的文件描述符
┊ int ret=select(maxfd+1,&tmpfd,NULL,NULL,NULL);//监听有可读文件描述符
┊ if(ret==-1)
┊ {
┊ ┊ perror("select");
┊ ┊ exit(1);
┊ }
┊ if(FD_ISSET(sockfd,&tmpfd))//1.判断是否为sockfd
┊ {
┊ ┊ int i;
┊ ┊ for( i=0;i<1024;i++)//2.判断哪个通信fd是没用过的,因为当客户端退出后,那个位置就为0
┊ ┊ {
┊ ┊ ┊ if(confd[i]==0)
┊ ┊ ┊ {
┊ ┊ ┊ ┊ break;
┊ ┊ ┊ }
┊ ┊ }<F9>
┊ ┊ //3.接受链接
┊ ┊ confd[i]=accept(sockfd,(struct sockaddr*)&clientaddr_in,(socklen_t *)&length);
┊ ┊ if(confd[i]==-1)
┊ ┊ {
┊ ┊ ┊ perror("accept");
┊ ┊ ┊ exit(1);
┊ ┊ }
┊ //打印连接的客户端IP
┊ ┊ printf("客户端%s连接成功 fd=%d\n",inet_ntoa(clientaddr_in.sin_addr),confd[i]);
┊ ┊ FD_SET(confd[i],&readfd);//4.把上线的客户端的文件描述符写入集合
┊ ┊ if(confd[i]>maxfd)//5.修改监听的最大个数
┊ ┊ {
┊ ┊ ┊ maxfd=confd[i];
┊ ┊ }
┊ }
┊ else//1.表示是通信描述符的
┊ {
┊ ┊ memset(buf,0,sizeof(buf));//清除Buf
┊ ┊ for(int i=0;i<1024;i++)//2.判断是哪个通信文件描述符
┊ ┊ {
┊ ┊ ┊ if(FD_ISSET(confd[i],&tmpfd))
┊ ┊ ┊ {
┊ ┊ ┊ ┊ int ret=recv(confd[i],buf,sizeof(buf),0);//3.接收来自客户端信息
┊ ┊ ┊ ┊ if(ret==-1)
┊ ┊ ┊ ┊ {
┊ ┊ ┊ ┊ ┊ perror("recv");
┊ ┊ ┊ ┊ ┊
┊ ┊ ┊ ┊ }
┊ ┊ ┊ ┊ if(ret==0)//判断是否为退出
┊ ┊ ┊ ┊ {
┊ ┊ ┊ ┊ ┊ perror("客户端退出");
┊ ┊ ┊ ┊ ┊ close(confd[i]);//关闭通信文件描述符
┊ ┊ ┊ ┊ ┊ FD_CLR(confd[i],&readfd);//从监听集合中清除通信文件符
┊ ┊ ┊ ┊ ┊ confd[i]=0;//文件描述符置0
┊ ┊ ┊ ┊ }
┊ ┊ ┊ ┊ else
┊ ┊ ┊ ┊ {
┊ ┊ ┊ ┊ ┊ printf("收到客户%d的信息:%s\n",confd[i],buf);//打印信息
┊ ┊ ┊ ┊ }
┊ ┊ ┊ ┊ break;
┊ ┊ ┊ }
┊ ┊ }
┊ }
}
return 0;
}