标题:
局域网MiNi QQ完成 C语言
[打印本页]
作者:
51hei单片
时间:
2016-3-13 18:09
标题:
局域网MiNi QQ完成 C语言
靠,终于将 青春版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;
}
}
/************************************************************************************************************************************************/
复制代码
作者:
wei0212016
时间:
2016-4-19 09:03
好纽币啊。看不懂。
欢迎光临 (http://www.51hei.com/bbs/)
Powered by Discuz! X3.1