基于UDP的网络聊天室

项目需求:

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


服务器

#include <myhead.h>

#define IP "127.0.0.1"
#define PORT 4399

typedef struct sockaddr_in datatype;//类型重命名

//创建信息结构体
typedef struct msg
{
	char type;//操作码 'L'登录 'C'群聊 'Q'退出
	char name[20];
	char text[128];
}msg_t;

//创建链表保存地址信息
typedef struct Node
{
	union
	{
		datatype resin;//数据域
		int len;//头结点数据域
	};

	struct Node *next;//指针域
}Node, *Linklistptr;

//需要传入分支线程的参数
struct Climsg
{
	int sfd;
	datatype sin;
};
void *task(void *arg);
//注册函数
int deal_login(int sfd, Linklistptr L, datatype cin, msg_t msg);
//群聊
int deal_chat(int sfd, Linklistptr L, datatype cin, msg_t msg);
//退出
int deal_quit(int sfd, Linklistptr L, datatype cin, msg_t msg);
//创建链表
Linklistptr list_create();
//申请节点封装地址信息
Linklistptr node_buy(datatype cin);
//头插
int list_insert_head(Linklistptr L,datatype cin);

int main(int argc, const char *argv[])
{

	//创建报式套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}
	printf("socket create success sfd=%d\n",sfd);

	//填充接收方的地址信息结构体,给bind函数使用
	
	datatype 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("bind success\n");


	//创建接收地址信息结构体
	datatype cin;
	socklen_t c_addrlen = sizeof(cin);
	
	pthread_t tid;
	msg_t msg;


	//创建一个链表
	Linklistptr L = list_create();
	if(NULL == L)
	{
		return -1;
	}

	struct 	Climsg info;

	while(1)
	{
	
		//主线程负责接收并处理
		if(recvfrom(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&cin, &c_addrlen) < 0)
		{
			ERR_MSG("recvfrom");
			return -1;
		}
		printf("[%s:%d] : %s\n", msg.name, ntohs(cin.sin_port), msg.text);

		switch(msg.type)
		{
		case 'L'://登录
				deal_login(sfd, L, cin, msg);
			break;
		case 'C'://群聊
				deal_chat(sfd, L, cin, msg);
			break;
		case 'Q'://退出
				deal_quit(sfd, L, cin, msg);
			break;
		default :
			printf("输入错误\n");
			break;
		}
		
		info.sfd = sfd;
		info.sin = sin;
		//分支线程只负责发送系统信息
		if(pthread_create(&tid, NULL, task, (void *)&info) != 0)
		{
			fprintf(stderr, "pthread_create failed__%d__\n",__LINE__);
			return -1;
		}

		pthread_detach(tid);

	}

	//关闭文件描述符
	if(close(sfd) < 0)
	{
		ERR_MSG("close");
		return -1;
	}
	return 0;
}


void *task(void *arg)
{

	int sfd = ((struct Climsg*)arg)->sfd;
	datatype sin = ((struct Climsg*)arg)->sin;
	msg_t msg;
	msg.type = 'C';	

	while(1)
	{	

		//从终端获取消息文本
		fgets(msg.text, sizeof(msg.text), stdin);
		msg.text[strlen(msg.text)-1] = '\0';
		//将信息包名定为服务器
		strcpy(msg.name,"servce");

		if(sendto(sfd , &msg, sizeof(msg), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
		{
			ERR_MSG("sendto");
		}
	}
	close(sfd);
	pthread_exit(NULL);
}

//创建链表
Linklistptr list_create()
{
	//从堆区申请一个头结点类型
	Linklistptr L = (Linklistptr)malloc(sizeof(Node));
	if(NULL == L)
	{
		printf("创建失败\n");
		return NULL;
	}

	//创建成功后,对节点进行初始化工作
	L->len = 0;
	L->next = NULL;

	return L;

}


//申请节点封装地址信息
Linklistptr node_buy(datatype cin)
{
	//在堆区申请节点
	Linklistptr p = (Linklistptr)malloc(sizeof(Node));
	if(NULL == p)
	{
		printf("申请失败]n");
		return NULL;
	}

	//节点申请成功,封装数据
	p->resin = cin;
	p->next = NULL;
	
	return p;
	

}

//头插
int list_insert_head(Linklistptr L,datatype cin)
{
	//判断逻辑
	if(NULL == L)
	{
		printf("所给链表不合法\n");
		return 0;
	}
	//调用节点封装数据
	
	Linklistptr p = node_buy(cin);
	if(NULL == p)
	{
		return 0;
	}

	//插入逻辑
	p->next = L->next;
	L->next = p;

	//表长变化
	L->len++;

	return 1;
}
//注册函数
int deal_login(int sfd, Linklistptr L, datatype cin, msg_t msg)
{

	Linklistptr p = L;//遍历指针
	while(p->next != NULL)
	{
		p=p->next;

		if(sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&(p->resin), sizeof(p->resin)) < 0)
		{
			ERR_MSG("sendto");
			return -1;
		}

	}

	//将自己的信息插入链表
	list_insert_head(L,cin);

	return 0;
}
//群聊
int deal_chat(int sfd, Linklistptr L, datatype cin, msg_t msg)
{
	Linklistptr p = L;

	while(p->next != NULL)
	{
		p=p->next;
		if(memcmp(&(p->resin), &cin, sizeof(cin)))
		{
			if(sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&(p->resin), sizeof(p->resin)) < 0)
			{
				ERR_MSG("sendto");
				return -1;
			}
		}
	}

	return 0;
}

//退出
int deal_quit(int sfd, Linklistptr L, datatype cin, msg_t msg)
{
	Linklistptr p = L;
	
	while(p->next != NULL)
	{
		if(memcmp(&(p->next->resin), &cin, sizeof(cin)))
		{
			p = p->next;
			if(sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&(p->resin), sizeof(p->resin)) < 0)
			{
				ERR_MSG("sendto");
				return -1;
			}

		}
		else//此时当前节点的下一个节点保存的就是要退出的成员的信息
		{

			Linklistptr q = p->next;
			if(sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&(q->resin), sizeof(q->resin)) < 0)
			{
				ERR_MSG("sendto");
				return -1;
			}

			p->next = q->next;
			free(q);
			q = NULL;
		}
	}

	return 0;	
}

客户端

#include <myhead.h>

#define IP "127.0.0.1"
#define PORT 4399

typedef struct sockaddr_in datatype;//类型重命名

//创建信息结构体
typedef struct msg
{
	char type;//操作码 'L'登录 'C'群聊 'Q'退出
	char name[20];
	char text[128];
}msg_t;

struct Climsg
{
	int cfd;
	datatype cin;
	msg_t msg;
};

void *task(void *arg);



int main(int argc, const char *argv[])
{
		//创建报式套接字
	int cfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(cfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}
	printf("socket create success cfd=%d\n",cfd);

	//填充接收方的地址信息结构体,给sendto函数使用
	
	datatype sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	sin.sin_addr.s_addr = inet_addr(IP);


	//创建接收地址信息结构体
	datatype cin;
	socklen_t c_addrlen = sizeof(cin);
	
	pthread_t tid;
	msg_t msg;
	memset(&msg, 0, sizeof(msg_t));
	struct Climsg info;

	printf("请输入登录名>>>");
	fgets(msg.name, sizeof(msg.name), stdin);
	msg.name[strlen(msg.name)-1] = '\0';
	
	msg_t msg1=msg;
	msg.type = 'L';
	strcpy(msg.text,"已进入群聊");

	//发送登录请求包
	if(sendto(cfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		ERR_MSG("sendto");
		return -1;
	}
	printf("登陆成功\n");

	info.cfd = cfd;
	info.cin = sin;
	info.msg = msg;
	
	while(1)
	{
		
		memset(&msg, 0, sizeof(msg));
		if(recvfrom(cfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&cin, &c_addrlen) < 0)
		{
			ERR_MSG("recvfrom");
			return -1;
		}
		if(strcmp(msg.name, msg1.name) == 0)
		{
			break;
		}

		printf("[%s] : %s\n", msg.name, msg.text);


		if(pthread_create(&tid, NULL, task, (void *)&info) != 0)
		{
			ERR_MSG("pthread_create");
			return -1;
		}
	
		//将分支线程设置为分离态
		pthread_detach(tid);
		}

	//关闭套接字描述符
	close(cfd);
	return 0;
}

//线程体函数
void *task(void *arg)
{

	int cfd = ((struct Climsg*)arg)->cfd;
	datatype sin = ((struct Climsg*)arg)->cin;
	msg_t msg = ((struct Climsg*)arg)->msg;

	while(1)
	{
		//清空文本内容
		bzero(msg.text, sizeof(msg.text));
		//从终端获取数据
		fgets(msg.text, sizeof(msg.text), stdin);
		msg.text[strlen(msg.text)-1] = '\0';

		msg.type = 'C';
		if(strcmp(msg.text,"quit") == 0)
		{
			msg.type = 'Q';
			strcpy(msg.text, "已下线");
		}
		if(sendto(cfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
		{
			ERR_MSG("sendto");
		}
		if(strcmp(msg.text, "已下线") == 0)
		{
			break;
		}
	}
	close(cfd);
	pthread_exit(NULL);
}

猜你喜欢

转载自blog.csdn.net/qq_53478460/article/details/132656903
今日推荐