#include
#include
#include
#include
#include
#include
#include
#define ERR_MSG(msg) do{\
fprintf(stderr,"__%d__",__LINE__);\
perror("socket");\
}while(0)
#define IP "192.168.122.73"
#define PORT 8888
int main(int argc, const char *argv[])
{
//创建流式套接字
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd<0){
ERR_MSG("socket");
return -1;
}
//允许端口快速被重用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
ERR_MSG("setsockopt");
return -1;
}
printf("允许端口快速被重用成功\n");
//填充服务器的地址信息结构体 AF_INET: man 7 IP
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port= htons(PORT);
sin.sin_addr.s_addr=inet_addr(IP);
//绑定服务器的IP和端口 --->必须绑定
int bindResult=bind(sfd,(struct sockaddr *)&sin,sizeof(sin));
if(bindResult<0){
ERR_MSG("bind");
return -1;
}
puts("bind success");
//将套接字设置为被动监听状态,监听是否有客户端连接
int cliMaxCount=128;
int listenResult=listen(sfd,cliMaxCount);
if(listenResult<0){
ERR_MSG("listen");
return -1;
}
puts("listen success");
//创建集合
fd_set readfds, tempfds;
//fd_set
//本质上是一个结构体,且结构体中是一个整形数组,若不初始化,其中会存有随机值
//由于fd_set这个集合需要存放要监测的文件描述符,不能让其中随机到有效文件描述符
//所以需要初始化集合
//将集合初始化
FD_ZERO(&readfds);
FD_ZERO(&tempfds);
//将需要监测的文件描述符放入集合中
FD_SET(0,&readfds);
FD_SET(sfd,&readfds);
int maxfd = sfd; //存最大的文件描述符
int newfd = -1;
char buf[128]="";
ssize_t res =0;
int s_res=0;
//存储客户端地址信息
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
struct sockaddr_in saveCin[1024-3];//newfd对应的客户端信息存储在newfd-3下标位置
while(1){
tempfds=readfds;
//让内核监测集合中的文件描述符是否准备就绪
s_res=select(maxfd+1,&tempfds, NULL,NULL,NULL);
if(s_res < 0){
ERR_MSG("select");
return -1;
} else if(s_res ==0){
puts("time out...");
break;
}
printf("__%d__\n", __LINE__);
//能运行到当前位置,则代表集合中有文件描述符准备就绪
//判断哪个文件描述符准备就绪,走对应的处理函数即可
//当集合中有某个文件描述符准备就绪了,集合中会只剩下产生事件的文件描述符
//若0准备就绪,则集合中会只剩下0
//若sfd准备就绪,则集合中会只剩下sfd
//若sfd和0均准备就绪,则集合中会剩下0和sfd
//通过循环遍历集合中的所有文件描述符
//由于sfd和newsdf的值不确定,所以从0开始遍历
for(int i=0;i<=maxfd;i++){
if(FD_ISSET(i,&tempfds)==0){
continue;
}
//能运行到当前位置,则说明i所代表的文件描述符在tempfds中
if(0==i){//键盘输入事件
puts("触发键盘输入事件");
int sndfd;//存储要发送给哪个客户端
res=scanf("%d %s", &sndfd,buf);
while(getchar()!=10);//循环吸收垃圾字符
if(res!=2){
fprintf(stderr, "输入的数据格式错误:fd string\n");
continue;
}
//能运行到当前位置,则代表输入的格式是正确的
//需要排除非法的文件描述符
if(sndfd<=2 || sndfd>=1024 || !FD_ISSET(sndfd,&readfds)){
fprintf(stderr,"sndfd=%d 文件描述符错误\n",sndfd);
continue;
}
if(send(sndfd, buf, sizeof(buf), 0) < 0){
ERR_MSG("send");
continue;
}
puts("发送成功");
}else if(sfd==i){//客户端连接事件
puts("触发客户端连接事件");
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);
//将newfd添加到集合readfds中
FD_SET(newfd,&readfds);
//更新maxfd
maxfd=maxfd > newfd ? maxfd :newfd ;
}else {//客户端交互事件
puts("触发客户端交互事件");
bzero(buf,sizeof(buf));
//接收数据
res=recv(i,buf,sizeof(buf),0);
if(res<0){
ERR_MSG("recv");
continue;
}else if(res== 0) {
printf("[%s : %d]newfd=%d 客户端下线\n",\
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),i);
//关闭文件描述符
close(i);
//将i所代表的文件描述符删除
FD_CLR(i,&readfds);
//更新maxfd --> 有可能将集合中最大的文件描述符剔除
//从最大的文件描述符开始遍历,直到文件描述符在集合中
//则该文件描述符就是最大的文件描述符
while(!FD_ISSET(maxfd,&readfds) && maxfd-->=0);
continue;
}
printf("[%s : %d]newfd=%d : %s\n",\
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),i,buf);
}
}
}
//关闭文件描述符
if(close(sfd)<0){
ERR_MSG("close");
return -1;
}
return 0;
}