udt聊天室

创建一个简单的udp聊天室

服务器代码思路:

  1. 初始化

    • 创建UDP套接字。
    • 配置服务器的IP和端口号,并绑定套接字到这个地址。
  2. 数据接收和处理

    • 使用循环接收客户端发来的消息。recvfrom()
    • 解析消息类型(如登录、发送、下线)和内容。
  3. 广播消息

    • 对于聊天消息,将其广播给所有连接的客户端。
    • 对于登录和下线消息,通知其他客户端有新用户上线或某用户下线。
  4. 客户端管理

    • 维护一个客户端列表或字典,用于跟踪在线客户端的地址和状态。
    • 更新客户端列表以反映登录和下线事件。
  5. 清理和关闭

    • 在服务器关闭时,适当清理资源并关闭套接字。

服务器代码:

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

typedef struct linklist
{ //创建存储客户端ip、端口号的链表
    struct sockaddr_in addr;
    struct linklist *next;
} l_node, *l_pnode;

typedef struct sockt_data
{                   //创建收发的信息结构体
    char name[64];  //名字
    char type[8];   //消息类别
    char text[128]; //信息内容
} data_t;

typedef struct Sock
{ //创建存储服务器发送数据的信息结构体
    struct sockaddr_in caddr;
    data_t cdata; //发送消息结构体
    int sockfd;   //套接字
} sock_t;

l_pnode create();                                              //创建链表节点
void login(int sockfd, data_t data, struct sockaddr_in saddr); //登录
void line(int sockfd, data_t data, struct sockaddr_in saddr);  //发送和下线
void *func(void *arg);                                         //子线程

l_pnode S; //开辟一个全局变量的链表

int main(int argc, char *argv[])
{
//  1、创建套接字 -- socket 
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    // 2、绑定IP地址和端口号 -- bind
    struct sockaddr_in saddr, caddr;

    caddr.sin_family = AF_INET;
    caddr.sin_port = htons(8888); //将主机字节序转换为网络字节序再赋值
    caddr.sin_addr.s_addr = htons(INADDR_ANY);

    int s_len = sizeof(saddr);
    bind(sockfd, (struct sockaddr *)&caddr, s_len);
    // 3、发送接送数据 
    data_t sdata = {0, 0, 0}; //定义接送数据结构体
    int d_len = sizeof(sdata);
    int c_len = sizeof(caddr); //计算服务器地址结构的大小
    memset(&saddr, 0, c_len);  //清空接收地址结构

    S = (l_pnode)malloc(sizeof(l_node)); //初始化链表
    S->next = NULL;

    sock_t csock;          //创建子线程所需结构体
    csock.sockfd = sockfd; //传递套接字

    pthread_t thread;
    pthread_create(&thread, NULL, func, &csock); //创建子线程

    while (1)
    {
        memset(&sdata, 0, d_len);                                              //清空接送到的数据
        recvfrom(sockfd, &sdata, d_len, 0, (struct sockaddr *)&saddr, &c_len); //接送来自客户端的消息
        printf("%s:%s\n", sdata.name, sdata.text);                             //在服务器中显示接送到的消息
        if (strcmp(sdata.type, "login") == 0)                                  //判断接受消息内型
            login(sockfd, sdata, saddr);                                       //上线操作
        else
            line(sockfd, sdata, saddr); //聊天、下线操作
    }
//   4、关闭套接字 
    close(sockfd);
    return 0;
}
//  子线程 
void *func(void *arg) //线程处理函数
{
    sock_t csock = *(sock_t *)arg;     //获取主函数中传递过来的内容
    strcpy(csock.cdata.type, "login"); //设置要发送的结构图
    strcpy(csock.cdata.name, "服务器");
    while (1)
    {
        memset(&csock.cdata.text, 0, 128);                     //清空输入进来的内容
        fgets(csock.cdata.text, 64, stdin);                    //获取输入进来的内容
        csock.cdata.text[strlen(csock.cdata.text) - 1] = '\0'; //删除获取进来的回车'\n'
        line(csock.sockfd, csock.cdata, csock.caddr);          //向所有在线的客户端发送入进来的内容
    }
}
//  上线 
void login(int sockfd, data_t data, struct sockaddr_in saddr) //登录
{
    int d_len = sizeof(data);
    int s_len = sizeof(saddr);
    l_pnode new = create(); //创建新节点
    new->addr = saddr;      //传递接送到的客户端IP、端口号
    new->next = NULL;
    l_pnode N = create();
    N = S;
    while (N->next) //尾插并发送给其他客户端
    {
        N = N->next;
        sendto(sockfd, &data, d_len, 0, (struct sockaddr *)&N->addr, s_len); //发送给除该客户信息的其他客户
    }
    N->next = new;
}
//  聊天、下线 
void line(int sockfd, data_t data, struct sockaddr_in saddr) //聊天、下线
{
    int d_len = sizeof(data);
    int s_len = sizeof(saddr);
    l_pnode N = create();
    N = S;
    while (N->next)
    {
        if ((N->next->addr.sin_addr.s_addr == saddr.sin_addr.s_addr) && (N->next->addr.sin_port == saddr.sin_port)) //判断是否存在该客户端数据
        {
            if (strcmp(data.type, "line") == 0) //下线,删除用户信息
            {
                l_pnode Q = N->next;
                N->next = Q->next; //连线
                free(Q);           //释放空间
                Q = NULL;
            }
            if (strcmp(data.type, "send_t") == 0) //聊天,跳过用户
                N = N->next;
        }
        if (N->next != NULL) //判断是否到尾节点
        {
            N = N->next;
            sendto(sockfd, &data, d_len, 0, (struct sockaddr *)&N->addr, s_len); //发送给除该客户信息的其他客户
        }
        else
            break;
    }
}
//  创建链表新节点
l_pnode create()
{
    l_pnode S = (l_pnode)malloc(sizeof(l_node));
    S->next = NULL;
    return S;
}

客户端代码思路:

  1. 初始化和创建套接字:在函数中,创建一个UDP套接字,并配置客户端的IP地址和端口。main

  2. 进程创建:使用创建一个子进程。子进程负责读取用户输入的用户名和消息,并处理登录、发送和下线操作。fork()

  3. 父进程接收消息:父进程不断接收来自服务器的消息,并在控制台显示。

  4. 登录、发送和下线功能

    • login():向服务器发送登录消息。
    • send_t():发送聊天消息。
    • line():发送下线消息并退出子进程。
  5. 信号处理:使用处理子进程结束的信号,确保子进程资源得到清理。signal(SIGCHLD, mysignal)

客户端代码:


#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
typedef struct sockt_data
{
    char name[64];//用户户名
    char type[8];//消息类别
    char text[128];//消息内容
} data_t;

void mysignal(int arg) //回收子进程
{
    wait(NULL);
    exit(0);
}

void login(int sockfd, data_t cdata, struct sockaddr_in caddr); //登录
void send_t(int sockfd, data_t data, struct sockaddr_in saddr); //发送信息
void line(int sockfd, data_t cdata, struct sockaddr_in caddr);  //下线

int main(int argc, char *argv[])
{
    if (argc != 2)
        printf("请正确输入: ./client IP号\n");
    //  1、创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    // 2、绑定IP地址和端口号 -- bind
    struct sockaddr_in saddr, caddr;
    caddr.sin_family = AF_INET;
    caddr.sin_port = htons(8888); //将主机字节序转换为网络字节序再赋值
    caddr.sin_addr.s_addr = inet_addr(argv[1]);

    int s_len = sizeof(saddr);
    //  3、发送接送数据
    data_t cdata = {0, 0, 0}, sdata = {0, 0, 0}; //定义收发数据结构体

    int d_len = sizeof(cdata); //计算数据结构体长度

    memset(&saddr, 0, s_len);  //清空接收地址结构
    memset(&cdata, 0, d_len);  //清空接收地址结构
    signal(SIGCHLD, mysignal); //捕获子进程释放信号

    pid_t pid = fork(); //创建线程
    if (0 == pid)       //子线程
    {
        printf("请输入你的用户名--->");
        fgets(cdata.name, 64, stdin);              //获取输入进来的名字
        cdata.name[strlen(cdata.name) - 1] = '\0'; //删除获取进来的回车'\n'
        login(sockfd, cdata, caddr);               //登录

        while (1)
        {
            fgets(cdata.text, 64, stdin);              //获取输入进来的内容
            cdata.text[strlen(cdata.text) - 1] = '\0'; //删除获取进来的回车'\n'
            if (strcmp(cdata.text, "line!!!") == 0)    //判断是否结束聊天
                line(sockfd, cdata, caddr);            //下线
            send_t(sockfd, cdata, caddr);              //聊天
        }
    }

    while (1)
    {
        memset(&sdata, 0, d_len); //清空接送到的数据
        recvfrom(sockfd, &sdata, d_len, 0, (struct sockaddr *)&saddr, &s_len);
        if (strcmp(sdata.type, "send_t") == 0) //在客户端中显示接送到的消息
            printf("%s:%s\n", sdata.name, sdata.text);
        else
            printf("%s-->%s\n", sdata.name, sdata.text);
    }
    //  4、关闭套接字
    close(sockfd);
    return 0;
}
// 登录
void login(int sockfd, data_t cdata, struct sockaddr_in caddr) //登录
{
    int d_len = sizeof(cdata);
    int s_len = sizeof(caddr);
    strcpy(cdata.type, "login");
    strcpy(cdata.text, "上线了");
    sendto(sockfd, &cdata, d_len, 0, (struct sockaddr *)&caddr, s_len);
}
//    下线
void line(int sockfd, data_t cdata, struct sockaddr_in caddr) //下线
{
    int d_len = sizeof(cdata);
    int s_len = sizeof(caddr);
    strcpy(cdata.type, "line");
    strcpy(cdata.text, "下线了");
    sendto(sockfd, &cdata, d_len, 0, (struct sockaddr *)&caddr, s_len);
    exit(0);
}
// 聊天
void send_t(int sockfd, data_t cdata, struct sockaddr_in caddr) //发送信息
{
    int ret = 0;
    int d_len = sizeof(cdata);
    int s_len = sizeof(caddr);
    strcpy(cdata.type, "send_t");
    ret = sendto(sockfd, &cdata, d_len, 0, (struct sockaddr *)&caddr, s_len);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/52584.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV结构分析与形状描述符(22)计算图像中某个轮廓或区域的矩函数moments()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算一个多边形或光栅化形状直到三阶的所有矩。 该函数计算一个向量形状或光栅化形状直到三阶的矩。结果返回在 cv::Moments 结构中。 函数原型…

基本输入与输出

引言 在前面的课程中&#xff0c;我们已经学习了 Python 中的基本数据类型、数据类型的运算和转换。本课时将聚焦于 Python 中的基本输入与输出功能&#xff0c;具体包括使用 input() 函数获取用户输入、使用 print() 函数输出信息以及格式化输出。通过这些功能&#xff0c;你可…

ATF UFS初始化笔记

1. JESD220 中关于UFS初始化的描述 原文 13.1.3 Initialization and boot code download process The initialization and boot code download process is made up of the following phases: partial initialization, boot transfer and initialization completion. 13.1.3.…

多线程——死锁

死锁 在Java中使用多线程&#xff0c;就会有可能导致死锁问题。死锁会让程序一直卡住&#xff0c;程序不再往下执行。 我们只能通过中止并重启的方式来让程序重新执行。 这是我们非常不愿意看到的一种现象&#xff0c;我们要尽可能避免死锁的情况发生&#xff01; 死锁的原因…

数据结构之栈和队列的应用

目录 一、栈的应用 1. 括号匹配 2. 计算后缀表达式 方法一&#xff08;栈&#xff09; 方法二&#xff08;数组模拟栈&#xff09; 二、队列应用 1. 二叉树层序遍历 方法一&#xff08;队列&#xff09; 三、总结 一、栈的应用 1. 括号匹配 给定一个只包括 (&#xf…

List<Map<String, Object>>汇总统计排序

开发环境&#xff1a;jdk 1.8 需求一&#xff1a; 1、统计每个小时(升序)不同事件的产品产量 2、统计不同事件&#xff08;OK 、NG&#xff09;的总产量 public static void main(String[] args) {//数据源List<Map<String, Object>> list new ArrayList<Map…

云计算实训48——k8s环境搭建(详细版)

1.创建主机、设置ip、设置hostname 2.设置免密登录 # 生成私钥 [rootk8s-master ~]# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): /root/.ssh/id_rsa already exists. Overwrite (y/n)? y Enter passphr…

77-java 装饰器模式和适配器模式区别

‌Java中的装饰器模式和适配器模式虽然都涉及到对象的组合和包装&#xff0c;但它们的应用场景和目的有所不同。‌ ‌装饰器模式的目的是在不修改原始对象的基础上&#xff0c;动态地添加功能或行为。‌它允许用户通过创建一个包含原始对象的包装类&#xff08;装饰器&#xff…

Computer Vision的学习路线

学习**Computer Vision&#xff08;计算机视觉&#xff09;**的过程中&#xff0c;可以按照以下步骤循序渐进地掌握基础知识、算法和实际应用。这个学习路线将涵盖从基础理论到前沿技术的各个层面。 1. 数学与基础知识 1.1 线性代数 计算机视觉中的图像处理和模型训练都依赖…

Uni-app 开发微信小程序

随着移动互联网的发展&#xff0c;微信小程序已经成为一种流行的应用开发模式。Uni-app 作为一种跨平台的开发框架&#xff0c;使用 Vue.js 语法&#xff0c;能够方便快速地开发出 微信小程序、H5、App 等多端应用。本指南将引导您从环境配置到实战案例开发&#xff0c;帮助您快…

vue3 使用swiper制作带缩略图的轮播图

效果图 实现代码 <template><div class"wrap"><!-- 主轮播图 --><swiper :style"{--swiper-navigation-color: #fff,--swiper-pagination-color: #fff,}" :modules"modules" :navigation"true" :thumbs"{ …

计算机网络 第2章 物理层

文章目录 通信基础基本概念信道的极限容量编码与调制常用的编码方法常用的调制方法 传输介质双绞线同轴电缆光纤以太网对有限传输介质的命名规则无线传输介质物理层接口的特性 物理层设备中继器集线器一些特性 物理层任务&#xff1a;实现相邻节点之间比特&#xff08;0或1&…

GORM高级查询

在日常开发中&#xff0c;我们经常需要执行复杂的数据库查询以满足各种业务需求。GORM作为Go语言中一个流行的ORM库&#xff0c;提供了许多高级查询功能&#xff0c;可以帮助我们高效地处理这些复杂场景。本文将详细介绍GORM的高级查询功能&#xff0c;包括智能选择字段、锁、子…

pptpd配置文件/etc/pptpd.conf详解

正文共&#xff1a;1111 字 2 图&#xff0c;预估阅读时间&#xff1a;1 分钟 如果要在Linux系统配置PPTP&#xff08;Point-to-Point Tunneling Protocol&#xff0c;点到点隧道协议&#xff09;VPN&#xff0c;一般是使用pptpd软件。pptpd命令通常从配置文件/etc/pptpd.conf中…

单片机拍照_将采集的RGB图像封装为BMP格式保存到SD卡

文章目录 一、前言二、BMP文件结构2.1 BMP图片的格式说明 2.2 RGB888与RGB565格式是什么&#xff1f;&#xff08;1&#xff09;RGB565&#xff08;2&#xff09;RGB888&#xff08;3&#xff09;区别&#xff08;4&#xff09;如何构成&#xff08;5&#xff09;示例 三、实现…

【Leetcode56】合并区间(数组 | 排序)

文章目录 一、题目二、思路三、代码 一、题目 二、思路 先将所有子列表按照start_pos进行排序&#xff0c;有利于保持顺序性&#xff0c;每次处理新子列表时&#xff0c;只用和结果列表ans_lst的最后一个子列表对比&#xff0c;如果有重合则合并&#xff0c;然后将合并的新子列…

Java 入门指南:Java 并发编程 —— 同步工具类 Phaser(相位器)

文章目录 同步工具类Phaser主要特点核心方法使用步骤适用场景使用示例 同步工具类 JUC&#xff08;Java.util.concurrent&#xff09;是 Java 提供的用于并发编程的工具类库&#xff0c;其中包含了一些通信工具类&#xff0c;用于在多个线程之间进行协调和通信&#xff0c;特别…

创新实验报告VC++案例开发十二生肖的俄罗斯方块智力游戏完整代码设计方案

一&#xff0e;项目名称 十二生肖俄罗斯方块 二&#xff0e;开发背景&#xff1a; 俄罗斯方块是一个很低古老的一个小游戏&#xff0c;到但今日它还有很大的魅力。 三&#xff0e;技术路线或工作原理&#xff1a; 采用的软件及开发平台 Micosoft Visual 6.0 项目的总体方…

Kotlin 极简小抄 P1(变量与常量、基本数据类型、流程控制)

一、Kotlin Kotlin 由 JetBrains 开发&#xff0c;是一种在 JVM&#xff08;Java 虚拟机&#xff09;上运行的静态类型编程语言 Kotlin 旨在提高开发者的编码效率和安全性&#xff0c;同时保持与 Java 的高度互操作性 Kotlin 是 Android 应用开发的首选语言&#xff0c;也可以…

uniapp 原生插件开发 UI

前言&#xff1a; 在集成某些特定 原生SDK的时候&#xff0c;它本身是带UI控件的。当我们使用 uniapp 开发app的时候实是 可以使使用 nvue 页面&#xff0c;以 weex 的方式嵌入原生的UI控件。 我这边的场景是 接入连连app的支付&#xff0c;它有个自己的密码键盘 控件是原生的页…