UDP服务器

news2025/1/15 16:45:40

目录

一,介绍

二,日志

三,服务端

1,服务器参数

2,创建套接字

3,绑定

3.1 填充套接字信息

3.2 绑定

 4,启动服务器

4.1接收客户端请求

4.2记录用户信息

4.3 消息转发

 4.4 main函数代码

4.5服务器整体代码

 四,客户端

1,输入记录服务端的ip和端口号

2,创建套接字

3,填充服务端套接字信息

4,开始通信

5,通信代码

6,整体代码

五,视频展示


一,介绍

     准备写一个基于udp协议的最简单的服务器,实现的功能目前的话就是一个群聊。也就是说所有连接服务器的人说的话都会转发给连接服务器的每一个人。后续学习完成之后还会加入新的东西,谢谢大家。

二,日志

      程序的日志其实还是挺重要的,日志写的好,写的全面一点可以更好的帮助我们识别出程序的错误,再针对性的去修改。我们我们单独实现一个日志类来搞一下,当然能也是可以直接cout打印的,但是日志吗肯定有一线重复的东西,搞一个也方便,不用一直解重复的东西。

这里用到了c语言的可变参数,所以会涉及到可变参数的提取。

#pragma once
#include<cstdio>
#include<cstdlib>
#include<cstdarg>
#include<ctime>
#include<cassert>
                          //调试     注意       警告       致命
const char *log_level[]={"DEBUG", "NOTICE", "WARINING", "FATAL"};
#define DEBUG 0
#define NOTICE 1
#define WARINTNG 2
#define FATAL 3

void logMessage(int level,const char* format,...)
{
    assert(level>=DEBUG);
    assert(level<=FATAL);

      //获取当前用于的名字。
    char* name=getenv("USER");
    char loginfo[1024];

    va_list ap;//ap->char*
    va_start(ap,format);
//int vsnprintf(char *str, size_t size, const char *format, va_list ap);  会自己调用va_arg进行提取,但是最后不会调用va_end提取
    vsnprintf(loginfo,sizeof(loginfo)-1,format,ap);
    va_end(ap);//ap==nullptr
    FILE* out=(level==FATAL)?stderr:stdout;

    fprintf(out,"%s | %u | %s %s\n",log_level[level],time(nullptr),name==nullptr?"unname":name,loginfo);
}

三,服务端

       其实也就是写一个"服务器",只不过这个服务器时简化版的,没有啥功能。

1,服务器参数

每一个服务器都必须有自己的ip和port(端口号),前者标识一台主机,后者标识一个主机上的进程,没有错,服务器也是一个进程,不过他是一个"死循环"进程,因为服务器24小时都可能有用户进行访问。第四个参数时为了记录连接服务器的信息,为了后面的群聊实现。

2,创建套接字

第一个参数是本地通信和网路通信的域,网络通信AF_INET,第二个参数是套接字类型,主要是面向字节流用户数据报 ,udp是用户数据报。创建好了之后,返回值就相等于一个文件描述符一样,创建失败返回-1.

 

3,绑定

3.1 填充套接字信息

填充网络协议,比如局域网通信还是网络通信,ip,端口号

3.2 绑定

把前面填充好的套接字信息绑定到进程中。 

 

 4,启动服务器

4.1接收客户端请求

recvform函数接收客户端请求。同时最后面俩个参数是输出形参数,为的是带回客户端的套接字信息,这样我们服务端在处理完用户请求之后就可以把处理结果给返回给用户了。

4.2记录用户信息

因为我们设计的是一个群聊嘛。所以一个用户发送的消息,我们会把他转发给所有的用户,所以记录连接的用户,之后进行消息转发。最后又视频展示效果的。

4.3 消息转发

 4.4 main函数代码

就是给一个ip和一个端口号,也可以直接给一个端口号,在bind那里我们可以控制的。

4.5服务器整体代码

#include <iostream>
#include <string>

#include <unistd.h>
#include <cstdlib>
#include <ctype.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <cstring>
#include <strings.h>
#include <unordered_map>
#include "Log.hpp"
using namespace std;

static void usage(const string s)
{
    cout << "Usage:\n\t" << s << " port [ip]" <<endl;
}
class UdpServer
{
public:
    // 初始化端口号、ip、套接字的信息
    UdpServer(uint16_t port, string ip = "")
        : port_(port), ip_(ip), sockfd_(-1)
    {
    }
    ~UdpServer()
    {
    }

public:
    void init()
    {
        // 1,创建socket套接字   相当于打开了一个文件
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd_ < 0)
        {
            logMessage(FATAL, "socket %s %d", strerror(errno), sockfd_);
            exit(1);
        }
        // 不需要换行了,log里面换过了。
        logMessage(DEBUG, "socket success: %d", sockfd_);
        // 2,填充绑定网络信息 指明 ip_ port_
        struct sockaddr_in local; // 注意这里不是socketaddr_in。
        bzero(&local, sizeof(local));
        // 填充协议家族
        local.sin_family = AF_INET;
        // 填充端口号,本地转网路
        local.sin_port = htons(port_);
        // 服务器都必须具有IP地址,"xx.yy.zz.aaa",字符串风格点分十进制 -> 4字节IP -> uint32_t ip
        // INADDR_ANY(0): 程序员不关心会bind到哪一个ip, 任意地址bind,强烈推荐的做法,所有服务器一般的做法
        // inet_addr: 指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行 h—>n
        // ip 地址
        local.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());

        // 3,绑定 bind
        if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) == -1)
        {
            logMessage(FATAL, "bind %s: %d", strerror(errno), sockfd_);
            exit(2);
        }
        logMessage(DEBUG, "bind success :%d ", sockfd_);
    }

    void start()
    {
        // 设计服务器的时候,我们要明白服务器都是死循环,因为用户任何时候都可能发起请求。
        char inbuffer[1024];  // 接收消息
        char outbuffer[1024]; // 发送消息

        while (true)
        {
            // 我们接收客户端的请求,之后要想要客户端的请求,那么我们要知道客户端的信息,这里我们通过一个输出形参数带回客户端信息。
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // udp是没有连接的
            int size = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (size > 0)
            {
                inbuffer[size] = 0;
            }
            else if (size == -1)
            {
                logMessage(DEBUG, "recvform %d : %d", strerror(errno), sockfd_);
                // 接收请求失败,那就继续接收呗,总不能任性到接收一个客户失败,把服务器挂掉呗。
                continue;
            }
            //logMessage(DEBUG, "recvfrom success: %d ", sockfd_);
            // 读取用户请求成功,该处理用户请求,然后把处理结果返回回去。
            // 获取用户信息。
            string peerip = inet_ntoa(peer.sin_addr);
            uint16_t peerport = ntohs(peer.sin_port);
            //记录用户数据,没有的话添加,有的话不管了。
            checkonlineuser(peerip, peerport, peer);

            //打印一下客户端请求,方便调试演练。
            logMessage(NOTICE,"[%s : %d ] %s",peerip.c_str(),peerport,inbuffer);
            //把这个客户发送的消息回环给所有的客户,就类似于微信群聊
            messageroute(peerip,peerport,inbuffer);
        }
    }

    void checkonlineuser(string& peerip, uint16_t peerport, const struct sockaddr_in& peer)
    {
        string key=peerip;
        key+=":";
        key+=to_string(peerport);
        auto iter=users_.find(key);
        if(iter==users_.end())
        {
            //没有的,要添加
            users_[key]=peer;

        }else
        {
            //看需要的话可以做点事,不需要就算了。
        }
    }

    void messageroute(string ip,uint16_t port,string info)
    {
        //注明:谁发送什么消息。
        string message="[";
        message+=ip;
        message+=":";
        message+="]";
        message+=info;
        for(auto& e:users_)
        {
            //把消息发送给每一个服务的客户端.
            sendto(sockfd_,message.c_str(),message.size(),0,(const struct sockaddr*)&e.second,sizeof(e.second));
        }
    }

private:
    uint16_t port_;                                   // 端口号
    string ip_;                                       // ip
    int sockfd_;                                      // 套接字信息
    unordered_map<string, struct sockaddr_in> users_; // 记录链接的用户套接字.   key:谁   value:套接字的信息
};

// struct client{
//     struct sockaddr_in peer;
//     uint64_t when; //peer如果在when之前没有再给我发消息,我就删除这用户
// }

int main(int argc,char* argv[])
{
    //程序名,端口号,ip
    //端口号必须有,ip不传的话那我们就设计就随机绑定一个,程序员控制的。
    if(argc!=2 && argc!=3)
    {
        usage(argv[0]);
    }
    uint16_t port=atoi(argv[1]);//字符串转整形。想清楚,别搞复杂了。
    string ip;
    if(argc==3)
    {
        ip=argv[2];
    }
    UdpServer server(port,ip);
    //初始化服务器
    server.init();
    //启动服务器
    server.start();
    //服务端暂时就完了。如果不发布不搞守护进程的话.

    return 0;
}

 四,客户端

1,输入记录服务端的ip和端口号

我们要和服务端通信嘛,这个必须要知道呢。

2,创建套接字

3,填充服务端套接字信息

4,开始通信

sendto向服务端发送请求

当前套接字给服务端套接字。dest_addr发送请求 

recvform接收服务端返回的请求结果

5,通信代码

俩个执行流,一个发起请求,一个接收请求的结果。

6,整体代码

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstdlib>
#include <ctype.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <cstring>
#include <strings.h>
#include<pthread.h>
#include"Log.hpp"
using namespace std;



void usage(char* s)
{
    cout<<"示范:"<<s<<"server_ip server_port"<<endl;
}

struct sockaddr_in server_;


//读取服务端的响应,并打印出来、
void*recverAndPrint(void* args)
{
    while(true)
    {
        int sockfd=*(int*)args;
        char buffer[1024];
        struct sockaddr_in tmp;
        socklen_t len=sizeof(tmp);
        int s=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&tmp,&len);
        if(s>0)
        {
            buffer[s]=0;
            cout<<"server echo # "<<buffer<<endl;
        } 
    }
}
int main(int argc,char* argv[])
{

    if(argc!=3)
    {
        usage(argv[0]);
        exit(1);
    }

//1,命令行设置要访问的服务器。
    string server_ip=argv[1];
    uint16_t server_port=atoi(argv[2]);
//2.创建客户端
//2.1创建socket
   int sockfd=socket(AF_INET,SOCK_DGRAM,0);
   if(sockfd<0)
   {
    logMessage(FATAL,"socket fail ");
    exit(2);
   }
   logMessage(DEBUG,"socket success %d ",sockfd);
   //client也是需要绑定自己的信息的,但是不要自己绑定,而是os绑定的。
   //因为客户端比较多,你自己绑定不知道哪一个绑定了,哪一个没有绑定,
   //如果你绑定的被别人绑定了,那么你就会绑定失败了,所以交给OS就好了。OS给你随机绑定 ip port。安心使用吧。
   //server进行绑定时因为server的ip port本来就是被大家熟知的,比如百度的网址一般时不会变化的。
//2.2 填写服务端的信息用来给服务端发送请求。
bzero(&server_,sizeof(server_));

server_.sin_family=AF_INET;
server_.sin_addr.s_addr=inet_addr(server_ip.c_str());
server_.sin_port=htons(server_port);

//可以准备通信了起始这里就,知道服务端信息了,自己信息os再第一次发送消息的时候随机绑定。


//俩个线程流去搞,一个线程流接收服务端的结果,一个线程流给服务端发送请求。
pthread_t t;
pthread_create(&t,nullptr,recverAndPrint,(void*)&sockfd);

//3,开始同行
string buffer;
while(true)
{
    cout<<"Please Enter# ";
    getline(cin,buffer);
    sendto(sockfd,buffer.c_str(),buffer.size(),0,(const struct sockaddr*)&server_,sizeof(server_));
}
    return 0;
}

五,视频展示

udp套接字群聊展示

 

lyh_linux-test: linux代码练习。 - Gitee.com

 

 

 

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

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

相关文章

单商户商城系统功能拆解47—应用中心—自定义海报

单商户商城系统&#xff0c;也称为B2C自营电商模式单店商城系统。可以快速帮助个人、机构和企业搭建自己的私域交易线上商城。 单商户商城系统完美契合私域流量变现闭环交易使用。通常拥有丰富的营销玩法&#xff0c;例如拼团&#xff0c;秒杀&#xff0c;砍价&#xff0c;包邮…

JVM之class加载过程

一、定义&#xff1a; java虚拟机把描述类的数据从class文件加载到内存&#xff0c;并对数据进行 校验/准备/解析和初始化&#xff0c;最终形成可以被虚拟机直接使用的Java类型&#xff0c;这个过程被称作虚拟机的类加载机制。 称作虚拟机的类加载机制。 loading -> linki…

南卡和FIIL蓝牙耳机哪个比较好?数码达人深度对比评测

2023即将到来&#xff0c;蓝牙耳机对于大部分人来说&#xff0c;成为了越来越重要的存在。日常生活中随处可见的都是在使用蓝牙耳机的人&#xff0c;有听歌的、看剧的、玩游戏的等等。伴随着更多的人使用蓝牙耳机&#xff0c;市面上可见的蓝牙耳机数量正在飙升&#xff0c;这也…

MySQL 的自增主键一定是连续的吗?

全文摘要&#xff1a;结合实例分析了自增值保存在哪里&#xff0c;自增值的修改策略&#xff0c;以及自增值不连续的四个场景&#xff0c;希望对各位小伙伴们有所帮助~ 众所周知&#xff0c;自增主键可以让聚集索引尽量地保持递增顺序插入&#xff0c;避免了随机查询&#xff…

微软确认配置错误导致65,000多家公司的数据泄露

©网络研究院 微软证实&#xff0c;在安全漏洞导致端点无需任何身份验证即可通过互联网公开访问后&#xff0c;它无意中暴露了与数千名客户相关的信息。 微软在警报中表示&#xff1a; “这种错误配置可能导致未经身份验证访问与微软和潜在客户之间的交互相对应的一些业务…

互联网大厂Java岗考点(阿里+百度+腾讯+字节跳动+美团+京东)

本文扼要 本文结构主要分为以下三个部分&#xff1a; 01 互联网大厂考点&#xff08;阿里百度腾讯字节跳动美团京东&#xff09;02 Java 面试考点大全&#xff08;基本功底常用技术技术深度技术经验学习能力工作能力项目经验&#xff09;03 面试真题重现 01 互联网大厂考点 …

OPSS-PEG-N3叠氮聚乙二醇巯基吡啶, N3-PEG-OPSS,点击化学PEG试剂

名称 叠氮聚乙二醇巯基吡啶 N3-PEG-OPSS 中文名称 叠氮PEG巯基吡啶 巯基吡啶PEG叠氮 巯基吡啶聚乙二醇叠氮 英文名称 N3-PEG-OPSS OPSS-PEG-N3 Azide-PEG-OPSS OPSS-PEG-Azide 溶剂 溶于二氯甲烷&#xff0c;氯仿&#xff0c;乙酸乙酯&#xff0c;四氢呋喃等有机溶剂 存储条…

pycharm安装使用pyQt5

一、创建项目 二、安装pyqt5库 三、配置PyCharm外部工具 打开File->settings->Tools->External Tools 1、配置QtDesigner 程序&#xff1a;D:\python3.7\Lib\site-packages\qt5_applications\Qt\bin\designer.exe//安装包的时候回自动下载&#xff0c;直接在包文件…

【微服务技术09】统一网关Gateway

【微服务技术09】统一网关Gateway 案例代码&#xff1a;https://gitee.com/pikachu2333/spring-cloud-hexuan 网关作用 网关功能&#xff1a; 身份认证和权限校验服务路由、负载均衡请求限流 权限控制&#xff1a; 网关作为微服务入口&#xff0c;需要校验用户是是否有请求…

配置Typora

配置Typora 文章目录配置Typora阿里云1&#xff09;网页搜索阿里云OSS2&#xff09;注册账号3&#xff09;点击立刻开通a) 点击“产品价格”b) 初次付费c) 交钱以免造成后续无法访问d&#xff09;进入管理控制台e) 创建钥匙PicGo1&#xff09;下载安装2&#xff09;设置选择显示…

领域知识网络即服务:知识助力产业升级“无形的手”?

文|智能相对论 作者|叶远风 你用过ChatGPT了吗&#xff1f; 这个风靡全球的AI应用&#xff0c;以出圈的方式展现着AI的能量。 一个对话的界面&#xff0c;可以聊任何你想聊的话题&#xff0c;可以写出任何你想要的文字。 似乎有无限的人类知识&#xff0c;被集中到了一起&…

别再背锅了,这个小技巧统计第三方接口耗时很安逸

前言 之前我有写过一篇记录生产环境事故的文章&#xff0c;获得了不少好评。 后续&#xff0c;我们团队有做过一些讨论&#xff0c;为了支撑运营维护&#xff0c;搭建了更好的日志平台GranfaLoki&#xff0c;也引入了SkyWalking做链路追踪。 但过程中也遇到了一些问题&#xff…

26个工业废水处理工艺流程图!水处理从业人员必看

1 矿泉水除溴酸盐工艺流程 季胺1型官能团耐受硫酸盐、氯离子等去除溴化物 2 电厂制备超纯水工艺流程 内冷水系统降电导装置&#xff0c;脱除阴阳离子 电导率&#xff1c;1μs/cm 3 锰矿除氨氮工艺流程 两串一备&#xff0c;通过磺酸基官能团捕捉铵根离子 4 垃圾渗滤液除氨…

5-7:Spring整合Elasticsearch

引入依赖 spring-boot-starter-data-elasticsearch 配置Elasticsearchcluster-name、cluster-nodes. Spring Data Elasticsearch ElasticsearchTemplateElasticsearchRepository 实现对帖子的搜索&#xff0c;将帖子的数据存到ES中 EslaticSearch使用 下载依赖 <!-- htt…

Elasticsearch:基于文件的用户认证

你可以使用内置文件域&#xff08;file realm&#xff09;管理和验证用户。 使用文件域&#xff0c;用户在集群中每个节点上的本地文件中定义。 重要&#xff1a;作为集群的管理员&#xff0c;你有责任确保在集群中的每个节点上定义相同的用户。 Elastic Stack 安全功能不提供任…

C++ Reference: Standard C++ Library reference: Containers: map: map: erase

C官网参考链接&#xff1a;https://cplusplus.com/reference/map/map/erase/ 公有成员函数 <map> std::map::erase C98 (1) void erase (iterator position); (2) size_type erase (const key_type& k); (3) void erase (iterator first, iterator last);…

HR是怎么看待PMP证书呢?

HR 也是打工人&#xff0c;企业需要什么样的人才就招聘什么样的求职者&#xff0c;问 HR 怎么看待 PMP 证书&#xff0c;其实也就是PMP 证书的市场价值怎么样。 市场上对PMP证书的评价争议非常大&#xff0c;尤其近两年考 PMP 的人越来越多&#xff0c;不可否认&#xff0c;很…

UNIAPP实战项目笔记52 输入手机号页面和输入验证码页面

UNIAPP实战项目笔记52 输入手机号页面和输入验证码页面 实际案例图片 输入手机号页面 输入验证码页面 显示输入手机号 使用验证码登录 具体内容图片自己替换哈&#xff0c;随便找了个图片的做示例 具体位置见目录结构 完善布局页面和样式 代码 login-tel.vue页面 <templat…

33-Vue之ECharts-仪表盘图

ECharts-仪表盘图前言仪表盘的特点仪表盘的基本实现仪表盘的常见效果前言 本篇来学习写仪表盘图 仪表盘的特点 可以更直观的表现出某个指标的进度或实际情况 仪表盘的基本实现 ECharts 最基本的代码结构准备数据, 设置给 series 下的 data在 series 下设置 type:gauge &l…

毕业设计-国内疫情数据综合可视化分析系统

目录 前言 课题背景和意义 实现技术思路 实现效果图样例 前言 &#x1f4c5;大四是整个大学期间最忙碌的时光,一边要忙着备考或实习为毕业后面临的就业升学做准备,一边要为毕业设计耗费大量精力。近几年各个学校要求的毕设项目越来越难,有不少课题是研究生级别难度的,对本科…