【Linux】基于UDP协议的“聊天室”

目录

预备知识

基本思路

服务端设计

重要接口详解

服务端核心代码

服务端运行代码

客户端设计


预备知识

UDP协议(User Datagram Protocal用户数据报协议)

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

基本思路

如下是我们设计的一个简单的“聊天室”的大致框架图:

        “聊天室”分为两个角色,一个是客户端,即参与聊天的用户,另一个是提供服务的服务端,负责接收来自客户端,对接收到的信息加工处理,显示发送方的ip和端口号,再转发给已经加入服务端所创建的用户列表中的所有用户(即已经在该聊天室的用户)。

服务端设计

重要接口详解

服务端设计只要有以下几个步骤:

//第一步   创建套接字socket

sockfd=socket (int domain, int type, int protocol) 

        1.domain指明使用的协议族,常用有AF_INET 、AF_INET6、AF_UNIX 、AF_ROUTE

        2. type指明socket类型 有三种:SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、  SOCK_RAW(原始类型,允许对底层协议如IP或ICMP进行直接访问,不太常用)

        3.protocol  通常赋值为0;

        --成功返回非负值的socket描述符,失败返回-1

//第二步   将创建的socket绑定到指定的IP地址和端口上

bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen)

        --成功返回0,失败返回1

PS:

        1.uint16_t需要头文件 #include <unistd.h>

        2.sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义

        3.bzero函数头文件是string.h(C语言)或 cstring(C++)

        4.void bzero(void *s, size_t n);

        5.bzero函数将指定内存块的前n个字节设置为0。

        6.服务器提供服务的端口一般选择大于1023,因为【0,1023】是系统内定的端口号

服务端核心代码

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unordered_map>
#include "Log.hpp"
Log lg;

enum{
    
    SOCKET_ERR=1,
    BIND_ERR
};
uint16_t defaultport=8080;
std::string defaultip="0.0.0.0";
const int size=1024;
class UdpServer
{
public:
   UdpServer(const uint16_t& port=defaultport,const std::string&ip=defaultip)
    :_sockfd(0),
    _port(port),
    _ip(ip)
   {} 
    //初始化
    void Init()
    {
        //1.创建 udp socket
        //udp的socket是全双工的,允许被同时读写
        //AF_INET表示使用IPv4地址族  SOCK_DGRAM表示创建一个数据报套接字,0表示以阻塞的方式
        _sockfd=socket(AF_INET,SOCK_DGRAM,0);
        //创建套接字失败
        if(_sockfd<0)
        {
            lg(Fatal,"socket create error,sockfd:%d",_sockfd);
            exit(SOCKET_ERR);
        }
        //创建套接字成功
        lg(Info,"socket create success,sockfd:%d",_sockfd);
        //2.bind socket
        struct sockaddr_in local;
        bzero(&local,sizeof(local));

        local.sin_family=AF_INET;
        local.sin_port=htons(_port);//需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的
        local.sin_addr.s_addr=inet_addr(_ip.c_str()); //string->uint32_t必须是网络序列


        if(bind(_sockfd,(const struct sockaddr*)&local,sizeof(local))<0)
        {
            //绑定失败
            lg(Fatal,"bind error,errno:%d,err string:%s",errno,strerror(errno));
            exit(BIND_ERR);
        }

        //绑定成功
        lg(Info,"bind success,errno:%d,err string:%s",errno,strerror(errno));

    }
    void CheckUser(const struct sockaddr_in& client,const std::string clientip,uint16_t clientport)
    {
        auto iter=_online_user.find(clientip);
        if(iter==_online_user.end())
        {
            _online_user.insert({clientip,client});
            std::cout<<"["<<clientip<<":"<<clientport<<"] add to online user."<<std::endl;
        }
    }
    //对存在用户列表的所有用户进行转发
    void Broadcast(const std::string&info,const std::string clientip,uint16_t clientport)
    {
        for(const auto& user:_online_user)
        {
            std::string message="[";
            message += clientip;
            message += ":";
            message += std::to_string(clientport);
            message += "]# ";
            message += info;
            socklen_t len = sizeof(user.second);
            sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)(&user.second),len);

        }
    }
    //开始运行
    void Run(){
        isrunning = true;
        char inbuffer[size];
        while(isrunning)
        {
            struct sockaddr_in client;
            socklen_t len=sizeof(client);
            //接收客户端
            ssize_t n=recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&client,&len);
            if(n<0)
            {
                //未收到
                lg(Warning,"recvfrom error,errno:%d,err string:%s",errno,strerror(errno));
                continue;
            }
            uint16_t clientport=ntohs(client.sin_port);
            std::string clientip=inet_ntoa(client.sin_addr);//网络字节序列转换 string
            //检查该用户是否已在聊天室
            CheckUser(client,clientip,clientport);
            std::string info=inbuffer;
            //向其他成员转发
            Broadcast(info,clientip,clientport);

        }
    }
private:
    int _sockfd;
    uint16_t _port;
    std::string _ip;
    bool isrunning; //服务器是否开始运行
    std::unordered_map<std::string,struct sockaddr_in> _online_user; //用户列表
};

服务端运行代码

#include "UdpServer.hpp"
#include <memory>
#include <cstdio>
#include <vector>
void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}
// ./udpserver port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));

    svr->Init(/**/);
    svr->Run();

    return 0;
}

客户端设计

客户端也是需要绑定端口的,但是不需要用户显示绑定,一般由os自由随机选择,在首次发消息的时候绑定。不同于服务端的是:服务端端口号必须是唯一确定的,客户端可变。

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Terminal.hpp"
using namespace std;

struct ThreadDate
{
    struct sockaddr_in server;
    int sockfd;
    std::string serverip;
};

void Usage(std::string proc)
{
    std::cout<<"\n\rUsage:"<<proc<<"serverip serverport\n"<<std::endl;
}
//收信息
void* recv_message(void* args)
{
    OpenTerminal();
    ThreadDate* td = static_cast<ThreadDate*>(args);
    char buffer[1024];
    while(true)
    {
        memset(buffer,0,sizeof(buffer));
        struct sockaddr_in tmp;
        socklen_t len=sizeof(tmp);

        ssize_t n=recvfrom(td->sockfd,buffer,1023,0,(struct sockaddr*)&tmp,&len);
        if(n>0)
        {
            buffer[n]='\0';
            cerr<<buffer<<endl;
        }
    }
}
//发信息
void* send_message(void* args)
{
    ThreadDate* td=static_cast<ThreadDate*>(args);
    string message;
    socklen_t len=sizeof(td->server);

    string welcome = td->serverip;
    welcome += "coming...";
    sendto(td->sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&(td->server),len);
    while(true)
    {
        cout<<"Please Enter@ ";
        getline(cin,message);
        
        sendto(td->sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&(td->server),len);
    }
}
//多线程
//./udpclient serverip serverporta
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip=argv[1];
    uint16_t serverport=std::stoi(argv[2]);

    struct ThreadDate td;
    bzero(&td.server,sizeof(td.server));

    td.server.sin_family=AF_INET;
    td.server.sin_port=htons(serverport);
    td.server.sin_addr.s_addr=inet_addr(serverip.c_str());

    td.sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(td.sockfd<0)
    {
        cout<<"scoket error"<<endl;
        return 1;
    }

    td.serverip=serverip;

    pthread_t recver,sender;
    pthread_create(&recver,nullptr,recv_message,&td);
    pthread_create(&sender,nullptr,send_message,&td);

    pthread_join(recver,nullptr);
    pthread_join(sender,nullptr);
    close(td.sockfd);
    return 0;
}

上述客户端为了用户交互友好,我们打开两个终端模拟,一个终端负责发信息,一个终端负责收信息显示,我们重定向客户端收到消息后,往第二个终端打印。

ls  -l  /dev/pts   //查看我们有哪些终端文件,显示它们的详细信息 

例如:

重定向输出信息

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//原来的终端
std::string terminal = "/dev/pts/2";

int OpenTerminal()
{
    int fd = open(terminal.c_str(), O_WRONLY);
    if(fd < 0)
    {
        std::cerr << "open terminal error" << std::endl;
        return 1;
    }
    修改到要显示的终端
    dup2(fd, 0);

    // printf("hello world\n");
    // close(fd);
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Arduino UNO硬件平台制作智能小车

目录 概述 1. 硬件组成 1.1 电机驱动模块 1.2 控制板 1.3 遥控器模块 2 机械结构 2.1 底盘介绍 2.2 转向功能实现 3 软件实现 4 运行测试 4.1 红外解码测试 4.2 电机运行测试 概述 本文主要介绍使用整体结构小车底盘&#xff0c;外加Arduion控制板和LN298N控制板…

【Linux】文件的软硬链接

文章目录 一、文件和目录的一些命令ls 命令stat 命令 二、链接的概念三、软链接&#xff08;symbolic link&#xff09;创建和删除软链接的示例软链接的特性软链接的应用使用 find 查找链接文件 四、硬链接&#xff08;hard link&#xff09;创建和删除硬链接的示例硬链接的特性…

Git简单了解

文章目录 1、Git概述2、Git下载与安装3、Git代码托管服务3.1、使用码云托管服务 1、Git概述 什么是Git Git是一个分布式版本控制工具&#xff0c;主要用于管理开发过程中的源代码文件&#xff08;Java类、xml文件、html页面等&#xff09;&#xff0c;在软件开发过程中被广泛使…

2/7 算法每日N题(二分+双指针)

第一题&#xff1a; class Solution { public:int search(vector<int>& nums, int target) {int left 0, right nums.size() - 1;while(left < right){int mid (right - left) / 2 left;int num nums[mid];if (num target) {return mid;} else if (num >…

STM32TIM定时器(1)

文章目录 前言一、介绍部分TIM简介了解定时器类型基本定时器框图通用定时器框图高级定时器框图定时器级联关系 所需简化定时器中断流程图时序部分预分频器时序计数器时序无影子寄存器计数器时序有影子寄存器计数器时序 时钟树 二、实例部分使用定时器计数使用对射红外传感器来控…

[计算机提升] 还原系统:系统映像

6.4 还原系统&#xff1a;系统映像 1、打开系统设置&#xff0c;进入到恢复页面&#xff0c;然后点击高级启动中的立即重新启动进入到高级启动页面。 2、点击疑难解答 3、点击高级选项 4、点选查看更多恢复选项到下一步系统映像修复&#xff1a; 5、点选系统映像恢复 …

.NET Core 实现 JWT 认证

写在前面 JWT&#xff08;JSON Web Token&#xff09;是一种开放标准, 由三部分组成&#xff0c;分别是Header、Payload和Signature&#xff0c;它以 JSON 对象的方式在各方之间安全地传输信息。通俗的说&#xff0c;就是通过数字签名算法生产一个字符串&#xff0c;然后在网络…

实例分割论文阅读之:《Mask Transfiner for High-Quality Instance Segmentation》

1.摘要 两阶段和基于查询的实例分割方法取得了显著的效果。然而&#xff0c;它们的分段掩模仍然非常粗糙。在本文中&#xff0c;我们提出了一种高质量和高效的实例分割Mask Transfiner。我们的Mask Transfiner不是在规则的密集张量上操作&#xff0c;而是将图像区域分解并表示…

Pymysql之Cursor常用API

Cursor常用API 1、cursor.execute(query, argsNone)&#xff1a;执行sql语句。 参数: query (str)&#xff1a;sql语句。 args (tuple, list or dict)&#xff1a;sql语句中如果有变量&#xff0c;或者格式化输出&#xff0c;会在这里填充数据。 Returns&#xff1a;返…

springboot项目启动报错:dynamic-datasource can not find primary datasource

项目启动报错信息 Caused by: com.baomidou.dynamic.datasource.exception.CannotFindDataSourceException: dynamic-datasource can not find primary datasourceat com.baomidou.dynamic.datasource.DynamicRoutingDataSource.determinePrimaryDataSource(DynamicRoutingDat…

七、Nacos源码系列:Nacos服务发现

目录 一、服务发现 二、getServices()&#xff1a;获取服务列表 2.1、获取服务列表 2.2、总结图 三、getInstances(serviceId)&#xff1a;获取服务实例列表 3.1、从缓存中获取服务信息 3.2、缓存为空&#xff0c;执行订阅服务 3.2.1、调度更新&#xff0c;往线程池中…

DC-8靶机渗透详细流程

信息收集&#xff1a; 1.存活扫描&#xff1a; arp-scan -I eth0 -l └─# arp-scan -I eth0 -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:dd:ee:6a, IPv4: 192.168.10.129 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.10…

uni使用openlayer加载本机离线地图

manifest.json添加配置 "runmode": "liberate"(默认为normal) 把地图打包进apk&#xff0c;这样手机每次访问地图就可以访问到工程文件夹的地图资源了&#xff0c;不用每次都请求云资源&#xff0c;消耗流量太大了

第9章 安全漏洞、威胁和对策(9.11-9.16)

9.11 专用设备 专用设备王国疆域辽阔&#xff0c;而且仍在不断扩张。 专用设备是指为某一特定目的而设计&#xff0c;供某一特定类型机构使用或执行某一特定功能的任何设备。 它们可被看作DCS、物联网、智能设备、端点设备或边缘计算系统的一个类型。 医疗设备、智能汽车、…

《MySQL 简易速速上手小册》第3章:性能优化策略(2024 最新版)

文章目录 3.1 查询优化技巧3.1.1 基础知识3.1.2 重点案例3.1.3 拓展案例 3.2 索引和查询性能3.2.1 基础知识3.2.2 重点案例3.2.3 拓展案例 3.3 优化数据库结构和存储引擎3.3.1 基础知识3.3.2 重点案例3.3.3 拓展案例 3.1 查询优化技巧 让我们来聊聊如何让你的 MySQL 查询跑得像…

【python】if __name__ == ‘__main__‘:

if __name__ __main__: 是一个Python脚本中使用的常见结构&#xff0c;用来判断该脚本文件是直接运行的还是被导入到其他文件中运行的。 当一个Python文件被运行时&#xff0c;Python解释器会自动创建一些特殊的变量&#xff0c;__name__就是其中之一。如果这个文件是作为主程…

米贸搜|Facebook在购物季使用的Meta广告投放流程

一、账户简化 当广告系列开始投放后&#xff0c;每个广告组都会经历一个初始的“机器学习阶段”。简化账户架构可以帮助AI系统更快获得广告主所需的成效。例如&#xff1a; 每周转化次数超过50次的广告组&#xff0c;其单次购物费用要低28%&#xff1b;成功结束机器学习阶段的…

Ondo宣布将其原生稳定币USDY带入Sui生态

重要提示&#xff1a;USDY是由短期美国国债支持的token化票据&#xff0c;持有者享受稳定币的实用性同时获得收益。USDY不得在美国或向美国人出售或以其他方式提供。USDY也未根据1933年美国证券法注册。 不到一年的时间&#xff0c;Sui已经成为全链TVL排名前十的区块链&#xf…

Netty源码 之 ByteBuf自适应扩缩容源码

Netty体系如何使得ByteBuf根据实际IO收发数据场景进行自适应扩容缩容的&#xff1f; IO收发数据的过程&#xff1a; read 读取&#xff08;"I"&#xff09;&#xff1a;网卡硬件通过网络传输介质读取对端传输过来的数据&#xff0c;网卡硬件再把数据写到recv-socke…

Flask 入门7:使用 Flask-Moment 本地化日期和时间

如果Web应用的用户来自世界各地&#xff0c;那么处理日期和时间可不是一个简单的任务。服务器需要统一时间单位&#xff0c;这和用户所在的地理位置无关&#xff0c;所以一般使用协调世界时&#xff08;UTC&#xff09;。不过用户看到 UTC 格式的时间会感到困惑&#xff0c;他们…
最新文章