Linux C语言 UDP协议实现的网络聊天室

需求分析

  • 网络协议:UDP
  • 服务器需求:
    1. 需要一个存放用户数据的容器
      • 因为是UDP协议,固采用链表的方式存储
    2. 服务器需要区分用户的操作类型(如:上线,下线,发送消息)
      • 需要解析消息协议区分用户行为
    3. 因为存在多个阻塞IO,需要多进程,多线程,或IO复用实现
      • 本文采用,多线程实现
  • 客户端需求:
    1. 客户端发送的消息,需要带有相应的消息类型
      • 客户端发送的消息需要自定义一个消息头
    2. 能够接收服务器转发消息,并区分出消息发送者

消息协议及其核心函数

c 复制代码
//消息结构体
typedef struct msg
{
	unsigned short type;
	unsigned char name[32];
	unsigned char data[128];
} Msg;

//登录事件
int Conn_Event(int server, Msg msg, ClistPtr C, struct sockaddr_in caddr);

//消息事件
int Data_Event(int server, Msg msg, ClistPtr C, struct sockaddr_in caddr);

//退出事件
int Close_Event(int server, Msg msg, ClistPtr C, struct sockaddr_in caddr);

登录事件核心功能实现

c 复制代码
//通过遍历链表的方式将用户上线消息发送给所有其他成员
while(p->next != NULL)
{
	p = p->next;
	if(sendto(server, &msg, sizeof(msg), 0, (struct sockaddr*)&(p->caddr), sizeof(p->caddr)) < 0)
	{
		LOG("sendto error");
		return -1;
	}

}
//当遍历结束后将登录用户信息加入链表
//链表头插
list_insert_head(C, caddr);

数据事件核心功能实现

c 复制代码
//遍历链表将用户发送的消息进行群发
while(C->next != NULL)
{
	C = C->next;
	if(memcmp(&(C->caddr), &caddr, sizeof(caddr)) != 0)
	{
		sendto(server, &msg, sizeof(msg), 0, (struct sockaddr*)&(C->caddr), sizeof(C->caddr));
	}
}

断连事件核心功能实现

c 复制代码
if(NULL == C->next)
{
	printf("最后一位用户%s已下线\n", msg.name);
	return 0;
}
		
while(p->next != NULL)
{
	//找到断连用户节点的前一个节点,进行节点删除
	if(memcmp(&(p->next->caddr), &caddr, sizeof(caddr)) == 0)
	{
		list_delete(p);
	}
	else
	{
		p = p->next;
		if(sendto(server, &msg, sizeof(msg), 0, (struct sockaddr*)&p->caddr, sizeof(p->caddr)) < 0)
		{
			LOG("sendto error");
			return -1;
		}			
	}
}

服务器函数

c 复制代码
#include "udp_sever.h"

#define LOG(s) printf("[%s] {%s:%d} %s \n", __DATE__, __FILE__, __LINE__, s);

//线程参数结构体
typedef struct
{
	int server;
	Msg msg;
	ClistPtr C;
	struct sockaddr_in caddr;
	
} Arg_t;

//线程转发函数
void* forward(void* arg)
{
	int server = ((Arg_t*)arg)->server;
	Msg msg = ((Arg_t*)arg)->msg;
	ClistPtr C = ((Arg_t*)arg)->C;
	struct sockaddr_in caddr = ((Arg_t*)arg)->caddr;
	
	Data_Event(server, msg, C, caddr);
	pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
	//创建结构体变量
	struct sockaddr_in saddr = {0};

	ClistPtr C = list_create();
	if(NULL == C)
	{
		LOG("list_creat error");
		return -1;
	}
	
	if(argc != 2)
	{
		printf("please inputs: %s <port>\n", argv[0]);
		return -1;
	}
	
	int server = 0;
	if((server = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		LOG("server socket error");
		return -1;
	}
	
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(atoi(argv[1]));
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	if(bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) < 0)
	{
		LOG("bind error");
		return -1;
	}
	
	printf("udp server start success\n");
	
	int res = -1;
	Msg msg;
	struct sockaddr_in caddr = {0};
	socklen_t len = sizeof(caddr);
	pthread_t tid;
	Arg_t arg;
	
	//循环判断接收事件类型
	while(1)
	{
		if((res = recvfrom(server, &msg, sizeof(msg), 0, (struct sockaddr*)&caddr, &len)) < 0)
		{
			LOG("recvfrom error");
			return -1;
		}
		switch(msg.type)
		{
			//连接事件
			case EVE_CONN:
			Conn_Event(server, msg, C, caddr);
			break;
			
			//数据事件
			case EVE_DATA:
			arg.server = server;
			arg.msg = msg;
			arg.caddr = caddr;
			arg.C = C;
		
			if(pthread_create(&tid, NULL, forward, &arg) < 0)
			{
				LOG("pthread_create error");
				return -1;
			}
			pthread_detach(tid);
			break;
			
			//断开事件
			case EVE_CLOSE:
			Close_Event(server, msg, C, caddr);
			break;
			
			default:
			LOG("event error");
			return -1;
		}
	}
	
	close(server);
	
    return 0;
}

客户端函数

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "udp_sever.h"

#define LOG(s) printf("[%s] {%s:%d} %s\n", __DATE__, __FILE__, __LINE__, s);

//消息结构体
Msg msg;
//文件描述符变量
int sock = 0;
//远端地址变量
struct sockaddr_in remote = {0};

void handler(int sig);

int main(int argc, char const *argv[])
{
	if(argc != 4)
	{
		printf("please inputs: %s <user name> <host port> <server ip> <server port>\n", argv[0]);
		return -1;
	}
	

    //本机地址变量
    struct sockaddr_in addr = {0};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(atoi(argv[2]));
	addr.sin_addr.s_addr = htonl(INADDR_ANY);


	//创建select监听队列和副本
	fd_set reads = {0};
	fd_set temps = {0};
    
    //创建socket
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if( sock == -1 )
    {
       LOG("socket error");
        return -1;
    }
    
	signal(SIGINT, handler);

    //指定一个远端地址
    remote.sin_family = AF_INET;
    remote.sin_addr.s_addr = inet_addr(argv[3]);
    remote.sin_port = htons(atoi(argv[4]));
	
	//登录
	msg.type = EVE_CONN;
	stpcpy(msg.name, argv[1]);
	if(sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr*)&remote, sizeof(remote)) < 0)
	{
		LOG("connect failed");
		return -1;
	}
    
	printf("connect success\n");
	
	FD_ZERO(&reads);
	FD_SET(sock, &reads);
	FD_SET(0, &reads);

	Msg rmsg;
	
    while(1)
    {
		msg.type = EVE_DATA;
		temps = reads;
		//轮询监测收发事件
		int num = select(sock+1, &temps, NULL, NULL, NULL);
		
		if(num > 0)
		{
			if(FD_ISSET(0, &temps))
			{
				bzero(msg.data, sizeof(msg.data));
				fgets(msg.data, sizeof(msg.data), stdin);
				msg.data[strlen(msg.data)-1] = 0;
				
				if(sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr*)&remote, sizeof(remote)) < 0)
				{
					LOG("sendto error");
					return -1;
				}
			}
			else
			{
				int res = -1;
				if((res = recvfrom(sock, &(rmsg), sizeof(rmsg), 0, NULL, NULL)) < 0)
				{
					LOG("recvfrom error");
					return -1;
				}
				else if(0 == res)
				{
					printf("server close \n");
					return -1;
				}
				
				printf("[%s]: %s\n", rmsg.name, rmsg.data);
			}	
		}    
    }
    
    close(sock);
    
    return 0;
}

void handler(int sig)
{
	msg.type = EVE_CLOSE;
	sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr*)&remote, sizeof(remote));
	
	exit(-1);
}
相关推荐
雨中rain16 分钟前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
就爱学编程18 分钟前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
ProcessOn官方账号20 分钟前
如何绘制网络拓扑图?附详细分类解说和用户案例!
网络·职场和发展·流程图·拓扑学
Bessssss36 分钟前
centos日志管理,xiao整理
linux·运维·centos
s_yellowfish36 分钟前
Linux服务器pm2 运行chatgpt-on-wechat,搭建微信群ai机器人
linux·服务器·chatgpt
豆是浪个38 分钟前
Linux(Centos 7.6)yum源配置
linux·运维·centos
vvw&39 分钟前
如何在 Ubuntu 22.04 上安装 Ansible 教程
linux·运维·服务器·ubuntu·开源·ansible·devops
我一定会有钱40 分钟前
【linux】NFS实验
linux·服务器
Ven%44 分钟前
如何在防火墙上指定ip访问服务器上任何端口呢
linux·服务器·网络·深度学习·tcp/ip
是阿建吖!1 小时前
【Linux】基础IO(磁盘文件)
linux·服务器·数据库