基于UDP的网络多人聊天室

UDP服务器

#include <myheader.h>

//宏定义打印错误信息
#define PRINT_ERR(msg)                                  \
	do                                                  \
	{                                                   \
		printf("%S,%D,%S\n",__FILE__,__LINE__,__func__);\
		perror(msg);                                    \
		exit(-1);                                       \
	}while(0);

//定义一个结构体
//由于客户端给服务器发送第数据内容较多,定义一个结构体来发送
typedef struct
{
	char code;  //操作码 'L'登录 'C'群聊 'Q'退出
	char name[32];   //保存登录用户名
	char txt[128];   //保存发送的信息
}msg_t;

//定义一个链表
// 给在线所有客户端发送数据,将每一个客户端的信息用链表来保存
typedef struct NODE
{
	struct sockaddr_in c_addr;   //数据域  客户端第网络信息结构体
	struct NODE *next;   // 指针域   保存下一个结点的地址
}node_t;

//创建一个链表头第函数,定义链表头结点
//链表头结点函数
void creat_link(node_t **head)
{
	*head = (node_t *)malloc(sizeof(node_t));
}

int do_register(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
	//遍历链表将登录信息发送给所以人
    node_t *p = phead;
    while (p->next != NULL)
    {
        p = p->next;
        if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
        {
            perror("recvfrom error");
        }
    }
    //将登录的客户端信息插入保存在链表
    //头插
    //定义一个新的指针保存客户端信息
    node_t *newp = NULL;
    creat_link(&newp);
    newp->c_addr = clientaddr;
    newp->next = phead->next;
    phead->next = newp;
    return 0;
}

int do_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
	//遍历链表,将消息发给除自己之外的所有人
    node_t *p = phead;
    while (p->next != NULL)
    {
        p = p->next;
        //判断链表客户端信息是否是自己
        //是自己就不发送
        if (memcmp(&(p->c_addr), &clientaddr, sizeof(clientaddr)))
        {
            if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
            {
                perror("recvfrom error");
            }
        }
    }
    return 0;
}

//退出群聊操作
int quit_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
	node_t *p = phead;

    while (p->next != NULL)
    {
        //判断链表客户端信息是否是自己
        //是自己就不发送并且将自己的客户端信息在链表内删除
        if (memcmp(&(p->next->c_addr), &clientaddr, sizeof(clientaddr)))
        {
            p = p->next;
            if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
            {
                perror("recvfrom error");
            }
        }
        else
        {
            node_t *pnew;
            pnew = p->next;
            p->next = pnew->next;
            pnew->next = NULL;
            free(pnew);
            pnew = NULL;
        }
    }
    return 0;
}

int main(int argc, const char *argv[])
{
	//入参合理性判断
	if(argc != 3)
	{
		printf("age:%s ip port\n",argv[0]);
		return -1;
	}

	//创建套接字
	int sfd = socket(AF_INET,SOCK_DGRAM,0);
	if(sfd == -1)
	{
		perror("socket error");
		return -1;
	}
	printf("sfd = %d\n",sfd);

	//创建服务器网络信息结构体
	struct sockaddr_in sin;
	memset(&sin,0,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(atoi(argv[2]));
	sin.sin_addr.s_addr = inet_addr(argv[1]);
	socklen_t sin_len = sizeof(sin);

	//绑定
	if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");

	//创建客户端的网络信息结构体
	struct sockaddr_in cin;
	memset(&cin,0,sizeof(cin));
	socklen_t cin_len = sizeof(cin);
	msg_t msg;

	//建一个父子进程 
	//用来实现服务器既可以发送系统消息,又可以接收客户端的信息
	 pid_t pid;
	 pid = fork();
	 if(pid == -1)
	 {
		 //创建错误
		 perror("fork error");
	 }
	 else if(pid == 0)
	 {
		 //子进程
		 //接收数据并处理
		 //循环接收客户端发来的信息,通过Switch判断code所存的协议
		 //定义链表头结点
		 node_t *phead = NULL;
		 creat_link(&phead);
		 phead->next = NULL;
			

		 //循环接受客户端发来的信息并通过switch进行判断执行哪个功能函数
		 while (1)
		 {
			 memset(&msg, 0, sizeof(msg));//清空操作
			 memset(&cin, 0, sizeof(cin));//清空操作
			 if ((recvfrom(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&cin, &cin_len)) == -1)
			 {
				 perror("recvfrom error");
			 }
			 printf("%8s : [%s]\n", msg.name, msg.txt);
			 switch (msg.code)
			 {
			 case 'L':
				 do_register(sfd, msg, cin, phead);
				 break;
			 case 'C':
				 do_group_chat(sfd, msg, cin, phead);
				 break;
			 case 'Q':
				 quit_group_chat(sfd, msg, cin, phead);
				 break;
			 }
		 }
	 }
	 else if(pid > 0)
	 {

		 //父进程
		 //发送系统信息
		 //视为一个客户端,向子进程发送消息给在线客户
		 msg.code='C';
		 strcpy(msg.name,"server");
		 while(1)
		 {
			 fgets(msg.txt,128,stdin);
			 msg.txt[strlen(msg.txt)-1]='\0';
			 if(sendto(sfd,&msg,sizeof(msg_t),0,(struct sockaddr *)&sin,sin_len)==-1)
			 {
				 perror("sendto error");
			 }
		 }
		close(sfd);
		return 0;
	 }
	return 0;
}

UDP客户端

#include <myheader.h>

//宏定义打印错误信息
#define perror(msg)                                      \
    do                                                      \
    {                                                       \
        printf("%s,%d,%s\n", __FILE__, __LINE__, __func__); \
        perror(msg);                                        \
        exit(-1);                                           \
    } while (0)

typedef struct
{
    char code; //操作码 'L' 登录  'C' 群聊  'Q' 退出
    char name[32];
    char txt[128];
} msg_t;

int main(int argc, const char *argv[])
{
	//入参合理性判断
    if (argc != 3)
    {
        printf("age:%s ip port\n", argv[0]);
        return -1;
    }
    //创建套接字
    int sfd;
    if ((sfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        perror("socket error");
    }
    //创建服务器网络信息结构体
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = inet_addr(argv[1]);
    sin.sin_port = htons(atoi(argv[2]));
    socklen_t sin_len = sizeof(sin);
    //给服务器发送登录数据包
    msg_t msg;
    memset(&msg, 0, sizeof(msg_t));
    msg.code = 'L';
    printf("请输入用户名:");
    fgets(msg.name, 32, stdin);
    msg.name[strlen(msg.name) - 1] = '\0';

    strcpy(msg.txt, "加入群聊");
    if (sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&sin, sin_len) == -1)
    {
        perror("sendto error");
    }
    //创建父子进程
    pid_t pid;
    pid = fork();
    if (pid == -1)
    {
        perror("fork error");
    }
	else if (pid == 0)
    {
        //子进程
        //接受数据并处理
        while (1)
        {
            //每次循环前将msg置零
            memset(&msg, 0, sizeof(msg));
            //接受服务器发过来的信息并打印到终端上
            if (recvfrom(sfd, &msg, sizeof(msg_t), 0, NULL, NULL) == -1)
            {
                perror("recvfrom error");
            }
            printf("%8s:[%s]\n", msg.name, msg.txt);
        }
    }
    else if (pid > 0)
    {
        //父进程
        //发送消息
        while (1)
        {   
            //memset会把name清除
            msg.code = 'C';
            fgets(msg.txt, 128, stdin);
            msg.txt[strlen(msg.txt) - 1] = '\0';
            if (strcmp(msg.txt, "quit") == 0)
            {
                msg.code = 'Q';
                strcpy(msg.txt, "退出群聊");
            }
            if (sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&sin, sin_len) == -1)
            {
                perror("sendto error");
            }
            if (strcmp(msg.txt, "退出群聊") == 0)
            {
                break;
            }
        }
        kill(pid,SIGKILL);
        wait(NULL);
        close(sfd);
	}
	return 0;
}
相关推荐
ProcessOn官方账号39 分钟前
如何绘制网络拓扑图?附详细分类解说和用户案例!
网络·职场和发展·流程图·拓扑学
Ven%1 小时前
如何在防火墙上指定ip访问服务器上任何端口呢
linux·服务器·网络·深度学习·tcp/ip
神的孩子都在歌唱2 小时前
TCP/IP 模型中,网络层对 IP 地址的分配与路由选择
网络·tcp/ip·智能路由器
阿雄不会写代码2 小时前
ubuntu安装nginx
linux·服务器·网络
starstarzz2 小时前
计算机网络实验四:Cisco交换机配置VLAN
网络·计算机网络·智能路由器·vlan·虚拟局域网
EasyDSS3 小时前
国标GB28181-2022平台EasyGBS:安防监控中P2P的穿透方法
网络协议·php·音视频·p2p
网安墨雨4 小时前
常用网络协议
网络·网络协议
Tlzns4 小时前
Linux网络——UDP的运用
linux·网络·udp
黑客老陈4 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
HSunR6 小时前
计算机网络
网络·计算机网络