栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux&C语言简单实现基于UDP的网络群聊聊天室recvfrom/sendto-传输层

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux&C语言简单实现基于UDP的网络群聊聊天室recvfrom/sendto-传输层

目录
  • UDP 客户端 和 服务器 通信
    • 要求
    • 提示
    • 代码实现
      • 服务器---01server.c
      • 客户端---02client.c
      • 头文件---uDP.h
      • 执行结果
    • 注意
  • 精简如下

UDP 客户端 和 服务器 通信 要求

有新用户登录,其他在线的用户可以收到登录信息
有用户群聊,其他在线的用户可以收到群聊信息
有用户退出,其他在线的用户可以收到退出信息
服务器可以发送系统信息

提示

客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程或者多线程
服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程或者多线程
服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存 数据传输的时候要定义结构体,结构体中包含操作码、用户名以及数据

代码实现 服务器—01server.c
//---------------------------服务器-----------------------------
#include "./uDP.h"

//创建节点的函数
int create_node(node_t **pnew, struct sockaddr_in cli_addr);

//使用头插法插入数据
int insert_into_list_by_head(node_t *phead, struct sockaddr_in cli_addr);

int main(int argc, const char *argv[])
{
    if (3 != argc)
    {
        printf("Usage : %s  n", argv[0]);
        exit(-1);
    }
    // 1.创建套接字     // IPV4使用,//UDP
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        ERRLOG("socket error");
    }

    // 2.填充服务器网络信息结构体
    struct sockaddr_in server_addr;
    //清空、填充0
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET; // IPV4
    //端口号  填 8888 9999 6789 ...都可以
    //将无符号2字节整型  主机-->网络
    server_addr.sin_port = htons(atoi(argv[2]));
    // ip地址 要么是当前Ubuntu主机的IP地址 或者
    //如果本地测试的化  使用  127.0.0.1 也可以
    //将strptr所指的字符串转换成32位的网络字节序二进制值。
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);

    //创建头节点-------链表
    //可以使用返回值的方式,也可以使用地址传递的方式
    node_t *phead = NULL;
    create_node(&phead, server_addr);

    //结构体长度
    socklen_t server_addr_len = sizeof(server_addr);

    // 3.将套接字和网络信息结构体绑定//强制类型转换//网络信息结构体
    if (-1 == bind(sockfd, (struct sockaddr *)&server_addr,
                   server_addr_len))
        ERRLOG("bind error");

    //用来保存客户端信息的结构体
    // UPD网络通信
    //如果给发送端回信,就必须保存发送端的网络信息结构体
    struct sockaddr_in client_addr, client_addr_o;
    memset(&client_addr, 0, sizeof(client_addr));
    socklen_t client_addr_len = sizeof(client_addr);
    //-------------------------------------------------------------------------------
    udp_t user_d; //客户端数据
    node_t *ptemp = NULL;
    node_t *pdel = NULL;

    pid_t pid;
    pid = fork();
    if (pid < 0)
    {
        ERRLOG("fork error");
    }
    else if (pid == 0) //子进程节收数据
    {
        while (1)
        {
            //阻塞接收客户端发来的数据                              //强制类型转换//发送端的网络信息结构体
            if (-1 == recvfrom(sockfd, &user_d, sizeof(udp_t), 0, (struct sockaddr *)&client_addr, &client_addr_len))
                ERRLOG("recvfrom error");
            if (user_d.OS == 1)
            {
                //在服务器显示
                printf("客户端 (%s:%d) >",
                       inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                printf("[%s]上线n", user_d.name);
                //遍历链表,将消息转发给除了自己之外的所有人
                ptemp = phead;
                while (ptemp->next != NULL)
                {
                    ptemp = ptemp->next;
                    if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0,
                                     (struct sockaddr *)&(ptemp->cli_addr), sizeof(ptemp->cli_addr)))
                        ERRLOG("sendto error");
                }
                //再将自己-使用头插法插入数据
                insert_into_list_by_head(phead, client_addr);
            }
            else if (user_d.OS == 2 || user_d.OS == 3)
            {
                if (user_d.OS == 2)
                {
                    printf("[%s]发来消息n", user_d.name);
                    ptemp = phead;
                    while (ptemp->next != NULL)
                    {
                        ptemp = ptemp->next;
                        if (memcmp(&(ptemp->cli_addr), &client_addr, sizeof(struct sockaddr_in)))
                        {
                            if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0,
                                             (struct sockaddr *)&(ptemp->cli_addr), client_addr_len))
                                ERRLOG("sendto error");
                        }
                    }
                }
                else if (user_d.OS == 3)
                {
                    printf("[%s]退出n", user_d.name);
                    ptemp = phead;
                    while (ptemp->next != NULL)
                    {
                        if (memcmp(&(ptemp->next->cli_addr), &client_addr, sizeof(struct sockaddr_in)))
                        {
                            //不是自己就转发
                            ptemp = ptemp->next; //只有不删除节点的情况下  ptemp才往后走
                            if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0,
                                             (struct sockaddr *)&(ptemp->cli_addr), client_addr_len))
                                ERRLOG("sendto error");
                        }
                        else
                        {
                            //是自己 就将自己在链表中删除
                            pdel = ptemp->next;
                            ptemp->next = pdel->next;
                            free(pdel);
                            pdel = NULL;
                        }
                    }
                }
            }
            else //父进程发来的系统消息
            {
                ptemp = phead;
                while (ptemp->next != NULL)
                {
                    ptemp = ptemp->next;
                    if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0,
                                 (struct sockaddr *)&(ptemp->cli_addr), sizeof(ptemp->cli_addr)))
                        ERRLOG("sendto error");
                }
            }
        }
    }
    else //父进程发送系统消息
    {
        while (1)
        {
            printf("input > ");
            memset(user_d.buff2, 0, sizeof(user_d.buff2));
            scanf("%s", user_d.buff2);
            user_d.OS = 0;
            //把消息发给子进程
            if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0,
                             (struct sockaddr *)&server_addr, server_addr_len))
                ERRLOG("sendto error");
        }
    }
    close(sockfd);
    return 0;
}

//创建节点的函数
int create_node(node_t **pnew, struct sockaddr_in cli_addr)
{
    *pnew = (node_t *)malloc(sizeof(node_t));
    if (NULL == *pnew)
    {
        ERRLOG("空间分配失败n");
    }
    (*pnew)->cli_addr = cli_addr;
    (*pnew)->next = NULL;
    return 0;
}
//使用头插法插入数据
int insert_into_list_by_head(node_t *phead, struct sockaddr_in cli_addr)
{
    if (NULL == phead)
    {
        ERRLOG("入参为NULL 请检查n");
    }
    //创建新节点
    node_t *pnew = NULL;
    create_node(&pnew, cli_addr);
    //将新节点头插到原链表里
    pnew->next = phead->next;
    phead->next = pnew;
    return 0;
}
客户端—02client.c
//----------------------------------------客户端---------------------------------
#include "./uDP.h"
int main(int argc, const char *argv[])
{
    if (3 != argc)
    {
        printf("Usage : %s  n", argv[0]);
        exit(-1);
    }

    // 1.创建套接字     // IPV4使用,//UDP
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        ERRLOG("socket error");
    }

    // 2.填充服务器网络信息结构体
    struct sockaddr_in server_addr;
    //清空、填充0
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET; // IPV4
    //端口号  填 8888 9999 6789 ...都可以
    //将无符号2字节整型  主机-->网络
    server_addr.sin_port = htons(atoi(argv[2]));
    // ip地址 要么是当前Ubuntu主机的IP地址 或者
    //如果本地测试的化  使用  127.0.0.1 也可以
    //将strptr所指的字符串转换成32位的网络字节序二进制值。
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);

    //结构体长度
    socklen_t server_addr_len = sizeof(server_addr);

    //-------------------------------------------------------------------------------
    char buff[128] = {0};
    udp_t user_d; //客户端数据

SCANF_T:
    printf("input 操作码----------1登录 > ");
    scanf("%d", &user_d.OS);
    if (user_d.OS == 1)
    {
        printf("input your name> ");
        scanf("%s", user_d.name);
        strcpy(buff,user_d.name);
        //将数据发给服务器                                    //强制类型转换   //网络信息结构体
        if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0, (struct sockaddr *)&server_addr, server_addr_len))
            ERRLOG("sendto error");
    }
    else
    {
        goto SCANF_T;
    }

    pid_t pid;
    pid = fork();
    if (pid < 0)
    {
        ERRLOG("fork error");
    }
    else if (pid == 0) //子进程节收数据
    {
        while (1)
        {
            //接收服务器的应答
            //客户端就无须再保存服务器的网络信息结构体了
            //因为server_addr 没有改变
            if (-1 == recvfrom(sockfd, &user_d, sizeof(udp_t), 0, NULL, NULL))
                ERRLOG("recvfrom error");    
            if (user_d.OS == 0)
            {
                printf("系统消息  > [%s]n", user_d.buff2);
            }
            if (user_d.OS == 1)
            {
                printf("[%s]上线了n", user_d.name);
            }
            if (user_d.OS == 2)
            {
                printf("%s >  %sn", user_d.name, user_d.buff);
            }
            if (user_d.OS == 3)
            {
                printf("[%s]下线了n", user_d.name);
            }
        }
        exit(EXIT_SUCCESS); //退出进程的时候会刷新缓冲区
    }
    else//父进程节发数据
    {
    SCANF_T2:
        printf("input 操作码--2群聊---3退出 > ");
        scanf("%d", &user_d.OS);
        if (user_d.OS == 3)
        {
            //将数据发给服务器                      //强制类型转换   //网络信息结构体
            if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0, (struct sockaddr *)&server_addr, server_addr_len))
                ERRLOG("sendto error");
        }
        else if (user_d.OS == 2)
        {
            while (1)
            {
                scanf("%s", user_d.buff);
                if (strcmp(user_d.buff, "quit") == 0)
                {
                    user_d.OS = 3;
                }
                //将数据发给服务器                      //强制类型转换   //网络信息结构体
                if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0, (struct sockaddr *)&server_addr, server_addr_len))
                    ERRLOG("sendto error");

                if (user_d.OS == 3)
                {
                    exit(-1);
                }
            }
        }
        else
        {
            goto SCANF_T2;
        }
        
        wait(NULL); //阻塞等待回收子进程的资源
    }

    close(sockfd);

    return 0;
}
头文件—uDP.h
//-----------------基于UDP的网络群聊聊天室--------------------------
#ifndef __uDP_H__
#define __uDP_H__

#include 

#include 
#include 

#include 

#include 
#include 
#include 

#include 
#include 
#include 

#include 

#include 

#include 

#include 
#define ERRLOG(errmsg)                                       
	do                                                       
	{                                                        
		printf("%s--%s(%d):", __FILE__, __func__, __LINE__); 
		perror(errmsg);                                      
		exit(-1);                                            
	} while (0)

typedef struct 
{
    int OS;//操作码-1登录///2群聊---3退出
    char name[20];//用户名
    char buff[1024];//客户端数据
	char buff2[1024];//服务器数据
}udp_t;

typedef struct {
    struct sockaddr_in cli_addr;//数据域  保存客户端信息的结构体
	socklen_t len; 
}A_t;
//链表的节点
typedef struct __NODE{
    struct sockaddr_in cli_addr;//数据域  保存客户端信息的结构体
    struct __NODE *next;//下一节点的地址
}node_t;

#endif
执行结果

注意
  1. 链表只需插入第一次连接的(新登录的用户)网路信息结构体插入链表
//阻塞接收客户端发来的数据                            
if (-1 == recvfrom(sockfd, &user_d, sizeof(udp_t), 0,
			(struct sockaddr *)&client_addr,&client_addr_len))
	ERRLOG("recvfrom error");
if (user_d.OS == 1)
{
//在服务器显示
	printf("客户端 (%s:%d) >",
	inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
	printf("[%s]上线n", user_d.name);
	//遍历链表,将消息转发给除了自己之外的所有人
	ptemp = phead;
	while (ptemp->next != NULL)
	{
	ptemp = ptemp->next;
	if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0,
(struct sockaddr *)&(ptemp->cli_addr),sizeof(ptemp->cli_addr)))
	ERRLOG("sendto error");
	}
	//再将自己-使用头插法插入数据
	insert_into_list_by_head(phead, client_addr);
}
  1. 群聊时,将消息转发给除了自己之外的所有人
if (user_d.OS == 2)
{
    printf("[%s]发来消息n", user_d.name);
    ptemp = phead;
    while (ptemp->next != NULL)
    {
        ptemp = ptemp->next;
        //判断不是自己再转发,是自己就不用发了
        if (memcmp(&(ptemp->cli_addr), &client_addr,
        						 sizeof(struct sockaddr_in)))
        {
           if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0,
     (struct sockaddr *)&(ptemp->cli_addr), client_addr_len))
                ERRLOG("sendto error");
        }
    }
}
  1. 退出时,判断ptemp的下一节点,若将自己从链表中删除则不需往后走
ptemp = phead;
while (ptemp->next != NULL)
{
	//此时 判断ptemp的下一节点
    if (memcmp(&(ptemp->next->cli_addr), &client_addr, 
    							  sizeof(struct sockaddr_in)))
    {//不是自己就转发
    	//只有不删除节点的情况下  ptemp才往后走
        ptemp = ptemp->next; 
        if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0,
      (struct sockaddr *)&(ptemp->cli_addr), client_addr_len))
            ERRLOG("sendto error");
    }
    else
    {
        //是自己 就将自己在链表中删除
        pdel = ptemp->next;
        ptemp->next = pdel->next;
        free(pdel);
        pdel = NULL;
    }
}
  1. 为实现父进程发送系统消息,将消息发给子进程
	printf("input > ");
	memset(user_d.buff2, 0, sizeof(user_d.buff2));
	scanf("%s", user_d.buff2);
	user_d.OS = 0;
	//把消息发给子进程
	if (-1 == sendto(sockfd, &user_d, sizeof(udp_t), 0,
			(struct sockaddr *)&server_addr, server_addr_len))
    ERRLOG("sendto error");
精简如下

UDP的网络群聊聊天室精简

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/893630.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号