目录
- 1.UDP群聊的功能
- 2.写项目的流程
- 3.流程图
- 4.代码实现
- 4.1头文件
- 4.2函数
- 4.3服务器
- 4.4客户端
1.UDP群聊的功能
有新用户登录,其他在线的用户可以收到登录信息
有用户群聊,其他在线的用户可以收到群聊信息
有用户退出,其他在线的用户可以收到退出信息
服务器可以发送系统信息
2.写项目的流程
画流程图
根据流程图写框架
一个功能一个功能实现
3.流程图

4.代码实现
4.1头文件
#ifndef __MYHEAD_H__
#define __MYHEAD_H__
#include <head.h>
#define N 512
//聊天操作用的结构体
typedef struct _MSG{
char ch;//用来'l'聊天,'q'退出,'登录d'
char name[128];//存名字
char text[N];//存聊天内容
}msg_t;
//用来保存每个用户信息的结构体
typedef struct _Jilu{
struct sockaddr_in addr;
struct _Jilu *next;
}jilu_t;
int create_head(jilu_t **head);
int input_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len);
int wx_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len);
int tuichu_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len);
#endif
4.2函数
#include "myhead.h"
//创建一个单链表头
int create_head(jilu_t **head)
{
if(head==NULL){
printf("传送错误,请检查\n");
return -1;
}
(*head)=(jilu_t *)malloc(sizeof(jilu_t));
(*head)->next=NULL;
return 0;
}
//记录登录的用户的信息
int input_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len)
{
if(head==NULL){
printf("传送错误,请检查\n");
return -1;
}
//将这个用户登录的信息发送给所有人
snprintf(msg.text,sizeof(msg.text),"[%s]%s",msg.name,"登录了");
//这个用来记录头的地址
jilu_t *jilu_head=head;
while(jilu_head->next!=NULL){
jilu_head=jilu_head->next;
if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&jilu_head->addr,clientaddr_len)==-1){
ERRLOG("将用户登录信息发给所有人失败");
}
}
//创建一个新的节点,并且把新的用户信息放入新得单列表
jilu_t *temp=NULL;
create_head(&temp);
temp->addr=clientaddr;
//用头插法将用户信息插入链表
temp->next=head->next;
head->next=temp;
return 0;
}
int wx_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len)
{
if(head==NULL){
printf("传送错误,请检查\n");
return -1;
}
//将接受到的消息发给除了自己以外的所有人
jilu_t *jilu_head=head;
while(jilu_head->next!=NULL){
jilu_head=jilu_head->next;
if(0!=memcmp(&(jilu_head->addr),&clientaddr,sizeof(clientaddr))){
if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&jilu_head->addr,clientaddr_len)==-1){
ERRLOG("将聊天内容发给所有人失败");
}
}
}
return 0;
}
int tuichu_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len)
{
if(head==NULL){
printf("传送错误,请检查\n");
return -1;
}
snprintf(msg.text,sizeof(msg.text),"%s%s",msg.name,"退出登录");
jilu_t *jilu_head=head;
jilu_t *pdel=NULL;
while(jilu_head->next!=NULL){
if(0==memcmp(&(jilu_head->next->addr),&clientaddr,sizeof(clientaddr))){
pdel=jilu_head->next;
jilu_head->next=pdel->next;
free(pdel);
pdel=NULL;
}else{
jilu_head=jilu_head->next;
if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&jilu_head->addr,clientaddr_len)==-1){
ERRLOG("将这个退出的信息告诉所有人失败");
}
}
}
return 0;
}
4.3服务器
#include "myhead.h"
int main(int argc, char const *argv[])
{
int sockfd=0;
pid_t pid=0;
msg_t msg;//用来进行各种操作
msg_t faso;//用来发系统消息
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1){
ERRLOG("创建服务器套接字失败");
}
//将网络信息结构体放入服务器中
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr=inet_addr(argv[1]);
socklen_t serveraddr_len=sizeof(serveraddr);
//将套接字与网络信息结构体绑定
if(bind(sockfd,(struct sockaddr*)&serveraddr,serveraddr_len)==-1){
ERRLOG("将套接字与网络信息结构体绑定失败");
}
//创建一个新的网络信息结构体来存客户端的信息
struct sockaddr_in clientaddr;
clientaddr.sin_family=AF_INET;
socklen_t clientaddr_len=sizeof(clientaddr);
//创建进程
pid=fork();
if(pid==-1){
ERRLOG("服务器创建进程失败");
}else if(pid==0){
//创建一个单列表保存网络信息结构体
jilu_t *head;
create_head(&head);
memset(&msg,0,sizeof(msg));
while(1){
if(recvfrom(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&clientaddr,&clientaddr_len)==-1){
ERRLOG("接受客户端传来的信息失败");
}
switch(msg.ch){
case 'd'://登录信息
input_addr(head,msg,sockfd,clientaddr,clientaddr_len);
//head->next=NULL; //这个用来测试用的
break;
case 'l'://聊天信息
wx_addr(head,msg,sockfd,clientaddr,clientaddr_len);
break;
case 'q'://退出信息
tuichu_addr(head,msg,sockfd,clientaddr,clientaddr_len);
break;
}
}
}else{
while(1){
//发系统消息
memset(&faso,0,sizeof(faso));
fgets(faso.text,sizeof(faso.text),stdin);
faso.text[strlen(faso.text)-1]='\0';
faso.ch='l';
sprintf(faso.name,"%s","系统消息");
if(sendto(sockfd,&faso,sizeof(faso),0,(struct sockaddr*)&serveraddr,serveraddr_len)==-1){
ERRLOG("发送系统消息失败");
}
}
}
return 0;
}
4.4客户端
#include "myhead.h"
int main(int argc, char const *argv[])
{
//判断输入的对不对
if(argc!=3){
printf("输入格式错误,./a.out ip port\n");
exit(EXIT_SUCCESS);
}
int sockfd=0;
pid_t pid=0;
msg_t msg;//创建发送用户的信息
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1){
ERRLOG("创建客户端套接字失败");
}
//将客户端网络信息结构体进行绑定
struct sockaddr_in clientaddr;
memset(&clientaddr,0,sizeof(clientaddr));
clientaddr.sin_family=AF_INET;
clientaddr.sin_port=htons(atoi(argv[2]));
clientaddr.sin_addr.s_addr=inet_addr(argv[1]);
socklen_t clientaddr_len=sizeof(clientaddr);
//输入用户的姓名进行登录操作
msg.ch='d';
printf("请输入你用来登录的姓名");
fgets(msg.name,sizeof(msg.name),stdin);
msg.name[strlen(msg.name)-1]='\0';
//给服务器发送用户已经登录上的操作
if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&clientaddr,clientaddr_len)==-1){
ERRLOG("客户端给服务器发送的登录信息失败");
}
//创建进程,子进程用来接受,父进程用来发送
pid=fork();
if(pid==-1){
ERRLOG("客户端创建进程失败");
}else if(pid==0){
//用来接受服务器发来的消息
while(1){
memset(&msg,0,sizeof(msg));
if(recvfrom(sockfd,&msg,sizeof(msg),0,NULL,NULL)==-1){
ERRLOG("接受服务器发来的信息错误");
}
printf("[%s]>>(%s)\n",msg.name,msg.text);
}
}else{
//写入要判聊天的内容
while(1){
memset(msg.text,0,sizeof(msg.text));
fgets(msg.text,sizeof(msg.text),stdin);
msg.text[strlen(msg.text)-1]='\0';
if(strncmp("quit",msg.text,5)==0){
msg.ch='q';
}else{
msg.ch='l';
}
//将写好的内容发送给服务器
if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&clientaddr,clientaddr_len)==-1){
ERRLOG("将聊天内容发送给服务器失败");
}
//当识别到停止的时候,关闭进程
if(strncmp("quit",msg.text,5)==0){
kill(pid,SIGKILL);
close(sockfd);
exit(EXIT_SUCCESS);
}
}
}
return 0;
}