1、基于UDP的网络聊天室
服务器:
#include<myhead.h>//定义一个结构体存储成员信息
typedef struct Node
{struct sockaddr_in cin;struct Node *next;
}*Linklist;//定义一个结构体表示消息类型
typedef struct _msg
{char code; //L:登录,C:群聊,Q:退出,S:系统消息char name[20];char text[128];
}msg_t;
//登录操作的函数
int do_login(int sfd,msg_t msg,Linklist head,struct sockaddr_in cin)
{//打印登录成功printf("%s [%s:%d]登录成功\n,",msg.name, (char*)inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));sprintf(msg.text,"----------%s登陆成功---------\n",msg.name);//先遍历链表,将新用户加入群聊的消息发给所有人Linklist p = head;while(p->next != NULL){p = p->next;//给客户端发送消息sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(p->cin), sizeof(p->cin));}//将新用户的网络信息结构体,头插入链表Linklist temp = NULL;if((temp = (Linklist)malloc(sizeof(struct Node*))) == NULL){printf("malloc error\n");return -1;}temp->cin = cin;temp->next = NULL;head->next = temp;return 0;
}
void do_chat(int sfd, msg_t msg, Linklist head,struct sockaddr_in cin)
{printf("%s [%s:%d]chat成功\n",msg.name,(char*)inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));char buf[200]="";sprintf(buf, "%s:%s",msg.name, msg.text);strcpy(msg.text, buf);//遍历链表,将群聊消息发送给客户端Linklist p = head;while(p->next != NULL){p = p->next;//判断是否给自己发送消息if(memcmp(&cin, &(p->cin), sizeof(cin))){sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(p->cin), sizeof(p->cin));}}return ;
}
void do_quit(int sfd,msg_t msg,Linklist head, struct sockaddr_in cin)
{sprintf(msg.text,"---------%s已下线------\n",msg.name);//遍历链表,是自己就将自己在链表里删除//不是自己就发送下线通知Linklist p=head;Linklist del = NULL;while(p->next != NULL){if(memcmp(&(p->next->cin),&cin,sizeof(cin))){//不是自己sendto(sfd,&msg,sizeof(msg), 0, (struct sockaddr*)&(p->cin),sizeof(p->cin));}else{del = p->next;p->next = del->next;free(del);del = NULL;}}return ;
}
int main(int argc, const char *argv[])
{//判断终端输入是否违规if(argc != 3){printf("请输入ip地址和端口号!!!\n");return -1;}//1、创建用于通信的套接字int sfd = socket(AF_INET, SOCK_DGRAM, 0);if(sfd == -1){perror("socket error");return -1;}//2、绑定//填充地址信息struct sockaddr_in sin;sin.sin_family = AF_INET; //地址族sin.sin_port = htons(atoi(argv[2])); //端口号sin.sin_addr.s_addr = inet_addr(argv[1]); //ip地址//将地址信息绑定到服务器if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1){perror("bind error\n");return -1;}//定义客户端地址信息用于接收struct sockaddr_in cin;socklen_t socklen = sizeof(cin);msg_t msg; //定义接收的消息pid_t pid = fork();if(pid == -1){perror("fork error");return -1;}else if(pid > 0){//父进程:用于发送系统消息strcpy(msg.name, "*ststem*:");msg.code = 'C'; //服务端向客户端群发消息while(1){//清空消息结构体memset(&msg,0,sizeof(msg));//输入要发送的消息fgets(msg.text, sizeof(msg.text), stdin);msg.text[strlen(msg.text)-1] = 0;//发送消息sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&sin, sizeof(sin));}}else{//子进程//创建链表头结点存储客户端信息Linklist head=NULL;head = (Linklist)malloc(sizeof(struct Node));if(head == NULL){printf("error\n");return -1;}memset(head, 0, sizeof(head));head->next = NULL;while(1){//清空数据和新用户信息memset(&msg, 0, sizeof(msg));memset(&cin, 0, sizeof(cin));//接收客户端发送的信息recvfrom(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&cin, &socklen);//判断消息操作码switch(msg.code){case 'L':{do_login(sfd, msg, head, cin);}break;case 'C':{do_chat(sfd, msg, head, cin);}break;case 'Q':{do_quit(sfd, msg, head, cin);}break;}}}return 0;
}
客户端:
#include<myhead.h>//定义消息类型结构体
typedef struct msg
{char code; //操作码char name[20];char text[128];
}msg_t;
typedef void (*sighandler_t)(int);
void handler(int sig)
{//回收子进程资源并退出while(waitpid(-1, NULL, WNOHANG) > 0);exit(0);
}int main(int argc, const char *argv[])
{//判断终端输入if(argc != 3){printf("请输入ip地址和端口号!\n");return -1;}//注册信号处理函数,让子进程退出后,父进程回收子进程资源并退出sighandler_t s = signal(SIGCHLD, handler);if(s == SIG_ERR){perror("signal error");return -1;}//创建用于通信的套接字int cfd = socket(AF_INET, SOCK_DGRAM, 0);if(cfd == -1){perror("socket error");return -1;}//2、绑定//填充地址信息结构体struct sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(atoi(argv[2]));sin.sin_addr.s_addr = inet_addr(argv[1]);//清空消息内容msg_t msg;memset(&msg, 0, sizeof(msg));//输入用户名printf("请输入用户名>>>");fgets(msg.name, sizeof(msg.name), stdin);msg.name[strlen(msg.name)-1] = 0;msg.code = 'L';//给服务器发送消息sendto(cfd, &msg,sizeof(msg),0, (struct sockaddr*)&sin,sizeof(sin));//创建进程pid_t pid = fork();if(pid == -1){perror("fork error");return -1;}else if(pid > 0){//父进程//循环终端接受数据并发给客户端while(1){memset(msg.text, 0, sizeof(msg.text));fgets(msg.text, sizeof(msg.text),stdin); //在终端获取聊天信息msg.text[strlen(msg.text)-1] = 0;//判断输入的聊天内容if(strcmp(msg.text,"quit") == 0){msg.code = 'Q';strcpy(msg.text, "下线了");}else{msg.code = 'C';}sendto(cfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,sizeof(sin));if(msg.code == 'Q')exit(0);}}else{//子进程//循环接收并打印消息while(1){recvfrom(cfd, &msg, sizeof(msg), 0, NULL,NULL);}printf("[%s]:%s\n",msg.name,msg.text);}close(cfd);return 0;
}