selectTcpSer.c:
#include
#define ERR_MSG(msg) do{\
fprintf(stderr,"__%d__\n",__LINE__);\
perror(msg);\
}while(0)
#define PORT 8888
int keybroad_event(fd_set readfds)
{
char buf[128] = "";
int sendfd = -1;
bzero(buf,sizeof(buf));
int res = scanf("%d %s",&sendfd,buf);
while(getchar()!=10);
if(res != 2)
{
printf("input error,usage:sendfd string\n");
return -1;
}
if(sendfd <=2 || FD_ISSET(sendfd,&readfds) == 0)
{
printf("非法的文件描述符\n");
return -1;
}
if(send(sendfd,buf,sizeof(buf),0) < 0)
{
ERR_MSG("send");
return -1;
}
printf("send success\n");
return 0;
}
int cliconnect_event(int sfd,struct sockaddr_in cliaddrs[],fd_set *preadfds,int *pmaxfd)
{
int newfd = -1; //客户端文件描述符
struct sockaddr_in cin; //用来存储客户端地址信息
socklen_t addrlen = sizeof(cin);
newfd = accept(sfd,(struct sockaddr*)&cin,&addrlen);
if(newfd < 0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s:%d]客户端连接成功,newfd = %d\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);
cliaddrs[newfd]=cin; //将客户端地址信息保存到数组中
FD_SET(newfd,preadfds); //将文件描述符添加到集合中
*pmaxfd = *pmaxfd > newfd ? *pmaxfd:newfd; //更新maxfd
return 0;
}
int clichange_event(int fd,struct sockaddr_in cliaddrs[],fd_set *preadfds,int *pmaxfd)
{
char buf[128] = "";
bzero(buf,sizeof(buf));
ssize_t ret = recv(fd,buf,sizeof(buf),0);
if(ret < 0)
{
ERR_MSG("recv");
return -1;
}
else if(0 == ret)
{
printf("[%s:%d]客户端下线 fd = %d",\
inet_ntoa(cliaddrs[fd].sin_addr),ntohs(cliaddrs[fd].sin_port),fd);
close(fd);
FD_CLR(fd,preadfds);
while(FD_ISSET(*pmaxfd,preadfds) == 0 && *pmaxfd-->=0);
return 0;
}
printf("[%s:%d] newfd = %d:%s\n",\
inet_ntoa(cliaddrs[fd].sin_addr),ntohs(ntohs(cliaddrs[fd].sin_port)),fd,buf);
//发送
strcat(buf,">-<");
if(send(fd,buf,sizeof(buf),0) < 0)
{
ERR_MSG("send");
return -1;
}
printf("send success\n");
return 0;
}
int main(int argc, const char *argv[])
{
//判断输入合法性
if(argc < 2)
{
printf("input error,usage:./a.out ip\n");
return -1;
}
//创建流式套接字
int sfd;
if((sfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket success sfd = %d\n",sfd);
//允许端口快速复用
int reuse = 1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) < 0)
{
ERR_MSG("setsockopt");
return -1;
}
//填写地址信息结构体给bind使用
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(argv[1]);
//bind绑定地址信息,必须绑定
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
//listen 将套接字转换成监听模式
if(listen(sfd,128) < 0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success\n");
//创建一个读集合
fd_set readfds,tempfds;
//清零
FD_ZERO(&readfds);
//将文件描述符添加到集合中
FD_SET(0,&readfds);
FD_SET(sfd,&readfds);
int s_res = -1;
ssize_t res = -1;
int maxfd = sfd; //存储最大文件描述符
struct sockaddr_in cliaddrs[1024]; //记录客户端信息
while(1)
{
//用来额外备份,保证原先的集合不变
tempfds = readfds;
//实行IO多路复用
s_res = select(maxfd+1,&tempfds,NULL,NULL,NULL);
if(s_res < 0)
{
ERR_MSG("select");
return -1;
}
else if(s_res == 0)
{
printf("timeout\n");
return -1;
}
//当有文件描述符准备就绪时,该集合中就会只剩下已经准备就绪的文件描述符
for(int i=0;i<=maxfd;i++)
{
if(FD_ISSET(i,&tempfds) == 0)
continue;
//运行到这说明集合内有这个文件描述符
if(0 == i)
{
//键盘输入事件
keybroad_event(readfds);
}else if(sfd == i)
{
//客户端连接事件
cliconnect_event(sfd,cliaddrs,&readfds,&maxfd);
}else{
//客户端交互事件
clichange_event(i,cliaddrs,&readfds,&maxfd);
}
}
}
//关闭套接字
if(close(sfd) < 0)
{
ERR_MSG("close");
return -1;
}
return 0;
}
效果图: