基于UDP的网络聊天室

项目需求:

  1. 如果有用户登录,其他用户可以收到这个人的登录信息
  2. 如果有人发送信息,其他用户可以收到这个人的群聊信息
  3. 如果有人下线,其他用户可以收到这个人的下线信息
  4. 服务器可以发送系统信息

写项目的方法:

  1. 画流程图
  2. 根据流程图写框架
  3. 将每个功能实现(一个一个实现,不要一起写)

服务器代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

//链表结构体
typedef struct node{
	union{
		int len;
		struct sockaddr_in node_cin;
	};
	struct node *next;
}linklist;

//函数声明
void* send_msg(void*arg);
linklist *list_create();
int list_empty(linklist *l);
linklist *create_node(struct sockaddr_in cin);
int list_insert(linklist *l,struct sockaddr_in cin);
int list_delete(linklist *l,int pos);
int list_search_value(linklist *l,struct sockaddr_in cin);
int list_delete_head(linklist *l);
void list_free(linklist *l);

//数据包结构体协议
typedef struct data{
	char type;    //操作码,用于确定数据包的类型
	char name[20];  //数据包名字,用于确定是客户端的姓名
	char text[520];  //文本信息
}DataPack;

//创建链表变量
linklist *l;

//宏定义
#define ERR_MSG(msg) do{\
	fprintf(stderr,"line:%d\n",__LINE__);\
	perror(msg);\
}while(0)
#define IP "192.168.8.129"
#define PORT 6666

int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd=socket(AF_INET, SOCK_DGRAM,0);
	if(sfd < 0){
		ERR_MSG("socket");
		return -1;
	}
	//允许端口快速重用
	int reuse = 1;
	if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) < 0){
		ERR_MSG("setsockopt");
		return -1;
	}
	//创建真实信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	sin.sin_addr.s_addr = inet_addr(IP);
	//绑定服务器
	if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) < 0){
		ERR_MSG("bind");
		return -1;
	}
	printf("*******************温馨提示*******************\n");
	printf("网络聊天室服务器已打开,各位用户可以登录聊天\n");
	printf("如果需要关闭服务器,请输入'system quit'\n");
	printf("**********************************************\n");
	//定义变量
	DataPack datapack;
	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);
	int newfd = 0;
	ssize_t res = 0;
	//创建链表
	l=list_create();
	if(NULL == l){
		return -1;
	}
	//创建线程
	pthread_t tid;
	if(pthread_create(&tid,NULL,&send_msg,&sfd) != 0){
		fprintf(stderr,"line:%d pthread_create failed\n",__LINE__);
		return -1;
	}
	while(1){
		bzero(datapack.name,sizeof(datapack.name));
		bzero(datapack.text,sizeof(datapack.text));
		//接收数据包
		res = recvfrom(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&cin,&addrlen);
		if(res < 0){
			ERR_MSG("recvfrom");
			return -1;
		}
		//判断数据包类型
		if('L' == datapack.type){
			printf("[%s:%d]:%s已上线\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),datapack.name);
			//如果是登录,则加入链表中
			list_insert(l,cin);
			linklist* q=l->next;
			for(int i=0; i<l->len ;i++){
				if((q->node_cin.sin_port != cin.sin_port) && (q->node_cin.sin_addr.s_addr == cin.sin_addr.s_addr)){
				if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&(q->node_cin),sizeof(q->node_cin))<0){
				ERR_MSG("sendto");
				break;
				}
				}
				q=q->next;
			}
		}else if('C' == datapack.type){
			linklist* q=l->next;
			printf("[%s:%d]:%s:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),datapack.name,datapack.text);
			//如果是消息
			for(int i=0; i<l->len ;i++){
				if((q->node_cin.sin_port != cin.sin_port) && (q->node_cin.sin_addr.s_addr == cin.sin_addr.s_addr)){
				if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&(q->node_cin),sizeof(q->node_cin))<0){
				ERR_MSG("sendto");
				break;
				}
				}
				q=q->next;
			}
		}else if('Q' == datapack.type){
			//如果是下线,则遍历链表,找到下线的客户端,剔除下线的客户端
			if(strcmp(datapack.text,"system quit")==0){
				printf("聊天室服务器关闭\n");
				break;
			}
		}else if('q'==datapack.type){
			printf("%s已下线\n",datapack.name);
			linklist* q = l->next;
			for(int i=0; i<l->len ;i++){
				if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&(q->node_cin),sizeof(q->node_cin))<0){
				ERR_MSG("sendto");
				break;
				}
				q=q->next;
			}
			int pos=list_search_value(l,cin);
			list_delete(l,pos);
		}
	}
	list_free(l);
	close(sfd);
	return 0;
}

void* send_msg(void* arg){
	//分离线程
	pthread_detach(pthread_self());
	//定义结构体变量
	DataPack datapack;
	//定义套接字管道符
	int sfd = *(int*)arg; 
	while(1){
		//修改数据包操作码
		datapack.type = 'S';
		//清空datapack.text
		bzero(datapack.text,sizeof(datapack.text));
		//从终端输入,将内容放入datapack.text中
		fgets(datapack.text,sizeof(datapack.text),stdin);
		datapack.text[strlen(datapack.text)-1] = '\0';
		linklist* q = l->next;
		socklen_t node_addrlen = sizeof(q->node_cin);
		if(strcmp(datapack.text,"system quit") == 0){
			datapack.type = 'Q';
			for(int i=0; i<l->len ;i++){
				if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&(q->node_cin),node_addrlen)<0){
				ERR_MSG("sendto");
				break;
				}
				q=q->next;
			}
			break;
		}
		//发送系统消息
		for(int i=0; i<l->len ;i++){
			if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&(q->node_cin),node_addrlen)<0){
				ERR_MSG("sendto");
				break;
			}
			q=q->next;
		}
	}
}


//创建链表
linklist *list_create(){
	//申请一个结点空间
	linklist *l = (linklist *)malloc(sizeof(linklist));
	if(NULL == l){
		printf("创建失败\n");
		return NULL;
	}
	//创建成功进行初始化
	l->len=0;
	l->next=NULL;
	return l;
}

//链表判空
int list_empty(linklist *l){
	return l->next==NULL;
}

//申请结点封装数据
linklist *create_node(struct sockaddr_in cin){
	//在堆区申请一个结点的空间
	linklist *p=(linklist*)malloc(sizeof(linklist));
	if(NULL==p){
		printf("空间申请失败\n");
		return NULL;
	}
	//将数据封装到结点的数据域中
	p->node_cin = cin;
	p->next=NULL;
	return p;
}

//插入链表
int list_insert(linklist *l,struct sockaddr_in cin){
	if(NULL==l){
		printf("插入链表失败,所给链表不合法\n");
		return 0;
	}
	//申请结点封装数据
	linklist *p=create_node(cin);
	//头插逻辑
	p->next=l->next;
	l->next=p;
	//表的变化
	l->len++;
	return 1;
}
//按值查找
int list_search_value(linklist *l,struct sockaddr_in cin){
	if(list_empty(l) || NULL == l){
		printf("查找失败\n");
		return -1;
	}
	linklist *q=l->next;
	for(int i=1; i<=l->len;i++){
		if((q->node_cin.sin_port == cin.sin_port) && (q->node_cin.sin_addr.s_addr == cin.sin_addr.s_addr)){
			return i;
		}
		q=q->next;
	}
	return 0;
}

//删除结点
int list_delete(linklist *l,int pos){
	if(NULL == l || list_empty(l) || pos<1 ||pos>l->len){
		printf("删除失败\n");
		return 0;
	}
	linklist *q=l;
	for(int i=0;i<pos-1;i++){
		q=q->next;
	}
	linklist *p=q->next;
	q->next=p->next;
	free(p);
	l->len--;
	return 1;
}

//头删
int list_delete_head(linklist *l){
    if(NULL==l || list_empty(l)){
        printf("删除失败\n");
        return 0;
    }
    linklist *q=l->next;
    l->next=q->next;
    free(q);
    l->len--;
    printf("删除成功\n");
    return 1;
}

//释放链表                       
void list_free(linklist *l){
    if(NULL!=l){
        //循环进行头删
        while(l->next != NULL){
            list_delete_head(l);
        }
        free(l);
        l=NULL;
    }
}

客户端代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

//函数声明
void* send_msg(void*arg);

//数据包结构体协议
typedef struct data{
	char type;    //操作码,用于确定数据包的类型
	char name[20];  //数据包名字,用于确定是客户端的姓名
	char text[520];  //文本信息
}DataPack;

//传参结构体
struct pass_value{
	struct sockaddr_in sin;
	int sfd;
	DataPack datapack;
};

//宏定义
#define ERR_MSG(msg) do{\
	fprintf(stderr,"line:%d\n",__LINE__);\
	perror(msg);\
}while(0)
#define IP "192.168.8.129"
#define PORT 6666

int main(int argc, const char *argv[])
{
	//传参结构体
	struct pass_value pas_val;
	//创建报式套接字
	pas_val.sfd=socket(AF_INET, SOCK_DGRAM,0);
	if(pas_val.sfd < 0){
		ERR_MSG("socket");
		return -1;
	}

	//创建真实地址信息结构体
	pas_val.sin.sin_family = AF_INET;
	pas_val.sin.sin_port = htons(PORT);
	pas_val.sin.sin_addr.s_addr = inet_addr(IP);
	//发送客户端名字
	bzero(pas_val.datapack.text,sizeof(pas_val.datapack.text));
	pas_val.datapack.type = 'L';
	printf("请输入登录名字:");
	scanf("%s",pas_val.datapack.name);
	while(getchar()!=10);
	if(sendto(pas_val.sfd,&pas_val.datapack,sizeof(pas_val.datapack),0,(struct sockaddr*)&(pas_val.sin),sizeof(pas_val.sin)) < 0){
		ERR_MSG("sendto");
		return -1;
	}
	printf("登录成功\n");
	//创建线程
	pthread_t tid;
	if(pthread_create(&tid,NULL,&send_msg,&pas_val) != 0){
		fprintf(stderr,"line:%d pthread_create failed\n",__LINE__);
		return -1;
	}
	//定义接收数据包结构体变量
	DataPack datapack;
	ssize_t res = 0;
	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);
	while(1){
		bzero(datapack.name,sizeof(datapack.name));
		bzero(datapack.text,sizeof(datapack.text));
		//接收数据包
		res = recvfrom(pas_val.sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&cin,&addrlen);
		if(res < 0){
			ERR_MSG("recvfrom");
			return -1;
		}
		if('L' == datapack.type){
			printf("%s已上线\n",datapack.name);
		}else if('S' == datapack.type){
			printf("---system---:%s\n",datapack.text);
		}else if('C' == datapack.type){
			printf("%s:%s\n",datapack.name,datapack.text);
		}else if('Q' == datapack.type){
			if(sendto(pas_val.sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&cin,sizeof(cin)) < 0){
				ERR_MSG("sendto");
				return -1;
			}
			printf("聊天室服务器关闭,用户强制下线\n");
			pthread_cancel(tid);
			break;
		}else if('q'==datapack.type){
			if(strcmp(datapack.name,pas_val.datapack.name)==0){
				printf("退出聊天室成功\n");
				break;
			}else{
				printf("%s已下线\n",datapack.name);
			}
		}
	}
	pthread_join(tid,NULL);
	close(pas_val.sfd);
	return 0;
}

void* send_msg(void* arg){
	//定义传参变量
	DataPack datapack =(*(struct pass_value*)arg).datapack; 
	int sfd = (*(struct pass_value*)arg).sfd;
	struct sockaddr_in sin = (*(struct pass_value*)arg).sin;
	while(1){
		bzero(datapack.text,sizeof(datapack.text));
		fgets(datapack.text,sizeof(datapack.text),stdin);
		datapack.text[strlen(datapack.text)-1] = '\0';
		//发送下线消息
		if(strcmp(datapack.text,"quit") == 0){
			datapack.type = 'q';
			if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&sin,sizeof(sin))<0){
				ERR_MSG("sendto");
				break;
			}
			break;
		}else{
			//发送群聊消息
			datapack.type = 'C';
			if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&sin,sizeof(sin))<0){
				ERR_MSG("sendto");
				break;
			}
		}
	}
	pthread_exit(NULL);
}


运行结果:

        登录界面:

 

         聊天界面:

                

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值