jjzjj

网络编程 : 基于UDP的网络群聊聊天室

码猫Mrr 2023-11-06 原文

一、UDP网络编程:

1.1 流程

服务器流程:

创建用户数据报套接字

填充服务器的网络信息结构体

绑定套接字与服务器网络信息结构体

收发数据

关闭套接字

客户端流程:

创建用户数据报套接字

填充服务器的网络信息结构体

收发数据

关闭套接字

二、基于UDP的网络群聊聊天室                                           

 2.1 功能:

有新用户登录,其他在线的用户可以收到登录信息

有用户群聊,其他在线的用户可以收到群聊信息

有用户退出,其他在线的用户可以收到退出信息

服务器可以发送系统信息

提示:

客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程或者多线程

服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程或者多线程

服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存

数据传输的时候要定义结构体,结构体中包含操作码、用户名以及数据

2.2  流程图

 

 2.3 代码实现

 头文件:dup.h

#ifndef __UDP_H__
#define __UDP_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<arpa/inet.h>
#include<signal.h>
#include<wait.h>

#define N 128
#define M 32

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

typedef struct _Node{
    struct sockaddr_in addr; 
    struct _Node *next;
}node_t;

typedef struct _Msg{
    char code;
    char user[M];
    char text[N];
}msg_t;
#endif

服务器:server.c

#include "udp.h"

//创建节点的函数
int create_node(node_t **phead){
    *phead = (node_t *)malloc(sizeof(node_t));
    if(NULL == *phead){
        printf("内存分配失败\n");
        exit(-1);
    }
    (*phead)->next=NULL;
    return 0;
}
//尾插法
int insert_data_by_tail(node_t *phead,struct sockaddr_in addr){
     if(NULL == phead){
        printf("入参为NULL,请检查\n");
        return -1;
    }
    //将新客户端使用尾插法插入链表中
    node_t *pnew = NULL;
    create_node(&pnew);
    pnew->addr = addr;  
    node_t *ptemp =phead;
    while(ptemp->next != NULL){
        ptemp = ptemp->next;
    }
    //让尾结点的指针域指向新节点
    ptemp->next = pnew;
    return 0;
}

int main(int argc,const char *argv[]){    
    if(3 != argc){
        printf("Uage:%s <IP><port>\n",argv[0]);
        return -1;
    }
    int sockfd = 0;
    if(-1==(sockfd=socket(AF_INET,SOCK_DGRAM,0))){
        ERRLOG("socket error");
    }
    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t serveraddr_len = sizeof(serveraddr);

    if(-1 == bind(sockfd,(struct sockaddr *)&serveraddr,serveraddr_len)){
        ERRLOG("bind error");
    } 

    struct sockaddr_in clientaddr,temp_clientaddr;
    memset(&clientaddr,0,sizeof(clientaddr));
    socklen_t clientaddr_len = sizeof(clientaddr);
 
    char name[32] = {0};
    pid_t pid = 0;
    msg_t msg;
    msg_t msg_send;
    //创建头结点
    node_t *phead;
    create_node(&phead);
    phead->addr = clientaddr;
 
    if(-1 == (pid = fork())){
        ERRLOG("fork error");
    }else if(0 == pid){   //子进程 接收数据 (1、d 登录操作 2、q 群聊操作 3、t 退出操作)  
        while(1){
            memset(&msg,0,sizeof(msg));
            if(-1 == recvfrom(sockfd, (void*)&msg, sizeof(msg),0, (struct sockaddr *)&clientaddr,&clientaddr_len)){
                perror("recv error");
            }    
        switch(msg.code){
            // 1、d 登录操作 2、q 群聊操作 3、t 退出操作 
            case 'd':
                printf("[%s]该玩家已上线\n", msg.user);              
                insert_data_by_tail(phead,clientaddr);
                node_t *q=phead->next;                
                while(q != NULL){
                    msg.code='d';
                    if(-1 == sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&q->addr,sizeof(q->addr))){
                        ERRLOG("send error");
                    }
                    q=q->next;
                }
                break; 
            case 'q':                     
                if(strcmp("管理员",msg.user)!=0){
                printf("[%s]:%s\n",msg.user, msg.text);
                }
                node_t *p = phead->next;                
                while(p != NULL){
                    msg.code='q';
                    if(-1 == sendto(sockfd,(void *)&msg,sizeof(msg),0,(struct sockaddr *)&p->addr,sizeof(p->addr))){
                        ERRLOG("send error");
                    }
                    p=p->next;
                }                
                break; 
            case 't':    
                printf("[%s]:退出了...\n", msg.user);
                node_t *t = phead; 
                node_t *pdel = NULL;               
                while(t->next != NULL){
                    msg.code='t';
                    if( 0 == memcmp(&(t->next->addr), &clientaddr,sizeof(clientaddr))){
                        pdel = t->next;
                         t->next = pdel->next;
                        free(pdel);
                    }else{
                        t = t->next;
                        if(-1 == sendto(sockfd, &msg,sizeof(msg),0,(struct sockaddr *)&t->addr,sizeof(t->addr))){
                            ERRLOG("send error");
                        }
                    }    
                }     
                break;
            }
        }
    }else if(0 < pid){
        //父进程 发送系统消息
        while(1){  
            strcpy(msg_send.user,"管理员");
            memset(msg_send.text,0,N);
            fgets(msg_send.text,N,stdin);
            msg_send.text[strlen(msg_send.text)-1] = '\0';
            msg_send.code = 'q';              
            if(-1 == sendto(sockfd,&msg_send,sizeof(msg_send),0,(struct sockaddr *)&serveraddr,serveraddr_len)){
                ERRLOG("send error");                
            }               
        }
    } 
    kill(pid, SIGKILL);
    wait(NULL);//给子进程回收资源
    exit(0);     
    close(sockfd);
    return 0;
}

 客户端:client.c

#include "udp.h"

int main(int argc, const char *argv[])
{
    if (3 != argc){
        printf("Usage: %s <IP> <port>\n", argv[0]);
        return -1;
    }

    //创建用户数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd){
        ERRLOG("socket error");
    }

    //填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    //网络字节序的端口号 8888  9999  6789 等 都可以
    serveraddr.sin_port = htons(atoi(argv[2]));
    //网络字节序的IP地址,IP地址不能乱填
    //自己的主机ifconfig 查到的ip地址是多少就填多少
    //如果本机测试使用 也可以填写 127.0.0.1
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t serveraddr_len = sizeof(serveraddr);

    int nbytes = 0;
    char name[32]={0};
    msg_t msg;
    pid_t pid;

    struct sockaddr_in clientaddr;
    memset(&clientaddr,0,sizeof(clientaddr));
    socklen_t clientaddr_len = sizeof(clientaddr);

    //输入用户名,完成登陆操作
    printf("请输入登录信息:");
    msg.code = 'd';
    memset(msg.user, 0, M);
    fgets(name, M, stdin);//在终端获取用户名
    strcpy(msg.user,name);
    msg.user[strlen(msg.user) - 1] = '\0'; //清空结尾的 '\n' 
    if (-1 == sendto(sockfd,&msg,sizeof(msg),0, (struct sockaddr *)&serveraddr,serveraddr_len)){  //给服务器发送用户名
        ERRLOG("send error");
    }

    //创建进程
    if(-1 == (pid = fork())){
        ERRLOG("fork error");
    }else if(0 == pid){   
        //子进程 接收数据  
        while (1){
            memset(&msg,0,sizeof(msg));
            //接收服务器的应答
            if (-1 == (nbytes=recvfrom(sockfd, &msg, sizeof(msg), 0,(struct sockaddr *)&serveraddr,&serveraddr_len))){
                ERRLOG("recv error");
            }  
            // printf("current ------->%d\n",strcmp(msg.user,name)); 
                       
            if(strcmp(msg.user,name) == -10){               
                continue;
            }else{
                //打印应答信息
                switch(msg.code){
                    case 'd':
                        printf("[%s]登录上线了....\n", msg.user); 
                        break; 
                    case 'q':
                        printf("[%s]:%s\n",msg.user,msg.text);
                        break;
                    case 't':            
                        printf("[%s]退出了....\n", msg.user); 
                    break;
                }  
            } 
        }    
        }else if(0 < pid){
            //父进程 发送数据(2、q:群聊操作  3、t:退出操作)
            while(1){
                //在终端获取群聊
                memset(msg.text, 0, N);
                fgets(msg.text, N, stdin);
                msg.text[strlen(msg.text) - 1] = '\0'; //清空结尾的 '\n'               
                if( 0 ==strcmp(msg.text, "quit")){
                    msg.code = 't'; 
                    if (-1 == sendto(sockfd, &msg, sizeof(msg), 0,(struct sockaddr *)&serveraddr,serveraddr_len)){
                        ERRLOG("send error");     
                    }   
                    break;
                }else{
                    msg.code = 'q';    
                }
                //给服务器发送群聊消息
                if (-1 == sendto(sockfd, &msg, sizeof(msg), 0,(struct sockaddr *)&serveraddr,serveraddr_len)){
                        ERRLOG("send error");
                }
            }     
        }      
    //关闭套接字
    close(sockfd);
    return 0;
}

2.4 实现结果 

有关网络编程 : 基于UDP的网络群聊聊天室的更多相关文章

  1. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  2. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  3. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  4. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  5. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  6. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  7. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  8. ruby - 如何以编程方式删除实例上的 "singleton information"以使其编码(marshal)? - 2

    我创建了一个由于“在运行时执行的单例元类定义”而无法编码的对象(这段代码的描述是否正确?)。这是通过以下代码执行的:#defineclassXthatmyusesingletonclassmetaprogrammingfeatures#throughcallofmethod:break_marshalling!classXdefbreak_marshalling!meta_class=class我该怎么做才能使对象编码正确?是否可以从对象instance_of_x的classX中“移除”单例组件?我真的需要一个建议,因为我们的一些对象需要通过Marshal.dump序列化机制进行缓存。

  9. Ruby 元编程问题 - 2

    我正在查看Ruby日志记录库Logging.logger方法并从sourceatgithub提出问题与这段代码有关:logger=::Logging::Logger.new(name)logger.add_appendersappenderlogger.additive=falseclass我知道类 最佳答案 这实际上删除了方法(当它实际被执行时)。这是确保close不会被调用两次的保障措施。看起来好像有嵌套的“class 关于Ruby元编程问题,我们在StackOverflow上找到一

  10. ruby - Paperclip:以编程方式分配图像并设置其名称 - 2

    使用Paperclip,我想从这样的URL抓取图像:require'open-uri'user.photo=open(url)问题是我最后得到一个像“open-uri20110915-4852-1o7k5uw”这样的文件名。有什么方法可以更改user.photo上的文件名?作为一个额外的变化,Paperclip将我的文件存储在S3上,所以如果我可以在初始分配中设置我想要的文件名就更好了,这样图像就会上传到正确的S3key。像这样:user.photo=open(url),:filename=>URI.parse(url).path 最佳答案

随机推荐