#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "kernel.h" //使用内核链表
#define PORT 12345
int main(void)
{
//创建socket套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0){
perror("socket error");
exit(-1);
}
//更改sockfd的属性,允许地址重复:防止报“地址重复”错误
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
//绑定IP和PORT
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY 实际上就是0
// server.sin_addr.s_addr = inet_addr("192.168.10.5");
if(bind(sockfd,(struct sockaddr *)&server,sizeof(server))){
perror("bind error");
exit(-1);
}
//监听,将主动连接变成被动连接
if(listen(sockfd,30)){
perror("listen error");
exit(-1);
}
//初始化链表,使用内核链表
pkernel_t p = kernel_init();
fd_set readfds; //创建一个名字叫readfds的集合
int ret;
char buf[50];
int max = 0;
int m = 10;
char buff[10]="sb";
while(1){
//清空readfds集合,每一次循环都把集合中的文件描述符全部清空,然后再一个一个的将文件描述符再次加入集合中
FD_ZERO(&readfds);
//将文件描述符放入
FD_SET(0,&readfds); //将文件描述符0(即:键盘的文件描述符)放入集合
FD_SET(sockfd,&readfds); //将服务器的文件描述符放入集合
max = sockfd; //由于集合中的文件描述的个数 等于 最大文件描述符的数值+1,所以这里先用max = sockfd;,后面再用max+1表示集合中的文件描述的个数
pkernel_t pos = NULL; //内核链表头节点
list_for_each_entry(pos,&p->list, list) //遍历链表
{
FD_SET(pos->data,&readfds); /*从头节点开始,一个一个的将链表中的文件描述符放入readfds集合中,注意:链表中的文件描述放在链表的数据域*/
if(pos->data > max)
max = pos->data; //每次有新的文件描述加入链表中,集合中的文件描述个数就会变多,所以需要让max有所变化
}
ret = select(max+1,&readfds,NULL,NULL,NULL); /*等待文件描述响动,如果有文件描述符响动,则该文件描述符会被保留下来,而其他的文件描述符会被清除,注意:此时readfds集合中就只有发生响动的这一个文件描述符。max+1表示集合中最大被允许装入的文件描述符个数*/
if(ret<0){
perror("select error");
exit(-1);
}
if(FD_ISSET(0,&readfds)){ /* FD_ISSET(0,&readfds)判断文件描述符0是否在readfds集合中(即:判断是否键盘的文件描述响动了)。注意:由于前面经过了select()函数的筛选,集合里面只剩下一个文件描述,只不过不知道是哪一个文件描述符响动了,所以这里需要判断*/
bzero(buf,sizeof(buf));
read(0,buf,sizeof(buf));
printf("%s",buf);
}else if(FD_ISSET(sockfd,&readfds)){ /*等待客户连接:如果键盘没有输入,那么就判断是否有客户需要连接,FD_ISSET(sockfd,&readfds)判断文件描述符sockfd是否在readfds集合中(即:判断是否有客户正在连接服务器,导致服务器的文件描述符sockfd响动了)。注意:由于前面经过了select()函数的筛选,集合里面只剩下一个文件描述,只不过不知道是哪一个文件描述符响动了,所以这里需要判断*/
/*如果有客户端来连接服务器,那么sockfd会响动,则sockfd会被保留下来,
其他的fd被清除,注意:此时readfds集合中就只有sockfd这一个文件描述符*/
struct sockaddr_in client;
int len = sizeof(client);
int fd = accept(sockfd,(struct sockaddr *)&client,&len); //客户端与服务器端进行连接,并产生一个新的客户文件描述fd
if(fd<0){
perror("accept error");
exit(-1);
}
printf("客户%d上线了\n",fd);
printf("其IP:%s PORT:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
kernel_insert_tail(p,fd); /*链表尾插操作,将新连接成功的客户的文件描述符fd加入链表里面。注意:是加入到链表里面而不是集合readfds里面,所以此时集合里面是没有该文件描述符的*/
}
/*如果前面的两个判断都没有通过,则说明是某个客户端在向服务器发送数据,那么这个客户的文件描述符fd会响动,这个客户fd被保留下来,其他的fd被清除,注意:此时readfds集合中就只剩下这个fd这一个文件描述符*/
list_for_each_entry(pos,&p->list, list) //由于不知道是哪一个客户的文件描述符发生了响动,所以将链表从头节点开始遍历,一个一个查询
{
if(FD_ISSET(pos->data,&readfds)){ //查询是集合中哪一个客户的文件描述符发生响动,如果找到,则开始接收客户端发送过来的消息
bzero(buf,sizeof(buf));
ret = recv(pos->data,buf,sizeof(buf),0);
if(ret<0){
if(!(m--)){ //如果接收失败20次,就打印"recv error"
perror("recv error");
}
}else if(ret==0){
printf("掉线了\n");
kernel_del(p,pos->data);
break;
}else{
m = 10;
printf("客户%d : %s\n",pos->data,buf); //打印客户端发送过来的消息
send(pos->data,buff,strlen(buff),0); //返回给客户端“sb”字符串
}
}
}
}
close(sockfd);
return 0;
}