標(biāo)題:
局域網(wǎng)MiNi QQ完成 C語(yǔ)言
[打印本頁(yè)]
作者:
51hei單片
時(shí)間:
2016-3-13 18:09
標(biāo)題:
局域網(wǎng)MiNi QQ完成 C語(yǔ)言
靠,終于將 青春版MiNi QQ項(xiàng)目完成!利用網(wǎng)絡(luò)編程,客戶和服務(wù)器是TCP協(xié)議,客戶和客戶聊天使用的事UDP協(xié)議!
客戶端:
#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 //注冊(cè)
#define EXIT 30 //退出
/*******************************************************************************************************************/
typedef struct onlineclient //在線用戶的結(jié)構(gòu)體
{
unsigned char onuser[6]; //在線用戶的名字
unsigned char onip[16]; //在線用戶的ip
uint16_t onport; //在線用戶的port
int onfd; //登錄成功的套接字
int count; //心跳的計(jì)數(shù)器
struct onlineclient *onnext;
}onclient,*onuser;
typedef struct ondata //客戶端的在線客戶結(jié)構(gòu)體
{
unsigned char onname[6];
unsigned char fip[16];
uint16_t fonport;
struct ondata *fnext;
}friendlink,*flink;
/*******************************************************************************************************************/
unsigned char IP[16] = {0}; //將自己的ip設(shè)置為全局變量
uint16_t PORT = 0; //將自己的port設(shè)置為全局變量
/*******************************************************************************************************************/
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); //注冊(cè)程序的聲明
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; //選擇注冊(cè)
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); //提取數(shù)據(jù)的長(zhǎng)度
printf("datalen is %d\n",datalen);
if(readback == -1)
{
printf("datalen read error\n");
}
readback = read(fd,buf,datalen); //提取所有的數(shù)據(jù)
if(readback == -1)
{
printf("read data error\n");
}
switch(headdata)
{
case 1000:if(buf[0] == 1)
{
printf(" 登錄成功 \n");
memcpy(IP,&buf[1],16); //被設(shè)置為全局變量
printf("IP is %s\n",IP);
PORT = buf[17] + buf[18]*256;
printf("PORT is %d\n",PORT); //被設(shè)置為全局變量
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(" 注冊(cè)成功 \n");
}
else
{
printf("注冊(cè)失敗\n");
}
break;
case 1004:printf(" 網(wǎng)絡(luò)出錯(cuò),請(qǐng)重新發(fā)送 \n");
break;
}
}
/******************************************************獲得在線連表*************************************************/
readserver: //使用goto到達(dá)這里
maxfd = fileno(stdin);
FD_ZERO(&set); //將關(guān)注的集合清零
FD_SET(fd,&set); //將套接字加入關(guān)注集合
FD_SET(maxfd,&set); //將鍵盤加入關(guān)注集合
maxfd = (maxfd > fd ? maxfd : fd) + 1; //提取掃描的范圍
while(1)
{
rset = set; //將關(guān)注集合備份
if((nfound = select(maxfd,&rset,(fd_set *)0,(fd_set*)0,NULL)) < 0)
{ //掃描關(guān)注集合
if(errno == EINTR)
{
fprintf(stderr,"interrupted system call\n");
continue;
}
perror("select");
exit(1);
}
if(FD_ISSET(fd,&rset)) //測(cè)試sock是否有變化
{
readback = read(fd,head,2); //讀出頭文件
readback = read(fd,&datalen,1); //讀出長(zhǎng)度
readback = read(fd,buf,datalen); //讀出所有的數(shù)據(jù)
headdata = head[0] + head[1]*256;
if(headdata == 1003)
{
pack_len = buf[0]; //提取總的節(jié)點(diǎn)的長(zhǎng)度
free_friend_on(fhead); //每次建立連表的時(shí)候先釋放以前的連表
for(i = 0;i < pack_len;i++) //循環(huán)每個(gè)節(jié)點(diǎn)
{
memset(friend,0,24); //清零
memcpy(friend,&buf[1+24*i],24); //提取解點(diǎn)
fhead = add_friend(friend,fhead); //建立在線客戶的連表
}
print_friend(fhead);
printf("請(qǐng)輸入你要聊天的朋友姓名\n");
}
else
{
continue; //收到的heddata不是1003就從新掃描
}
}
if(FD_ISSET(fileno(stdin),&rset))
{
memset(buffriend,0,sizeof(buffriend)); //將name數(shù)組清零
fnameback = getname_max(buffriend, 6); //判斷名字是否符合要求,并取得名字(小于 4 個(gè)字節(jié))
while(fnameback == -1)
{
memset(buffriend,0,sizeof(buffriend)); //將name數(shù)組清零
printf("請(qǐng)重新輸入你的姓名,至多還輸入 %d\n",fnamecount);
fnameback = getname_max(buffriend, 6);
printf("fnameback is %d\n",fnameback);
if(fnameback == -1)
{
fnamecount--; //登錄時(shí)輸入姓名錯(cuò)誤計(jì)數(shù)器減 1
}
if(fnameback == 1)
{
break; //登錄時(shí)輸入姓名正確退出循環(huán)
}
if(fnamecount == 0)
{
exit(0); //登錄時(shí)輸入錯(cuò)誤超過(guò)三次自動(dòng)退出
}
}
fp = buffriend;
fj = 0;
while(*fp != '\n') //將 \n 轉(zhuǎn)換為 \0 因此讀取數(shù)組的長(zhǎng)度
{
fj++;
fp++;
}
buffriend[fj] = '\0'; //將最后面的\n賦值為\0
pp = fhead;
while(pp != NULL)
{
if((back =strcmp((pp->onname),buffriend)) == 0)//對(duì)比連表查找ip 和 port
{
break; //找到ip 和 port 退出
}
pp = pp->fnext; //指向下一個(gè)節(jié)點(diǎn)
}
if(pp != NULL)
{
break; //如果找到就跳出循環(huán)
}
}
}
printf("friend pp->onname is %s\n",(pp->onname));//打印聊天朋友的名字
printf("friend pp->fonport is %d\n",(pp->fonport));//打印聊天朋友的port
/*********************************************開(kāi)始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); //關(guān)注集合清零
FD_SET(sockfd,&uset); //udp套接字加入關(guān)注集合
FD_SET(umaxfd,&uset); //鍵盤輸入也加入關(guān)注的集合
umaxfd = (umaxfd > sockfd ? umaxfd : sockfd) + 1; //找出最大的掃描量
while(1)
{
urset = uset; //備份關(guān)注集合
if((unfound = select(umaxfd,&urset,(fd_set*)0,(fd_set*)0,NULL)) < 0)
{ //沒(méi)有變化就阻塞
if(errno == EINTR)
{
fprintf(stderr,"interrupted system call\n");
continue;
}
perror("select");
exit(1);
}
if(FD_ISSET(fileno(stdin),&urset)) //檢測(cè)鍵盤是否有輸入
{
memset(ubuf,0,sizeof(ubuf));
system("date");
printf("請(qǐng)輸入你的聊天內(nèi)容~~~~~~~");
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) //發(fā)送給好友
{
perror("ERROR writing to UDP socket");
break;
}
}
if(FD_ISSET(sockfd,&urset)) //檢測(cè)好友有沒(méi)有發(fā)送東西過(guò)來(lái)
{
uint32_t length = sizeof(cliaddr);
memset(ubuf,0,sizeof(ubuf));
if((ret = recvfrom(sockfd,ubuf,sizeof(ubuf)-1,0,(struct sockaddr*)&cliaddr,&length)) < 0) //接受好友發(fā)過(guò)來(lái)的信息
{
perror("ERROR reading from UDP socket");
break;
}
ubuf[ret] = 0; //將最后的賦值為\n
system("date");
printf("你接受的內(nèi)容是 :%s\n",ubuf);
}
}
close(sockfd); //關(guān)閉套接字
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) //防止內(nèi)存泄露 釋放節(jié)點(diǎn)的程序
{
flink p = fhead;
while(fhead != NULL)
{
p = fhead;
fhead = p->fnext;
free(p);
}
}
/************************************************************************************************************/
flink add_friend(unsigned char *friend,flink fhead)
{ //服務(wù)器端發(fā)過(guò)來(lái)的新的在線客戶,將新的在線客戶添加到連表
flink p = fhead,s = NULL,tail = NULL;
s = (flink)malloc(sizeof(friendlink));//malloc新的空間
if(s == NULL)
{
printf("malloc fail\n"); //創(chuàng)建失敗直接返回首地址
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; //數(shù)據(jù)長(zhǎng)度是 1
buf[3]=1; //發(fā)給服務(wù)器的是數(shù)據(jù)是 1
while(1)
{
writeback = write(fd,buf,4);
if(writeback == -1)
{
printf("heart beat write fail\n ");
}
sleep(3); //每隔 3 秒發(fā)送 一個(gè) 1 給服務(wù)器
}
}
/****************************************下面主界面的打印程序***************************************/
int select_printf()
{
int select;
printf("\t\t\t\t 1.登錄 \t\t\n");
printf("\t\t\t\t 2.注冊(cè) \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輸入錯(cuò)誤請(qǐng)重新輸入\t\t\n");
printf("\t\t\t\t 1.登錄 \t\t\n");
printf("\t\t\t\t 2.注冊(cè) \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; //輸入字符的計(jì)數(shù)器
while (1)
{
*q = getchar(); //逐個(gè)取出緩存中的輸入
counter ++;
if (counter >= maxlen) //當(dāng)counter > maxlen時(shí) 將所有的輸入清零
{
if(*q == '\n')
{
memset(p, 0, maxlen); //清空剛才接受的字符數(shù)組
return -1;
}
if(*q != '\n') //當(dāng)counter >> maxlen是輸入的 \n 將所有的輸入清零
{
while (getchar() != '\n'); //把剩下的接受完
memset(p, 0, maxlen); //清空剛才接受的字符數(shù)組
return -1;
}
}
if (*q == '\n') //counter < maxlen 當(dāng)接受到 時(shí)就返回 1
{
return 1; //返回 1
}
else
{
q ++; //將指針 q 移到下一個(gè)位子
}
}
}
/*****************************************下面是獲取密碼的程序*********************************************/
int getpsword_max(unsigned char *p, unsigned int maxlen) //最多接受字符串maxlen-2
{
unsigned char *q = p;
unsigned int counter = 0; //輸入字符的計(jì)數(shù)器
while (1)
{
*q = getchar(); //逐個(gè)取出緩存中的輸入
counter ++;
if (counter >= maxlen) //當(dāng)counter > maxlen時(shí) 將所有的輸入清零
{
if(*q == '\n') //maxlen是輸入的 \n 將所有的輸入清零
{
memset(p, 0, maxlen); //清空剛才接受的字符數(shù)組
return -1;
}
if(*q != '\n') //當(dāng)counter >> maxlen是輸入的 \n 將所有的輸入清零
{
while (getchar() != '\n'); //把剩下的接受完
memset(p, 0, maxlen); //清空剛才接受的字符數(shù)組
return -1; //返回 -1
}
}
if (*q == '\n') //counter < maxlen 當(dāng)接受到 時(shí)就返回 1
{
return 1; //返回 1
}
else
{
q ++; //將指針 q 移到下一個(gè)位子
}
}
}
/****************************************下面是客戶登錄的程序************************************************/
int client_login(int fd) //客戶登錄的程序
{
unsigned char buf[250] = {0};
unsigned char name[6] ={0}; //獲得名字的字符數(shù)組 最多輸入 5 個(gè)字符
unsigned char psword[4] = {0}; //獲得密碼的字符數(shù)組 最多輸入 3 個(gè)字符
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("請(qǐng)輸入你的姓名(小于 4 個(gè)字節(jié))至多3次\n");
memset(name,0,sizeof(name)); //將name數(shù)組清零
nameback = getname_max(name, 6); //判斷名字是否符合要求,并取得名字(小于 4 個(gè)字節(jié))
while(nameback == -1)
{
memset(name,0,sizeof(name)); //將name數(shù)組清零
printf("請(qǐng)重新輸入你的姓名,至多還輸入 %d\n",namecount);
nameback = getname_max(name, 6);
if(nameback == -1)
{
namecount--; //登錄時(shí)輸入姓名錯(cuò)誤計(jì)數(shù)器減 1
}
if(nameback == 1)
{
break; //登錄時(shí)輸入姓名正確退出循環(huán)
}
if(namecount == 0)
{
exit(0); //登錄時(shí)輸入錯(cuò)誤超過(guò)三次自動(dòng)退出
}
}
//namelen = strlen(name);//獲取密碼的長(zhǎng)度(不能使用strlen的去長(zhǎng)度 psword 遇到\0 就結(jié)束)
p = name;
j = 0;
while(*p != '\n') //將 \n 轉(zhuǎn)換為 \0 因此讀取數(shù)組的長(zhǎng)度
{
j++;
p++;
}
name[j] = '\0'; //將最后面的\n賦值為\0
buf[2] = 10;
memcpy(&buf[3],name,6);
/*------------------------------------------------------------------名字取得完畢*/
memset(psword,0,sizeof(psword)); //將psword數(shù)組清零
printf("請(qǐng)輸入你的密碼(小于 3 個(gè)字節(jié))至多3次\n");
pswordback = getpsword_max(psword,4);//判斷密碼是否符合要求,并取得密碼(小于 3 個(gè)字節(jié))
while(pswordback == -1)
{
memset(psword,0,sizeof(psword)); //將psword數(shù)組清零
printf("請(qǐng)重新輸入你的密碼,至多還輸入 %d\n",namecount);
pswordback = getpsword_max(psword,4);
if(pswordback == -1)
{
pswordcount--; //密碼輸入錯(cuò)誤計(jì)數(shù)器減 1
}
if(pswordback == 1)
{
break; //密碼輸入正確退出while語(yǔ)句
}
if(pswordcount == 0)
{
exit(0); //密碼輸入錯(cuò)誤超過(guò)三次自動(dòng)退出
}
}
//pswordlen = strlen(psword);//獲取密碼的長(zhǎng)度(不能使用strlen的去長(zhǎng)度 psword 遇到 \0 就結(jié)束)
p = psword;
j = 0;
while(*p!='\n') //將 \n 轉(zhuǎn)換為 \0 因此讀取數(shù)組的長(zhǎng)度
{
j++;
p++;
}
psword[j] = '\0'; //將最后面的 \n 賦值為 \0
memcpy(&buf[9],psword,4); //將密碼賦值給以地址buf[13]開(kāi)頭的地址
/*-------------------------------------------------------------密碼取得完畢*/
writeback = write(fd,buf,13);
if(writeback == -1)
{
printf("write error\n");
return 1;
}
}
/****************************************下面是客戶注冊(cè)的程序************************************************/
int client_zhuce(int fd) //客戶端的注冊(cè)程序
{
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數(shù)組清零
printf("請(qǐng)輸入你的姓名(小于 5 字節(jié))至多3次\n");
nameback = getname_max(name, 6);//判斷名字是否符合要求,并取得名字(小于 5 字節(jié))
while(nameback == -1)
{
memset(name,0,sizeof(name)); //將name數(shù)組清零
printf("請(qǐng)重新輸入你的姓名,至多還輸入 %d\n",namecount);
nameback = getname_max(name, 6);
if(nameback == -1)
{
namecount--; //輸入的姓名不符合規(guī)定計(jì)數(shù)器減 1
}
if(nameback == 1)
{
break; //輸入的姓名正確退出循環(huán)
}
if(namecount == 0)
{
exit(0); //輸入姓名的次數(shù)超過(guò)三次退出
}
}
//namelen = strlen(name);//獲取密碼的長(zhǎng)度(不能使用strlen的去長(zhǎng)度 psword 遇到 \0 就結(jié)束)
p = name;
while(*p != '\n') //將 \n 轉(zhuǎn)換為 \0 因此讀取數(shù)組的長(zhǎng)度
{
j++;
p++;
}
name[j] = '\0'; //將最后面的\n賦值為\0
buf[2] = 10;
memcpy(&buf[3],name,6);
/*------------------------------------------------------名字取得完畢*/
memset(psword,0,sizeof(psword)); //將psword數(shù)組清零
printf("請(qǐng)輸入你的密碼(小于3個(gè)字符)\n");
pswordback = getpsword_max(psword,4); //判斷密碼是否符合要求,并取得密碼(小于3個(gè)字符)
while(pswordback == -1)
{
memset(psword,0,sizeof(psword)); //將psword數(shù)組清零
printf("請(qǐng)重新輸入你的密碼,至多還輸入 %d\n",namecount);
pswordback = getpsword_max(psword,4);
if(pswordback == -1)
{
pswordcount--; //輸入的密碼不符合規(guī)定計(jì)數(shù)器減 1
}
if(pswordback == 1)
{
break; //輸入的密碼正確退出循環(huán)
}
if(pswordcount == 0)
{
exit(0); //輸入密碼的次數(shù)超過(guò)三次退出
}
}
//pswordlen = strlen(psword);//獲取密碼的長(zhǎng)度(不能使用strlen的去長(zhǎng)度 psword 遇到 \0 就結(jié)束)
p = psword;
j = 0;
while(*p!= '\n') //將 \n 轉(zhuǎn)換為 \0 因此讀取數(shù)組的長(zhǎng)度
{
j++;
p++;
}
psword[j] = '\0'; //將最后面的\n賦值為\0
memcpy(&buf[9],psword,4); //將密碼賦值給以地址buf[13]開(kāi)頭的地址
/*--------------------------------------------------密碼取得完畢*/
writeback = write(fd,buf,13); //將打包好的數(shù)據(jù)發(fā)給服務(wù)器
printf("writeback = %d\n",writeback);
if(writeback == -1)
{
perror("error");
return 1;
}
}
/********************************以上是注冊(cè)讀出服務(wù)器的返回的在線客戶連表*******************************/
復(fù)制代碼
服務(wù)器端:
#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 //某一時(shí)刻可以監(jiān)聽(tīng)的個(gè)數(shù)
#define BUFFER_SIZE 250 //buf的大小
#define ECHO_PORT 20000 //服務(wù)器的port
/*******************************************************************************************************************/
typedef struct client //注冊(cè)的結(jié)構(gòu)體
{
unsigned char username[6]; //客戶的姓名
unsigned char userpsword[4]; //客戶的密碼
struct client *next; //指向注冊(cè)成功的下一個(gè)指針
}clientlink,*lclientlink;
typedef struct onlineclient //在線用戶的結(jié)構(gòu)體
{
unsigned char onuser[6]; //在線用戶的名字
unsigned char onip[16]; //在線用戶的ip
uint16_t onport; //在線用戶的port
int onfd; //登錄成功的套接字
int count; //心跳的計(jì)數(shù)器
int flag; //設(shè)計(jì)登錄的標(biāo)記
struct onlineclient *onnext; //指向下一個(gè)在線客戶的指針
}onclient,*onuser;
/*******************************************************************************************************************/
onuser onhead = NULL; //全局變量的在線客戶連表頭指針
unsigned char BUFIP[16] = {0}; //獲得上線客戶端的16個(gè)字節(jié)的ip
uint16_t CLIENTPORT = 0; //獲得上線客戶端的2個(gè)字節(jié)的port
int CLIENT_FD = 0; //登錄成功的套接字
/*******************************************************************************************************************/
void jiluname_psword(int fd,unsigned char *buf,unsigned char datalen);
//注冊(cè)的程序
void cmpname_psword(int fd,unsigned char *buf,unsigned char datalen);
//登錄的程序
onuser add_mes_to_onuser(unsigned char *name,int fd,onuser onhead);
//將登錄完的客戶的信息補(bǔ)充完整
void record_onuser_send(int fd,unsigned char *buf,onuser onhead);
//在線客戶連表廣播給剛登錄的客戶
void head_default(int fd,unsigned char *buf);
//頭文件出錯(cuò)的程序
lclientlink read_clientlink();
//打開(kāi)注冊(cè)過(guò)的文件程序
int save_link(lclientlink head);
//保存注冊(cè)過(guò)的文件程序
void add_onuser();
//添加新登錄的客戶程序
onuser reseach(onuser onhead,int fd);
//查找剛上線的客戶程序
void printfonuser(onuser onhead);
//打印在線客戶連表的程序
int recive_heatbeat(int fd);
//接受客戶的心跳程序
int scan_onlive_user();
//遍歷整個(gè)在線連表程序
/*******************************************************************************************************************/
int main(int argc,char **argv) //服務(wù)器的主函數(shù)
{
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);
//將字符串表示的地址轉(zhuǎn)換成協(xié)議大端字符地址
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) //監(jiān)聽(tīng)
{
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); //將關(guān)注集合清零
FD_SET(request_sock,&set); //將監(jiān)聽(tīng)套節(jié)字置 1
maxfd = request_sock; //將監(jiān)聽(tīng)套節(jié)字賦值給掃描范圍
while(1)
{
rset = set; //備份關(guān)注集合
timeout.tv_sec =0; // 秒鐘設(shè)置為 0
timeout.tv_usec = 500000; //設(shè)置為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("."); //沒(méi)有人上線就打點(diǎn)
fflush(stdout); //清空緩存
continue;
}
if(FD_ISSET(request_sock,&rset))
{
addrlen = sizeof(remote); //獲取結(jié)構(gòu)體的字節(jié)數(shù)
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);
/******************************************連接上了服務(wù)器的客戶信息****************************************/
memcpy(BUFIP,(inet_ntoa(remote.sin_addr)),16); //獲取上線的的ip 16
CLIENTPORT = ntohs(remote.sin_port); //獲取上線的port 4
CLIENT_FD = new_sock; //上線客戶的套接字 4
add_onuser(); //添加客戶的信息
/*******************************************以上連接上了服務(wù)器的客戶信息********************************/
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); // 讀出文件的頭并檢測(cè)
if(bytesread < 0) //數(shù)據(jù)讀取失敗
{
perror("read");
}
headlen = headdata[0] + headdata[1] * 256; //解析出 頭
bytesread = read(fd,&datalen,1); //讀出文件中數(shù)據(jù)的長(zhǎng)度并檢測(cè)
if(bytesread < 0) //讀取數(shù)據(jù)失敗
{
perror("read");
}
bytesread = read(fd,buf,datalen); //讀出文件中的數(shù)據(jù)并檢測(cè)
if(bytesread < 0) //讀取數(shù)據(jù)失敗
{
perror("read");
}
if(bytesread == 0) //數(shù)據(jù)讀取完畢
{
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); //注冊(cè)程序
break;
case 1002:recive_heatbeat(fd); //接受客戶的心跳程序
break;
default: head_default(fd,buf); //協(xié)議頭出現(xiàn)錯(cuò)誤程序
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就刪除節(jié)點(diǎn)
{
tail = p;
p = p->onnext;
}
tail->onnext = p->onnext;
break;
}
if(p == NULL) //如果能夠掃描結(jié)束解退出循環(huán)
{
break;
}
}
p =onhead;
while(p != NULL) //給計(jì)數(shù)器賦值
{
(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"); //理論上不應(yīng)該有空連表
return -1;
}
else
{
while(p != NULL)
{
if((p->onfd) == fd) //接受到心跳包 就將 該 fd 對(duì)應(yīng)的計(jì)數(shù)器清零
{
(p->count) = 0; //清零處理
printf("\n");
}
p = p->onnext; //指向下一個(gè)成員
}
}
}
/************************************************************************************************************************************************/
void head_default(int fd,unsigned char *buf)//head出現(xiàn)錯(cuò)誤
{
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)
{ //服務(wù)器端記載注冊(cè)的人
int writeback = 0;
int saveback = 0;
lclientlink head = NULL,s = NULL;
head = read_clientlink(); //打開(kāi)文件并讀文件將返回值給head
printf("buf is %s\n",buf);
saveback = add_client(head,buf); //將注冊(cè)人的信息添加到連表中去
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() //打開(kāi)文件并讀文件
{
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; //打開(kāi)文件失敗返回-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) //創(chuàng)建連表
{
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) //判斷此用戶明是否被注冊(cè)過(guò)
{
if((strcmp((p1->username),name)) == 0 )
{
printf("hello\n");
return -1;
}
p1 = p1->next;
}
memcpy((s->username),name,6); //讀取buf中的 6 個(gè)字節(jié)數(shù)據(jù)
memcpy((s->userpsword),buf + 6,4); //讀取buf中的 4個(gè)字節(jié)數(shù)據(jù)
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) //存儲(chǔ)連表
{
lclientlink p = head;
int fid ;
int writeback;
fid = open("client.ini",O_WRONLY|O_APPEND);
if(fid == -1)
{
printf("open error \n");
return -1; //打開(kāi)文件失敗返回-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)
{ //服務(wù)器端處理客戶的登錄
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數(shù)組清零
memset(psword,0,4); //psword數(shù)組清零
memcpy(name,buf,6); //將buf中的copy到name數(shù)組中去
memcpy(psword,buf + 6,4); //將剩下buf中的數(shù)據(jù)copy到psword數(shù)組中去
head = read_clientlink();
p = head;
while(p != NULL)
{
cmpnameresult = strcmp((p->username),name); //對(duì)比名字
cmppswordresult = strcmp((p->userpsword),psword); //對(duì)比密碼
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); //發(fā)送 4 個(gè)字節(jié)后 將在線連表 發(fā)送國(guó)去
if(writeback == -1)
{
printf("write error\n");
}
onhead = add_mes_to_onuser(name,fd,onhead); //將登錄上的客戶信息補(bǔ)充完整
record_onuser_send(fd,buf,onhead);//記錄登錄成功的客戶 并將登錄成功的好友信息發(fā)給剛上線的人
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一個(gè)新的空間
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來(lái)添加姓名 和 將登錄成功的標(biāo)志賦值
{
memcpy((p->onuser),name,6); //補(bǔ)充名字
p->flag = 1; //賦值登錄成功的標(biāo)志位
}
p = p->onnext; //下一個(gè)節(jié)點(diǎn)
}
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) //將連表中的所有在線的客戶發(fā)送給 剛上線的客戶
{
if((p->flag) == 1) //登錄成功的人才被發(fā)送出去
{
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; //指向下一個(gè)節(jié)點(diǎn)
}
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;
}
}
/************************************************************************************************************************************************/
復(fù)制代碼
作者:
wei0212016
時(shí)間:
2016-4-19 09:03
好紐幣啊?床欢。
歡迎光臨 (http://www.torrancerestoration.com/bbs/)
Powered by Discuz! X3.1