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);
}
相关推荐
学不动CV了16 分钟前
数据结构---链表结构体、指针深入理解(三)
c语言·arm开发·数据结构·stm32·单片机·链表
tan77º17 分钟前
【Linux网络编程】Socket - UDP
linux·服务器·网络·c++·udp
czhc114007566329 分钟前
Linux 76 rsync
linux·运维·python
小白爱电脑42 分钟前
光纤的最小弯曲半径是多少?
网络
蓝易云2 小时前
Qt框架中connect()方法的ConnectionType参数使用说明 点击改变文章字体大小
linux·前端·后端
花落已飘2 小时前
多线程 vs 异步
linux·网络·系统架构
PanZonghui2 小时前
Centos项目部署之Nginx部署项目
linux·nginx
码出钞能力3 小时前
linux内核模块的查看
linux·运维·服务器
星辰云-4 小时前
# Linux Centos系统硬盘分区扩容
linux·运维·centos·磁盘扩容
聽雨2374 小时前
02每日简报20250704
linux·科技·金融·生活·社交电子·娱乐·媒体