靠,終于將 青春版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;
- }
- }
- /************************************************************************************************************************************************/
復制代碼
|