靠,终于将 青春版MiNi QQ项目完成!利用网络编程,客户和服务器是TCP协议,客户和客户聊天使用的事UDP协议!
客户端:
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <netdb.h>
- #include <string.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <sys/wait.h>
- #include <sys/stat.h>
- #include <errno.h>
- #include <pthread.h>
- /*******************************************************************************************************************/
- #define CLIENT_LOGIN 10 //登录
- #define CLIENT_ZHUCE 20 //注册
- #define EXIT 30 //退出
- /*******************************************************************************************************************/
- typedef struct onlineclient //在线用户的结构体
- {
- unsigned char onuser[6]; //在线用户的名字
- unsigned char onip[16]; //在线用户的ip
- uint16_t onport; //在线用户的port
- int onfd; //登录成功的套接字
- int count; //心跳的计数器
- struct onlineclient *onnext;
- }onclient,*onuser;
- typedef struct ondata //客户端的在线客户结构体
- {
- unsigned char onname[6];
- unsigned char fip[16];
- uint16_t fonport;
- struct ondata *fnext;
- }friendlink,*flink;
- /*******************************************************************************************************************/
- unsigned char IP[16] = {0}; //将自己的ip设置为全局变量
- uint16_t PORT = 0; //将自己的port设置为全局变量
- /*******************************************************************************************************************/
- int select_printf(); //主界面的打印程序声明
- int getname_max(unsigned char *p, unsigned int maxlen); //获取名字的程序声明
- int getpsword_max(unsigned char *p, unsigned int maxlen); //获取密码的程序声明
- int client_login(int fd); //登录程序的声明
- int client_zhuce(int fd); //注册程序的声明
- void heatbeat(int fd); //心跳程序的声明
- flink add_friend(unsigned char *friend,flink fhead);//将信登录的客户添加到连表中
- void free_friend_on(flink fhead); //释放旧的在线客户连表
- void print_friend(flink fhead); //打印在线客户连表
- /*******************************************************************************************************************/
- int main() //客户端主程序
- {
- int ret = 0,fd = 0,maxfd = 0,nfound,fnameback,fnamecount = 2;
- struct sockaddr_in servaddr;
- struct sockaddr_in cliaddr; //UDP
- int uret;
- fd_set rset,set,urset ,uset; //UDP
- int umaxfd,unfound; //UDP
- unsigned char ubuf[250] = {0}; //UDP
- unsigned char buf[250] = {0};
- unsigned char head[2] = {0};
- unsigned char datalen = 0;
- unsigned char friend[24] = {0};
- unsigned char name[6] = {0};
- unsigned char ip[16] = {0};
- unsigned char buffriend[6] = {0};
- int headdata = 0,readback = 0,i;
- unsigned char pack_len = 0;
- unsigned char *fp = NULL;
- flink fhead = NULL,pp = NULL;
- int fj = 0,back = 0;
- uint16_t port = 0;
- int sockfd,myip = 0; //UDP
- pthread_t heatbeatid,pthread;
- int pthread_ret;
- int heat_beat = 0;
- fd = socket(AF_INET,SOCK_STREAM,0);
- memset(&servaddr,0,sizeof(servaddr));
- servaddr.sin_family= AF_INET;
- servaddr.sin_port=htons(20000);
- inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr.s_addr);
- ret = connect(fd,(struct sockaddr*)&servaddr,sizeof(servaddr)); //三次握手
- if(ret <0)
- {
- perror("connect");
- return 1;
- }
- while(1)
- {
- int select = 1;
- select = select_printf(); //打印主界面
- switch(select)
- {
- case CLIENT_LOGIN:select = client_login(fd);
- break; //选择登录
- case CLIENT_ZHUCE:select = client_zhuce(fd);
- break; //选择注册
- case EXIT: exit(0);
- break; //选择退出
- }
- readback = read(fd,head,2); //提取头
- if(readback == -1)
- {
- printf("read head error\n");
- }
- headdata = head[0] + head[1]*256;
- readback = read(fd,&datalen,1); //提取数据的长度
- printf("datalen is %d\n",datalen);
- if(readback == -1)
- {
- printf("datalen read error\n");
- }
- readback = read(fd,buf,datalen); //提取所有的数据
- if(readback == -1)
- {
- printf("read data error\n");
- }
- switch(headdata)
- {
- case 1000:if(buf[0] == 1)
- {
- printf(" 登录成功 \n");
- memcpy(IP,&buf[1],16); //被设置为全局变量
- printf("IP is %s\n",IP);
- PORT = buf[17] + buf[18]*256;
- printf("PORT is %d\n",PORT); //被设置为全局变量
- heat_beat = pthread_create(&heatbeatid,NULL,(void *)heatbeat,(void *)fd);
- if(heat_beat != 0) //此线程建立心跳
- {
- printf("heatbeat pthread create fail\n");
- exit(1);
- }
- goto readserver;
- }
- else
- {
- printf("登录失败\n");
- }
- break;
- case 1001:if(buf[0] == 1)
- {
- printf(" 注册成功 \n");
- }
- else
- {
- printf("注册失败\n");
- }
- break;
- case 1004:printf(" 网络出错,请重新发送 \n");
- break;
- }
- }
- /******************************************************获得在线连表*************************************************/
- readserver: //使用goto到达这里
- maxfd = fileno(stdin);
- FD_ZERO(&set); //将关注的集合清零
- FD_SET(fd,&set); //将套接字加入关注集合
- FD_SET(maxfd,&set); //将键盘加入关注集合
- maxfd = (maxfd > fd ? maxfd : fd) + 1; //提取扫描的范围
- while(1)
- {
- rset = set; //将关注集合备份
- if((nfound = select(maxfd,&rset,(fd_set *)0,(fd_set*)0,NULL)) < 0)
- { //扫描关注集合
- if(errno == EINTR)
- {
- fprintf(stderr,"interrupted system call\n");
- continue;
- }
- perror("select");
- exit(1);
- }
- if(FD_ISSET(fd,&rset)) //测试sock是否有变化
- {
- readback = read(fd,head,2); //读出头文件
- readback = read(fd,&datalen,1); //读出长度
- readback = read(fd,buf,datalen); //读出所有的数据
- headdata = head[0] + head[1]*256;
- if(headdata == 1003)
- {
- pack_len = buf[0]; //提取总的节点的长度
- free_friend_on(fhead); //每次建立连表的时候先释放以前的连表
- for(i = 0;i < pack_len;i++) //循环每个节点
- {
- memset(friend,0,24); //清零
- memcpy(friend,&buf[1+24*i],24); //提取解点
- fhead = add_friend(friend,fhead); //建立在线客户的连表
- }
- print_friend(fhead);
- printf("请输入你要聊天的朋友姓名\n");
- }
- else
- {
- continue; //收到的heddata不是1003就从新扫描
- }
- }
- if(FD_ISSET(fileno(stdin),&rset))
- {
- memset(buffriend,0,sizeof(buffriend)); //将name数组清零
- fnameback = getname_max(buffriend, 6); //判断名字是否符合要求,并取得名字(小于 4 个字节)
- while(fnameback == -1)
- {
- memset(buffriend,0,sizeof(buffriend)); //将name数组清零
- printf("请重新输入你的姓名,至多还输入 %d\n",fnamecount);
- fnameback = getname_max(buffriend, 6);
- printf("fnameback is %d\n",fnameback);
- if(fnameback == -1)
- {
- fnamecount--; //登录时输入姓名错误计数器减 1
- }
- if(fnameback == 1)
- {
- break; //登录时输入姓名正确退出循环
- }
- if(fnamecount == 0)
- {
- exit(0); //登录时输入错误超过三次自动退出
- }
- }
- fp = buffriend;
- fj = 0;
- while(*fp != '\n') //将 \n 转换为 \0 因此读取数组的长度
- {
- fj++;
- fp++;
- }
- buffriend[fj] = '\0'; //将最后面的\n赋值为\0
- pp = fhead;
- while(pp != NULL)
- {
- if((back =strcmp((pp->onname),buffriend)) == 0)//对比连表查找ip 和 port
- {
- break; //找到ip 和 port 退出
- }
- pp = pp->fnext; //指向下一个节点
- }
- if(pp != NULL)
- {
- break; //如果找到就跳出循环
- }
- }
- }
- printf("friend pp->onname is %s\n",(pp->onname));//打印聊天朋友的名字
- printf("friend pp->fonport is %d\n",(pp->fonport));//打印聊天朋友的port
- /*********************************************开始UDP通信*************************************************/
- if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0)//建立udp套接字
- {
- perror("error opening socket\n");
- return -1;
- }
- memset(&cliaddr,0,sizeof(cliaddr));
- cliaddr.sin_family = AF_INET; //赋值地址族
- printf("my port is %s\n",IP);
- myip = inet_pton(AF_INET,IP,&cliaddr.sin_addr.s_addr); //赋值ip
- printf("my port is %d\n",PORT);
- cliaddr.sin_port = htons(PORT); //赋值port
- if((uret = bind(sockfd,(struct sockaddr *)&cliaddr,sizeof(cliaddr))) < 0)
- { //邦定ip 和 port
- perror("error on binging");
- close(sockfd);
- }
- memset(&cliaddr,0,sizeof(cliaddr));
- cliaddr.sin_family = AF_INET; //赋值需要连接的地址族
- printf("pp->fip is %s\n",pp->fip);
- myip = inet_pton(AF_INET,(pp->fip),&cliaddr.sin_addr.s_addr);
- printf("pp->fonport is %d\n",pp->fonport);
- cliaddr.sin_port = htons(pp->fonport) ; //邦定需要连接ip 和 port
- umaxfd = fileno(stdin);
- FD_ZERO(&uset); //关注集合清零
- FD_SET(sockfd,&uset); //udp套接字加入关注集合
- FD_SET(umaxfd,&uset); //键盘输入也加入关注的集合
- umaxfd = (umaxfd > sockfd ? umaxfd : sockfd) + 1; //找出最大的扫描量
- while(1)
- {
- urset = uset; //备份关注集合
- if((unfound = select(umaxfd,&urset,(fd_set*)0,(fd_set*)0,NULL)) < 0)
- { //没有变化就阻塞
- if(errno == EINTR)
- {
- fprintf(stderr,"interrupted system call\n");
- continue;
- }
- perror("select");
- exit(1);
- }
- if(FD_ISSET(fileno(stdin),&urset)) //检测键盘是否有输入
- {
- memset(ubuf,0,sizeof(ubuf));
- system("date");
- printf("请输入你的聊天内容~~~~~~~");
- if(fgets(ubuf,sizeof(ubuf)-1,stdin) == NULL) //获得你的输入
- {
- if(ferror(stdin))
- {
- perror("stdin");
- break;
- }
- }
- ret = strlen(ubuf);
- ubuf[ret-1] = '\0';
- if((ret = sendto(sockfd,ubuf,strlen(ubuf),0,(struct sockaddr*)&cliaddr,sizeof(cliaddr))) < 0) //发送给好友
- {
- perror("ERROR writing to UDP socket");
- break;
- }
- }
- if(FD_ISSET(sockfd,&urset)) //检测好友有没有发送东西过来
- {
- uint32_t length = sizeof(cliaddr);
- memset(ubuf,0,sizeof(ubuf));
- if((ret = recvfrom(sockfd,ubuf,sizeof(ubuf)-1,0,(struct sockaddr*)&cliaddr,&length)) < 0) //接受好友发过来的信息
- {
- perror("ERROR reading from UDP socket");
- break;
- }
- ubuf[ret] = 0; //将最后的赋值为\n
- system("date");
- printf("你接受的内容是 :%s\n",ubuf);
- }
-
- }
- close(sockfd); //关闭套接字
- close(fd);
- }
- /************************************************************************************************************/
- void print_friend(flink fhead) //打印朋友连表的程序
- {
- flink p = fhead;
- while(p != NULL)
- {
- printf("ip is %s\n",(p->fip));
- printf("port is %d\n",(p->fonport));
- printf("name is %s\n",(p->onname));
- p = p->fnext;
- }
- }
- /************************************************************************************************************/
- void free_friend_on(flink fhead) //防止内存泄露 释放节点的程序
- {
- flink p = fhead;
- while(fhead != NULL)
- {
- p = fhead;
- fhead = p->fnext;
- free(p);
- }
- }
- /************************************************************************************************************/
- flink add_friend(unsigned char *friend,flink fhead)
- { //服务器端发过来的新的在线客户,将新的在线客户添加到连表
- flink p = fhead,s = NULL,tail = NULL;
- s = (flink)malloc(sizeof(friendlink));//malloc新的空间
- if(s == NULL)
- {
- printf("malloc fail\n"); //创建失败直接返回首地址
- return fhead;
- }
- memcpy((s->fip),friend,16); //提取朋友的ip
- s->fonport = friend[16] + friend[17] * 256;//提取朋友的port
- memcpy((s->onname),&friend[18],6); //提取朋友的姓名
- if(fhead == NULL)
- {
- fhead = s;
- fhead->fnext = NULL;
- }
- else
- {
- while(p != NULL)
- {
- tail = p;
- p = p->fnext;
- }
- tail->fnext = s;
- s->fnext = NULL;
- }
- return fhead; //返回首地址
- }
- /*****************************************下面是心跳线程程序****************************************/
- void heatbeat(int fd)
- {
- unsigned char buf[250] = {0};
- int writeback = 0;
- buf[0]=1002%256; //心跳的head 是 1002
- buf[1]=1002/256;
- buf[2]=1; //数据长度是 1
- buf[3]=1; //发给服务器的是数据是 1
- while(1)
- {
- writeback = write(fd,buf,4);
- if(writeback == -1)
- {
- printf("heart beat write fail\n ");
- }
- sleep(3); //每隔 3 秒发送 一个 1 给服务器
- }
- }
- /****************************************下面主界面的打印程序***************************************/
- int select_printf()
- {
- int select;
- printf("\t\t\t\t 1.登录 \t\t\n");
- printf("\t\t\t\t 2.注册 \t\t\n");
- printf("\t\t\t\t 3.退出 \t\t\n");
- scanf("%d",&select);
- getchar();
- //while(getchar()!='\n')
- while(select < 1||select > 3)
- {
- system("clear");
- printf("\t\t\t输入错误请重新输入\t\t\n");
- printf("\t\t\t\t 1.登录 \t\t\n");
- printf("\t\t\t\t 2.注册 \t\t\n");
- printf("\t\t\t\t 3.退出 \t\t\n");
- scanf("%d",&select);
- getchar();
- }
- select = select*10;
- return select ;
- }
- /*******************************************下面是获取名字的程序**************************************************/
- int getname_max(unsigned char *p, unsigned int maxlen) //最多接受字符串maxlen-2
- {
- unsigned char *q = p;
- unsigned int counter = 0; //输入字符的计数器
- while (1)
- {
- *q = getchar(); //逐个取出缓存中的输入
- counter ++;
- if (counter >= maxlen) //当counter > maxlen时 将所有的输入清零
- {
- if(*q == '\n')
- {
- memset(p, 0, maxlen); //清空刚才接受的字符数组
- return -1;
- }
- if(*q != '\n') //当counter >> maxlen是输入的 \n 将所有的输入清零
- {
- while (getchar() != '\n'); //把剩下的接受完
- memset(p, 0, maxlen); //清空刚才接受的字符数组
- return -1;
- }
- }
- if (*q == '\n') //counter < maxlen 当接受到 时就返回 1
- {
- return 1; //返回 1
- }
- else
- {
- q ++; //将指针 q 移到下一个位子
- }
- }
- }
- /*****************************************下面是获取密码的程序*********************************************/
- int getpsword_max(unsigned char *p, unsigned int maxlen) //最多接受字符串maxlen-2
- {
- unsigned char *q = p;
- unsigned int counter = 0; //输入字符的计数器
- while (1)
- {
- *q = getchar(); //逐个取出缓存中的输入
- counter ++;
- if (counter >= maxlen) //当counter > maxlen时 将所有的输入清零
- {
- if(*q == '\n') //maxlen是输入的 \n 将所有的输入清零
- {
- memset(p, 0, maxlen); //清空刚才接受的字符数组
- return -1;
- }
- if(*q != '\n') //当counter >> maxlen是输入的 \n 将所有的输入清零
- {
- while (getchar() != '\n'); //把剩下的接受完
- memset(p, 0, maxlen); //清空刚才接受的字符数组
- return -1; //返回 -1
- }
- }
- if (*q == '\n') //counter < maxlen 当接受到 时就返回 1
- {
- return 1; //返回 1
- }
- else
- {
- q ++; //将指针 q 移到下一个位子
- }
- }
- }
- /****************************************下面是客户登录的程序************************************************/
- int client_login(int fd) //客户登录的程序
- {
- unsigned char buf[250] = {0};
- unsigned char name[6] ={0}; //获得名字的字符数组 最多输入 5 个字符
- unsigned char psword[4] = {0}; //获得密码的字符数组 最多输入 3 个字符
- int nameback = 0, pswordback = 0;
- int namecount = 2,pswordcount = 2;
- int namelen = 0, pswordlen = 0;
- int writeback = 0,readback = 0;
- unsigned char *p = NULL;
- int j = 0;
- buf[0]=1000%256;
- buf[1]=1000/256;
- printf("请输入你的姓名(小于 4 个字节)至多3次\n");
- memset(name,0,sizeof(name)); //将name数组清零
- nameback = getname_max(name, 6); //判断名字是否符合要求,并取得名字(小于 4 个字节)
- while(nameback == -1)
- {
- memset(name,0,sizeof(name)); //将name数组清零
- printf("请重新输入你的姓名,至多还输入 %d\n",namecount);
- nameback = getname_max(name, 6);
- if(nameback == -1)
- {
- namecount--; //登录时输入姓名错误计数器减 1
- }
- if(nameback == 1)
- {
- break; //登录时输入姓名正确退出循环
- }
- if(namecount == 0)
- {
- exit(0); //登录时输入错误超过三次自动退出
- }
- }
- //namelen = strlen(name);//获取密码的长度(不能使用strlen的去长度 psword 遇到\0 就结束)
- p = name;
- j = 0;
- while(*p != '\n') //将 \n 转换为 \0 因此读取数组的长度
- {
- j++;
- p++;
- }
- name[j] = '\0'; //将最后面的\n赋值为\0
- buf[2] = 10;
- memcpy(&buf[3],name,6);
- /*------------------------------------------------------------------名字取得完毕*/
- memset(psword,0,sizeof(psword)); //将psword数组清零
- printf("请输入你的密码(小于 3 个字节)至多3次\n");
- pswordback = getpsword_max(psword,4);//判断密码是否符合要求,并取得密码(小于 3 个字节)
- while(pswordback == -1)
- {
- memset(psword,0,sizeof(psword)); //将psword数组清零
- printf("请重新输入你的密码,至多还输入 %d\n",namecount);
- pswordback = getpsword_max(psword,4);
- if(pswordback == -1)
- {
- pswordcount--; //密码输入错误计数器减 1
- }
- if(pswordback == 1)
- {
- break; //密码输入正确退出while语句
- }
- if(pswordcount == 0)
- {
- exit(0); //密码输入错误超过三次自动退出
- }
- }
- //pswordlen = strlen(psword);//获取密码的长度(不能使用strlen的去长度 psword 遇到 \0 就结束)
- p = psword;
- j = 0;
- while(*p!='\n') //将 \n 转换为 \0 因此读取数组的长度
- {
- j++;
- p++;
- }
- psword[j] = '\0'; //将最后面的 \n 赋值为 \0
- memcpy(&buf[9],psword,4); //将密码赋值给以地址buf[13]开头的地址
- /*-------------------------------------------------------------密码取得完毕*/
- writeback = write(fd,buf,13);
- if(writeback == -1)
- {
- printf("write error\n");
- return 1;
- }
- }
- /****************************************下面是客户注册的程序************************************************/
- int client_zhuce(int fd) //客户端的注册程序
- {
- unsigned char buf[250] = {0};
- unsigned char name[6] ={0};
- unsigned char psword[4] = {0};
- int nameback = 0,pswordback = 0;
- int namecount = 2,pswordcount = 2;
- int namelen = 0,pswordlen = 0;
- unsigned char len = 2;
- int j = 0;
- int writeback = 0,readback = 0;
- int cmpback = 3;
- unsigned char *p = NULL;
- memset(buf,0,250);
- buf[0]=1001%256;
- buf[1]=1001/256;
- memset(name,0,sizeof(name)); //将name数组清零
- printf("请输入你的姓名(小于 5 字节)至多3次\n");
- nameback = getname_max(name, 6);//判断名字是否符合要求,并取得名字(小于 5 字节)
- while(nameback == -1)
- {
- memset(name,0,sizeof(name)); //将name数组清零
- printf("请重新输入你的姓名,至多还输入 %d\n",namecount);
- nameback = getname_max(name, 6);
- if(nameback == -1)
- {
- namecount--; //输入的姓名不符合规定计数器减 1
- }
- if(nameback == 1)
- {
- break; //输入的姓名正确退出循环
- }
- if(namecount == 0)
- {
- exit(0); //输入姓名的次数超过三次退出
- }
- }
- //namelen = strlen(name);//获取密码的长度(不能使用strlen的去长度 psword 遇到 \0 就结束)
- p = name;
- while(*p != '\n') //将 \n 转换为 \0 因此读取数组的长度
- {
- j++;
- p++;
- }
- name[j] = '\0'; //将最后面的\n赋值为\0
- buf[2] = 10;
- memcpy(&buf[3],name,6);
- /*------------------------------------------------------名字取得完毕*/
- memset(psword,0,sizeof(psword)); //将psword数组清零
- printf("请输入你的密码(小于3个字符)\n");
- pswordback = getpsword_max(psword,4); //判断密码是否符合要求,并取得密码(小于3个字符)
- while(pswordback == -1)
- {
- memset(psword,0,sizeof(psword)); //将psword数组清零
- printf("请重新输入你的密码,至多还输入 %d\n",namecount);
- pswordback = getpsword_max(psword,4);
- if(pswordback == -1)
- {
- pswordcount--; //输入的密码不符合规定计数器减 1
- }
- if(pswordback == 1)
- {
- break; //输入的密码正确退出循环
- }
- if(pswordcount == 0)
- {
- exit(0); //输入密码的次数超过三次退出
- }
- }
- //pswordlen = strlen(psword);//获取密码的长度(不能使用strlen的去长度 psword 遇到 \0 就结束)
- p = psword;
- j = 0;
- while(*p!= '\n') //将 \n 转换为 \0 因此读取数组的长度
- {
- j++;
- p++;
- }
- psword[j] = '\0'; //将最后面的\n赋值为\0
- memcpy(&buf[9],psword,4); //将密码赋值给以地址buf[13]开头的地址
- /*--------------------------------------------------密码取得完毕*/
- writeback = write(fd,buf,13); //将打包好的数据发给服务器
- printf("writeback = %d\n",writeback);
- if(writeback == -1)
- {
- perror("error");
- return 1;
- }
- }
- /********************************以上是注册读出服务器的返回的在线客户连表*******************************/
复制代码
服务器端:
- #include <stdio.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <string.h>
- #include <stdarg.h>
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/shm.h>
- #include <sys/sem.h>
- #include <errno.h>
- #include <ctype.h>
- #include <sys/msg.h>
- #include <fcntl.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <netdb.h>
- #include <arpa/inet.h>
- #include <ctype.h>
- #include <sys/time.h>
- #include <sys/select.h>
- /*******************************************************************************************************************/
- #define LISTEN_QUEUE_NUM 5 //某一时刻可以监听的个数
- #define BUFFER_SIZE 250 //buf的大小
- #define ECHO_PORT 20000 //服务器的port
- /*******************************************************************************************************************/
- typedef struct client //注册的结构体
- {
- unsigned char username[6]; //客户的姓名
- unsigned char userpsword[4]; //客户的密码
- struct client *next; //指向注册成功的下一个指针
- }clientlink,*lclientlink;
- typedef struct onlineclient //在线用户的结构体
- {
- unsigned char onuser[6]; //在线用户的名字
- unsigned char onip[16]; //在线用户的ip
- uint16_t onport; //在线用户的port
- int onfd; //登录成功的套接字
- int count; //心跳的计数器
- int flag; //设计登录的标记
- struct onlineclient *onnext; //指向下一个在线客户的指针
- }onclient,*onuser;
- /*******************************************************************************************************************/
- onuser onhead = NULL; //全局变量的在线客户连表头指针
- unsigned char BUFIP[16] = {0}; //获得上线客户端的16个字节的ip
- uint16_t CLIENTPORT = 0; //获得上线客户端的2个字节的port
- int CLIENT_FD = 0; //登录成功的套接字
- /*******************************************************************************************************************/
- void jiluname_psword(int fd,unsigned char *buf,unsigned char datalen);
- //注册的程序
- void cmpname_psword(int fd,unsigned char *buf,unsigned char datalen);
- //登录的程序
- onuser add_mes_to_onuser(unsigned char *name,int fd,onuser onhead);
- //将登录完的客户的信息补充完整
- void record_onuser_send(int fd,unsigned char *buf,onuser onhead);
- //在线客户连表广播给刚登录的客户
- void head_default(int fd,unsigned char *buf);
- //头文件出错的程序
- lclientlink read_clientlink();
- //打开注册过的文件程序
- int save_link(lclientlink head);
- //保存注册过的文件程序
- void add_onuser();
- //添加新登录的客户程序
- onuser reseach(onuser onhead,int fd);
- //查找刚上线的客户程序
- void printfonuser(onuser onhead);
- //打印在线客户连表的程序
- int recive_heatbeat(int fd);
- //接受客户的心跳程序
- int scan_onlive_user();
- //遍历整个在线连表程序
- /*******************************************************************************************************************/
- int main(int argc,char **argv) //服务器的主函数
- {
- lclientlink head = NULL;
- struct sockaddr_in servaddr,remote;
- int request_sock,new_sock;
- int nfound,fd,maxfd,bytesread;
- uint32_t addrlen;
- fd_set rset,set;
- struct timeval timeout;
- unsigned char buf[BUFFER_SIZE] = {0};
- unsigned char headdata[2] = {0};
- unsigned char datalen = 0;
- int headlen = 0;
- pthread_t scanid; //初始化新的线程 ID
- int scanret;
- if((request_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
- { //建立套接字
- perror("socket");
- return -1;
- }
- memset(&servaddr,0,sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr.s_addr);
- //将字符串表示的地址转换成协议大端字符地址
- servaddr.sin_port = htons((uint16_t)ECHO_PORT);//bind port
- if(bind(request_sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
- { //bind套接字
- perror("bind");
- return -1;
- }
- if(listen(request_sock,LISTEN_QUEUE_NUM) < 0) //监听
- {
- perror("listen");
- return -1;
- }
- /*************************************扫描count删除连表中的下线成员****************************************/
- scanret = pthread_create(&scanid,NULL,(void *)scan_onlive_user,NULL);
- if(scanret != 0) //建立扫描心跳的线程
- {
- printf("creat pthread fail\n");
- exit(1);
- }
- /*******************************************************************************************************************/
- FD_ZERO(&set); //将关注集合清零
- FD_SET(request_sock,&set); //将监听套节字置 1
- maxfd = request_sock; //将监听套节字赋值给扫描范围
- while(1)
- {
- rset = set; //备份关注集合
- timeout.tv_sec =0; // 秒钟设置为 0
- timeout.tv_usec = 500000; //设置为500000微妙扫描 1 次
- if((nfound = select(maxfd + 1,&rset,(fd_set*)0,(fd_set*)0,&timeout)) < 0)
- {
- perror("select");
- return -1;
- }
- else if(nfound == 0)
- {
- printf("."); //没有人上线就打点
- fflush(stdout); //清空缓存
- continue;
- }
- if(FD_ISSET(request_sock,&rset))
- {
- addrlen = sizeof(remote); //获取结构体的字节数
- if((new_sock = accept(request_sock,(struct sockaddr*)&remote,&addrlen)) < 0)//
- {
- perror("accept");
- return -1;
- }
- printf("connection from host %s,port %d,socket %d\r\n",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port),new_sock);
- /******************************************连接上了服务器的客户信息****************************************/
- memcpy(BUFIP,(inet_ntoa(remote.sin_addr)),16); //获取上线的的ip 16
- CLIENTPORT = ntohs(remote.sin_port); //获取上线的port 4
- CLIENT_FD = new_sock; //上线客户的套接字 4
- add_onuser(); //添加客户的信息
- /*******************************************以上连接上了服务器的客户信息********************************/
- FD_SET(new_sock,&set);
- if(new_sock > maxfd)
- maxfd = new_sock;
- FD_CLR(request_sock ,&rset);
- nfound--;
- }
- for(fd = 0;fd <= maxfd&&nfound > 0;fd++) //扫描
- {
- if(FD_ISSET(fd,&rset))
- {
- nfound--;
- bytesread = read(fd,headdata,2); // 读出文件的头并检测
- if(bytesread < 0) //数据读取失败
- {
- perror("read");
- }
- headlen = headdata[0] + headdata[1] * 256; //解析出 头
- bytesread = read(fd,&datalen,1); //读出文件中数据的长度并检测
- if(bytesread < 0) //读取数据失败
- {
- perror("read");
- }
- bytesread = read(fd,buf,datalen); //读出文件中的数据并检测
- if(bytesread < 0) //读取数据失败
- {
- perror("read");
- }
- if(bytesread == 0) //数据读取完毕
- {
- fprintf(stderr,"server :end of file on %d\r\n",fd);
- FD_CLR(fd,&set);
- close(fd);
- continue;
- }
- switch(headlen)
- {
- case 1000:cmpname_psword(fd,buf,datalen); //登录程序
- break;
- case 1001:jiluname_psword(fd,buf,datalen); //注册程序
- break;
- case 1002:recive_heatbeat(fd); //接受客户的心跳程序
- break;
- default: head_default(fd,buf); //协议头出现错误程序
- break;
- }
- }
- }
- }
- return 0;
- }
- /************************************************************************************************************************************************/
- int scan_onlive_user() //心跳程序每5秒扫描一次
- {
- onuser p = onhead,tail = NULL;
- while(1)
- {
- sleep(5);
- while(1)
- {
- p = onhead;
- if(p == NULL)
- {
- printf("empty onuser list\n"); //有空连表
- return -1;
- }
- if((p->count) > 60)
- {
- onhead = onhead->onnext;
- }
- else
- {
- while((p != NULL)&&((p->count) < 60)) //少于60就删除节点
- {
- tail = p;
- p = p->onnext;
- }
- tail->onnext = p->onnext;
- break;
- }
- if(p == NULL) //如果能够扫描结束解退出循环
- {
- break;
- }
- }
- p =onhead;
- while(p != NULL) //给计数器赋值
- {
- (p->count) = (p->count) + 5;
- p = p->onnext;
- }
- }
- }
- /************************************************************************************************************************************************/
- int recive_heatbeat(int fd) // 如果有在线连表中的 count 清零
- {
- onuser p = NULL;
- p = onhead;
- if(p == NULL)
- {
- printf("empty onuser list\n"); //理论上不应该有空连表
- return -1;
- }
- else
- {
- while(p != NULL)
- {
- if((p->onfd) == fd) //接受到心跳包 就将 该 fd 对应的计数器清零
- {
- (p->count) = 0; //清零处理
- printf("\n");
- }
- p = p->onnext; //指向下一个成员
- }
-
- }
- }
- /************************************************************************************************************************************************/
- void head_default(int fd,unsigned char *buf)//head出现错误
- {
- int writeback = 0;
- memset(buf,0,sizeof(buf));
- buf[0]=1004%256;
- buf[1]=1004/256;
- buf[2]= 1;
- buf[3]= 2; //失败返回2
- writeback = write(fd,buf,strlen(buf));
- if(writeback == -1)
- {
- printf("write error\n");
- }
- }
- /************************************************************************************************************************************************/
- void jiluname_psword(int fd,unsigned char *buf,unsigned char datalen)
- { //服务器端记载注册的人
- int writeback = 0;
- int saveback = 0;
- lclientlink head = NULL,s = NULL;
- head = read_clientlink(); //打开文件并读文件将返回值给head
- printf("buf is %s\n",buf);
- saveback = add_client(head,buf); //将注册人的信息添加到连表中去
- printf("saveback is %d\n",saveback);
- if(saveback == -1)
- {
- memset(buf,0,sizeof(buf));
- buf[0]=1001%256;
- buf[1]=1001/256;
- buf[2]= 1;
- buf[3]= 2; //失败返回2
- writeback = write(fd,buf,strlen(buf));
- if(writeback == -1)
- {
- printf("write error\n");
- }
- }
- else
- {
- memset(buf,0,sizeof(buf));
- buf[0]=1001%256;
- buf[1]=1001/256;
- buf[2]= 1;
- buf[3]= 1; //成功返回1
- writeback = write(fd,buf,strlen(buf));
- if(writeback == -1)
- {
- printf("write error\n");
- }
- }
- }
- /************************************************************************************************************************************************/
- lclientlink read_clientlink() //打开文件并读文件
- {
- int fid;
- int read_value;
- lclientlink head = NULL,s = NULL,tail = NULL;
- fid = open("client.ini",O_RDONLY);
- if(fid == -1)
- {
- printf("open error\n");
- //return -1; //打开文件失败返回-1
- }
- s = (lclientlink)malloc(sizeof(clientlink));
- while(read_value = read(fid,s,sizeof(clientlink)) != 0)
- {
- if(head == NULL)
- {
- head = s;
- tail = s;
- head->next = NULL;
- }
- else
- {
- tail->next = s;
- tail = s;
- tail->next =NULL;
- }
- s = (lclientlink)malloc(sizeof(clientlink));
- }
- free(s);
- close(fid);
- return head;
- }
- /************************************************************************************************************************************************/
- int add_client(lclientlink head,unsigned char *buf) //创建连表
- {
- int saveback = 0;
- lclientlink p = head ,s = NULL,tail = NULL,p1 = head;
- unsigned char name[6] = {0};
- s = (lclientlink)malloc(sizeof(clientlink));
- memcpy(name,buf,6);
- while(p1 != NULL) //判断此用户明是否被注册过
- {
- if((strcmp((p1->username),name)) == 0 )
- {
- printf("hello\n");
- return -1;
- }
- p1 = p1->next;
- }
- memcpy((s->username),name,6); //读取buf中的 6 个字节数据
- memcpy((s->userpsword),buf + 6,4); //读取buf中的 4个字节数据
- if(head == NULL)
- {
- head = s;
- tail = s;
- p = s;
- head->next = NULL;
- }
- else
- {
- while(p != NULL)
- {
- tail=p;
- p = p->next;
- }
- tail->next=s;
- tail = s;
- s->next=NULL;
- }
- saveback = save_link(head);
- return saveback;
- }
- /************************************************************************************************************************************************/
- int save_link(lclientlink head) //存储连表
- {
- lclientlink p = head;
- int fid ;
- int writeback;
- fid = open("client.ini",O_WRONLY|O_APPEND);
- if(fid == -1)
- {
- printf("open error \n");
- return -1; //打开文件失败返回-1
- }
- while(p != NULL)
- {
- writeback = write(fid,p,sizeof(clientlink));
- if(writeback == -1)
- {
- printf("write error\n");
- return -1; //写入文件失败返回-1
- }
- p = p->next;
- }
- close(fid);
- return 1; //写入成功返回1
- }
- /************************************************************************************************************************************************/
- void cmpname_psword(int fd,unsigned char *buf,unsigned char datalen)
- { //服务器端处理客户的登录
- lclientlink head = NULL,p = NULL;
- int writeback = 0;
- int cmpnameresult = 3;
- int cmppswordresult = 3;
- unsigned char name[6] = {0};
- unsigned char psword[4] = {0};
- onuser s = NULL;
- memset(name,0,6); //name数组清零
- memset(psword,0,4); //psword数组清零
- memcpy(name,buf,6); //将buf中的copy到name数组中去
- memcpy(psword,buf + 6,4); //将剩下buf中的数据copy到psword数组中去
- head = read_clientlink();
- p = head;
- while(p != NULL)
- {
- cmpnameresult = strcmp((p->username),name); //对比名字
- cmppswordresult = strcmp((p->userpsword),psword); //对比密码
- if(cmpnameresult == 0&&cmppswordresult == 0)
- {
- s = reseach(onhead,fd); //连表中查找登录客户的ip 与 port
- memset(buf,0,sizeof(buf));
- buf[0]=1000%256;
- buf[1]=1000/256;
- buf[2]= 19;
- buf[3]= 1; //成功返回1
- memcpy(&buf[4],(s->onip),16);
- printf("ip is %s\n",(s->onip));
- buf[20] = (s->onport)%256;
- buf[21] = (s->onport)/256;
- writeback = write(fd,buf,22); //发送 4 个字节后 将在线连表 发送国去
- if(writeback == -1)
- {
- printf("write error\n");
- }
- onhead = add_mes_to_onuser(name,fd,onhead); //将登录上的客户信息补充完整
- record_onuser_send(fd,buf,onhead);//记录登录成功的客户 并将登录成功的好友信息发给刚上线的人
- break;
- }
- p = p->next;
- }
- memset(buf,0,sizeof(buf));
- buf[0]=1000%256;
- buf[1]=1000/256;
- buf[2]= 1;
- buf[3]= 2; //失败返回2
- writeback = write(fd,buf,strlen(buf));
- if(writeback == -1)
- {
- printf("write error\n");
- }
- }
- /************************************************************************************************************************************************/
- void add_onuser() //将刚连接上的客户加到连表上的程序
- {
- onuser p = onhead ,s = NULL,tail =NULL;
- unsigned char onbuf[6] = {0};
- s = (onuser)malloc(sizeof(onclient)); //malloc一个新的空间
- s->onport = CLIENTPORT; //提取port
- s->onfd = CLIENT_FD; //提取fd
- s->count = 0;
- s->flag = 0;
- memcpy((s->onip),BUFIP,sizeof(BUFIP)); //提取ip
- memcpy((s->onuser),onbuf,sizeof(onbuf));
- if(onhead == NULL)
- {
- onhead = s;
- tail = s;
- tail->onnext = NULL;
- }
- else
- {
- while(p != NULL)
- {
- tail = p;
- p = p->onnext;
- }
- tail->onnext = s;
- tail = s;
- tail->onnext = NULL;
- }
- }
- /************************************************************************************************************************************************/
- onuser reseach(onuser onhead,int fd) //寻找刚上线客户
- {
- onuser p = onhead;
- while(p != NULL)
- {
- if((p->onfd) == fd)
- {
- return p; //返回刚上线的客户地址
- }
- p = p->onnext;
- }
- }
- /************************************************************************************************************************************************/
- onuser add_mes_to_onuser(unsigned char *name,int fd,onuser onhead)
- {
- onuser p = onhead;
- while(p != NULL)
- {
- if((p->onfd) == fd) //比较fd来添加姓名 和 将登录成功的标志赋值
- {
- memcpy((p->onuser),name,6); //补充名字
- p->flag = 1; //赋值登录成功的标志位
- }
- p = p->onnext; //下一个节点
- }
- return onhead; //返回首指针
- }
- /************************************************************************************************************************************************/
- void record_onuser_send(int fd,unsigned char *buf,onuser onhead)
- {
- onuser p = NULL,tail = NULL;
- int writeback = 0;
- p = onhead;
- int i = 0;
- uint16_t port = 0;
- unsigned char datalen = 0;
- unsigned char pack_len = 0;
- printfonuser(onhead);
- while(p !=NULL) //将连表中的所有在线的客户发送给 刚上线的客户
- {
- if((p->flag) == 1) //登录成功的人才被发送出去
- {
- memcpy(&buf[4 + (16 + 2 + 6)*i + 0],(p->onip),16); //将onip打包
- buf[4 + (16 + 2 + 6)*i + 16] = (p->onport)%256; //将port打包
- buf[4 + (16 + 2 + 6)*i + 17] = (p->onport)/256;
- memcpy(&buf[4 + (16 + 2 +6)*i + 18],(p->onuser),6);//将名字打包
- i++;
- }
- p = p->onnext; //指向下一个节点
- }
- datalen = 1 + (16 + 2 + 6)*i;
- buf[0] = 1003%256;
- buf[1] = 1003/256;
- buf[2] = datalen;
- buf[3] = i;
- pack_len = 3 + datalen;
- p = onhead;
- while(p != NULL)
- {
- if((p->flag) == 1) //寻找连表中登录成功的人
- {
- writeback = write((p->onfd),buf,pack_len);
- if(writeback == -1)
- {
- printf("write error\n");
- }
- }
- p = p->onnext;
- }
- }
- /************************************************************************************************************************************************/
- void printfonuser(onuser onhead) //打印在线客户的程序
- {
- onuser p = onhead;
- if(p == NULL)
- {
- printf(" empty onuser link\n");
- }
- while(p != NULL)
- {
- printf("name is %s\n",p->onuser);
- printf("ip is %s\n",p->onip);
- printf("port is %d\n",p->onport);
- printf("fd is %d\n",p->onfd);
- printf("count is %d\n",p->count);
- printf("flag is %d\n",p->flag);
- p = p->onnext;
- }
- }
- /************************************************************************************************************************************************/
复制代码
|