UDP 聊天室

来源:互联网 发布:黄金价格用什么软件 编辑:程序博客网 时间:2024/05/22 00:07
--------------udp聊天室V1.0----------------/////////////////////////使用方法首先启动./server再启动./client 不需要加任何的参数/////////////////////////////功能描述支持群聊天支持向指定用户发送悄悄话功能支持不同消息不同颜色显示用户名为登录的唯一标示,所以不允许重名,客户端登录具有重名检查功能支持上线下线通知支持服务器发送系统消息功能支持登录时检测服务器是否在线支持服务器下线通知客户端,客户端强行下线
header.h 头文件//   更新日期 26/01/2013 10:19#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <fcntl.h>#include <dirent.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <errno.h>#include <netdb.h>#include <signal.h>#define BUFFERSIZE 1024typedef struct sockaddr SA;typedef struct sockaddr_in SA_IN;#define SERVER_IP "127.0.0.1"#define SERVER_PORT 8000 
server 端#include "header.h"#include <time.h>typedef struct {char name[10];SA_IN address;} USER;//XXX :用户链表typedef struct Hnode_list {USER data;struct Hnode_list *next;} Hlink, *plink;int memoryError(plink p);int creatUserList(plink head);int findUser(plink head, char name[10]);int delUser(plink head, char name[10]);int getAllUser(plink const head);int addUser(plink head, USER data);//XXX :用户链表void ProcessLogin(char* command, SA_IN rec_addr);void ProcessChat(char* command);void ProcessQuit(char* command);int   ProcessPrivate(char* command,SA_IN rec_addr);void ProcessAd();void sig_int(int signum);//ctl+c关掉服务器static plink head;static int socket_fd;int main(void) {char buf[BUFFERSIZE];SA_IN address, rec_addr;socklen_t length;struct timeval tv;tv.tv_sec=60;//插播广告时间tv.tv_usec=0;head = malloc(sizeof(Hlink));//用户列表头creatUserList(head); //创建列表signal(SIGINT,sig_int);if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {perror("套接字创建失败");exit(-1);}memset(&address, 0, sizeof(address));address.sin_family = AF_INET;address.sin_addr.s_addr = inet_addr(SERVER_IP);address.sin_port = htons(SERVER_PORT);if (bind(socket_fd, (SA *) &address, sizeof(address)) == -1) {perror("套接字绑定失败");exit(-1);}//30秒没有收到任何消息就发送广告printf("服务器启动成功ip:%s\n",inet_ntoa(address.sin_addr));length = sizeof(rec_addr);setsockopt(socket_fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));while (1) {memset(buf,0,sizeof(buf));//服务器只有此处用来接收if (recvfrom(socket_fd, buf, sizeof(buf), 0, (SA *) &rec_addr, &length)== -1){buf[0]='A';//发送广告标志位}switch (buf[0]) {case 'L'://登录ProcessLogin(buf, rec_addr);break;case 'C'://群聊天ProcessChat(buf);break;case 'Q'://退出ProcessQuit(buf);break;case 'A'://广告ProcessAd();break;case 'P'://悄悄话ProcessPrivate(buf,rec_addr);break;default:printf("ERROR");break;}}}void ProcessLogin(char* command, SA_IN rec_addr) {char name[10];USER user;char buf[BUFFERSIZE];memset(buf,'\0',BUFFERSIZE);strcpy(name, command + 1);if (findUser(head, name) == -1) {strcpy(user.name, name);user.address = rec_addr;addUser(head, user);buf[0] = 'Y';if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &rec_addr,sizeof(rec_addr)) == -1){perror("登录失败");}}else {buf[0] = 'N';if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &rec_addr,sizeof(rec_addr)) == -1){perror("登录失败");}}}void ProcessChat(char* command) {char buf[BUFFERSIZE];SA_IN address;memset(buf,'\0',BUFFERSIZE);sprintf(buf, "R%s\n<请输入>\n", command + 1); //'\n'强行推送数据必须否则要到缓冲区满才一次性发送plink p = head->next;while (p != NULL) {address = (p->data).address;if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &address,sizeof(address)) < 0){perror("消息发送失败");}p = p->next;}}void ProcessQuit(char* command) {char name[10];char buf[BUFFERSIZE];memset(buf,'\0',BUFFERSIZE);SA_IN address;sprintf(name, "%s", command + 1);plink p = head->next;delUser(head, name);sprintf(buf, "R%s下线了\n", name);while (p != NULL) {address = (p->data).address;if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &address,sizeof(address)) < 0) {perror("消息发送失败");}p = p->next;}}void ProcessAd(){char buf[BUFFERSIZE];char buf_temp[BUFFERSIZE];memset(buf,'\0',BUFFERSIZE);//必须清空否则会有虚假数据memset(buf_temp,'\0',BUFFERSIZE);SA_IN address;plink p = head->next;strcat(buf,"A<<系统消息>>当前在线用户\n");while (p != NULL){sprintf(buf_temp,"[%s:%s] ", (p->data).name, inet_ntoa((p->data).address.sin_addr));strcat(buf,buf_temp);p = p->next;}strcat(buf,"\n<请输入>\n");p = head->next;while (p != NULL) {address = (p->data).address;if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &address,sizeof(address)) < 0) {perror("消息发送失败");}p = p->next;}}int ProcessPrivate(char* command,SA_IN rec_addr){char buf[BUFFERSIZE];char buf_temp[BUFFERSIZE];char name_from[10];char name_to[10];int non_user_flag=0;int i,j;for(i=1,j=0;command[i]!='$';i++,j++){name_from[j]=command[i];}name_from[j]='\0';i++;for(j=0;command[i]!='$';i++,j++){name_to[j]=command[i];}name_to[j]='\0';i++;memset(buf,'\0',BUFFERSIZE);//必须清空否则会有虚假数据memset(buf_temp,'\0',BUFFERSIZE);strcpy(buf_temp,command+i);strcat(buf_temp,"\n<请输入>\n");//双方名字及消息准备就绪sprintf(buf,"P[来自%s的悄悄话]%s",name_from,buf_temp);plink p = head->next;SA_IN address;while (p != NULL) {if (strcmp((p->data).name, name_to) == 0){address = (p->data).address;break;}p = p->next;}if (p == NULL)non_user_flag=1;if(!non_user_flag){if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &address,sizeof(address)) < 0){perror("悄悄话发送失败");}sprintf(buf,"R%s已经收到消息,悄悄话发送成功\n<请输入>\n",name_to);if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &rec_addr,sizeof(address)) < 0){perror("消息发送失败");}}else{sprintf(buf,"R%s不在线,悄悄话发送失败\n<请输入>\n",name_to);if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &rec_addr,sizeof(address)) < 0){perror("悄悄话发送失败");}}return 0;}void sig_int(int signum){char buf[BUFFERSIZE];plink p = head->next;SA_IN address;sprintf(buf,"Q服务器下线了\n");while (p != NULL) {address = (p->data).address;if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &address,sizeof(address)) < 0){perror("发送下线消息给客户端失败");}p = p->next;}printf("服务器关闭成功\n");exit(0);}//XXX:使用带头结点的单向链表   存放用户信息int memoryError(plink p) //判断内存是否申请成功{if (p == NULL) {printf("MEMORY ERROR!");return 1;}return 0;}int creatUserList(plink head) {if (memoryError(head))return -1;head->next = NULL;}int addUser(plink head, USER data) //始终在表头插入{plink new_create = malloc(sizeof(Hlink));if (memoryError(new_create))return -1;new_create->data = data;new_create->next = head->next;head->next = new_create;}int delUser(plink head, char name[10]) {plink q = head;plink p = head->next;while (p != NULL) {if (strcmp((p->data).name, name) == 0)break;q = p;p = p->next;}if (p == NULL) {printf("删除用户失败\n");return -1;}q->next = p->next;free(p);p = NULL;}int findUser(plink head, char name[10]) {plink p = head->next;while (p != NULL) {if (strcmp((p->data).name, name) == 0)return 0;p = p->next;}if (p == NULL)return -1;}//XXX:使用带头结点的单向链表   存放用户信息

client 端#include "header.h"void sig_user1(int signo);void sig_user2(int signo);void sig_alrm(int signo);char* ProcessLogin(SA_IN serv_addr);//以下为无关紧要的功能函数声明void dispDot();int msSleep(long ms);static pid_t pid;static int socket_fd;int main(void) {char buf[BUFFERSIZE];char buf_temp[BUFFERSIZE];char name[10];SA_IN serv_addr;signal(SIGUSR1, sig_user1);signal(SIGUSR2,sig_user2);signal(SIGINT,SIG_IGN);//拒绝用户ctrl+c强制退出,只能键入quit退出if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {perror("套接字创建失败");exit(-1);}//XXX:服务器配置信息memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);serv_addr.sin_port = htons(SERVER_PORT);//XXXstrcpy(name, ProcessLogin(serv_addr));//得到登录名if ((pid = fork()) == -1) {perror("子进程创建失败");return -1;}if (pid == 0)///////////////////////////////////子进程,用来接收服务器的消息{memset(buf,'\0',BUFFERSIZE);//必须清空否则会有虚假数据memset(buf_temp,'\0',BUFFERSIZE);sprintf(buf, "C%s上线了", name);if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &serv_addr,sizeof(serv_addr)) < 0) {perror("上线失败");}kill(getppid(), SIGUSR1);//唤醒主进程让其输入while (1) {memset(buf,'\0',BUFFERSIZE);//必须清空否则会有虚假数据memset(buf_temp,'\0',BUFFERSIZE);if (recvfrom(socket_fd, buf, sizeof(buf), 0, NULL, NULL) < 0) {perror("接收消息失败");} if (strncmp("Q",buf,1) == 0) //接收服务器下线通知{printf("\033[22;30m%s", buf+1); //输出黑色文字系统消息kill(getppid(), SIGUSR2);kill(getpid(), SIGUSR2);}else if (strncmp("A",buf,1) == 0){printf("\033[22;96m%s", buf+1); //输出灰色文字系统消息}else if (strncmp("P",buf,1) == 0){printf("\033[22;31m%s", buf+1); //输出红色文字系统消息}else if (strncmp("R",buf,1) == 0){if (strncmp(buf+1, name, strlen(name)) == 0){sprintf(buf_temp, "我%s", buf + strlen(name)+1);printf("\033[22;32m%s", buf_temp);//输出黄色文字自己的消息}elseprintf("\033[22;33m[收到消息@]%s", buf+1); //输出绿色文字}printf("\033[22;30m");//恢复黑色}}//////////////////////////////////////////////////////主进程//////////////////////////////////////pause();//等待SIGUSR1int quit_flag = 0;getchar();//清空输入名字时残存的回车符,因为ProcessLogin使用scanfwhile (1)//父进程{memset(buf,'\0',BUFFERSIZE);//必须清空否则会有虚假数据memset(buf_temp,'\0',BUFFERSIZE);fgets(buf_temp,BUFFERSIZE,stdin);//采用fgets比采用scanf好,聊天时可以键入空格buf_temp[strlen(buf_temp)-1]='\0';if (strncmp("quit", buf_temp, 4) == 0) //退出{sprintf(buf, "Q%s", name);quit_flag = 1;}//键入$开头为悄悄话else if (strncmp("$", buf_temp, 1) == 0) //注:发送悄悄话的格式:$对方名称$消息内容{char *check=buf_temp+1;//必须对其检查一定要有两个$...$否则造成服务器当机while(*check!='\0'&&*check!='$')check++;if(*check!='$'){printf("[系统提示]\n发送悄悄话格式:$对方名称$消息内容\n<请输入>\n");continue;}sprintf(buf, "P%s%s", name,buf_temp);}else //群发消息{sprintf(buf, "C%s说:%s", name, buf_temp);}//开始发送消息if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &serv_addr,sizeof(serv_addr)) < 0){perror("发送失败");}if (quit_flag){kill(pid, SIGUSR2);break;}}kill(getpid(), SIGUSR2);}//////////////////////////////////////////////Siganl//////////////////////////////////////////////void sig_user2(int signo) {printf("%d进程结束\n", getpid());exit(-1);}void sig_user1(int signo) {;}void sig_alrm(int signo){return ;}//////////////////////////////////////////////Siganl//////////////////////////////////////////////char* ProcessLogin(SA_IN serv_addr) {char buf[10];static char name[10];struct sigaction act;//处理服务器未上线sigaction(SIGALRM,NULL,&act);act.sa_handler=sig_alrm;act.sa_flags&=~SA_RESTART;//有何用处??????sigaction(SIGALRM,&act,NULL);while (1) {printf("请输入用户名:");buf[0] = 'L';scanf("%s", buf + 1);strcpy(name, buf + 1);alarm(2);//设置连接服务器超时时间为2秒printf("登录中");dispDot();if (sendto(socket_fd, buf, sizeof(buf), 0, (SA *) &serv_addr,sizeof(serv_addr)) < 0){perror("登录失败");}recvfrom(socket_fd, buf, sizeof(buf), 0, NULL, NULL);if (buf[0] == 'N') {printf("该用户名已被使用,请重新输入用户名\n");} else if (buf[0] == 'Y') {printf("%s登录成功\n",name);alarm(0);//必须关闭闹钟return name;break;}else{printf("服务器未上线,退出登录\n");exit(-1);}}}////////////////////一些无关紧要的功能函数void dispDot(){int i;for(i=0;i<10;i++){printf(". ");fflush(stdout);msSleep(90000);}printf("\n");}int msSleep(long ms){    struct timeval tv;    tv.tv_sec = 0;    tv.tv_usec = ms;    return select(0, NULL, NULL, NULL, &tv);}