【Linux后端服务器开发】UDP协议

目录

一、端口号

二、UDP报头格式

三、UDP的特点

四、UDP协议实现网络聊天群


一、端口号

端口号port标识了一个主机上进行通信的不同的应用程序。

  • 0 ~ 1023:系统端口号,HTTP、FTP、SSH等这些广为使用的应用层协议,它们的端口号都是固定的系统端口号(知名端口号)
  • 1024 ~ 65535:操作系统动态分配的端口号,客户端程序的端口号,可有操作系统分配或程序员分配(普通端口号)

知名端口号(Well-Know Port Number):

  • ftp服务器:21
  • ssh服务器:22
  • telnet服务器:23
  • http服务器:80
  • https服务器:443

查看知名端口号:cat /etc/services

netstat:一个用来查看网络状态的重要工具

语法:netstat -[选项]

  • n:拒绝显示别名,能显示数字的全部显示数字
  • l:仅列出有在 listen 状态的服务状态
  • p:显示建立相关连接的程序名
  • t:仅显示tcp相关选项
  • u:仅显示udp相关选项
  • a:显示所有选项,默认不显示 listen 相关

pidof:查看服务器的进程id(通过进程名查看进程id)

语法:pidof [进程名]

端口号   ----->   进程(唯一关系)

  • 一个端口号是否可以被多个进程bind?不可以,端口号指向进程必须唯一
  • 一个进程是否可以绑定多个端口号?可以,进程指向端口号不一定唯一

二、UDP报头格式

  • 16位UDP长度,表示整个数据报(UDP首部+UDP数据)的最大长度(64K)
  • 如果检验和出错,数据直接丢弃 
  • 定长报头,报头的本质是结构化数据

 

三、UDP的特点

UDP传输的过程类似于寄信:

  • 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接
  • 不可靠:没有确认机制,没有重传机制,如果因为网络故障导致数据段无法发送,UDP协议层也不会给应用层返回任何错误信息
  • 面向数据报:应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并,不能够灵活的控制读写数据的次数和数量

        

UDP的缓冲区:

  • UDP没有真正意义上的发送缓冲区,调用sendto会直接交给内核,由内核将数据报传给网络层协议进行后续的传输处理
  • UDP具有接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报文的顺序和发送UDP报文的顺序一致;如果缓冲区满了,再到达的UDP数据就会被丢弃

UDP的socket既能读,也能写,这就是全双工概念。

基于UDP的常用应用层协议:

  • NFS:网络文件系统
  • TFTP:简单的文件传输协议
  • DHCP:动态主机配置协议
  • BOOTP:启动协议(用于无盘设备启动)
  • DNS:域名解析协议

UDP协议的应用场景多为视频网站、直播平台等不担心数据包丢失的情况。

四、UDP协议实现网络聊天群

设计思路:

  • 服务器与客户端通过udp协议通信,即server和client都需要创建自己的socket套接字
  • server创建完socket套接字之后,需要将server自身的ip和port和套接字bind绑定client的套接字不需要显式bind绑定(由OS自动绑定)
  • server先运行,通过recvfrom()向数据的接收缓冲区循环读取数据
  • client在运行的时候指定server的ip和port,通过sendto()向服务器的接收缓冲区发送数据
  • User.h头文件,进行在线用户管理,通过ip和port形成用户信息结构体,可以进行在线用户的添加、删除、判断,并且在server接收到数据的时候,进行广播(对所有在线用户发送刚刚接收到的数据)
  • 所有在线用户信息通过一个静态全局变量的哈希表管理,key值是ip-port组成的字符串
  • client在运行的时候就会进行一次线程分离,分离出去的线程循环读取并打印client自身的接收缓冲区内的数据(来自广播的数据)

设计效果:当多个client连接server之后,只要是online登录了的用户,都可以向server发送数据的同时看到自己和其他登录用户发送的数据,这就是网络聊天群

User.h

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <unordered_map>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>class User
{
public:User(const std::string& ip, const uint16_t& port): _ip(ip), _port(port){}std::string ip(){return _ip;}uint16_t port(){return _port;}private:std::string _ip;uint16_t _port;
};class OnlineUser
{
public:OnlineUser(){}void AddUser(const std::string& ip, const uint16_t& port){std::string id = ip + "-" + std::to_string(port);_users.insert(std::make_pair(id, User(ip, port)));}void DelUser(const std::string& ip, const uint16_t& port){std::string id = ip + "-" + std::to_string(port);_users.erase(id);}bool IsOnline(const std::string& ip, const uint16_t& port){std::string id = ip + "-" + std::to_string(port);return _users.find(id) != _users.end();}void BroadcastMessage(int sockfd, const std::string& message, const std::string& ip, const uint16_t& port){for (auto& user : _users){struct sockaddr_in client;client.sin_family = AF_INET;client.sin_addr.s_addr = inet_addr(user.second.ip().c_str());client.sin_port = htons(user.second.port());std::string s = "[" + ip + "-" + std::to_string(port) + "]" + "# " + message;sendto(sockfd, s.c_str(), s.size(), 0, (struct sockaddr*)&client, sizeof(client));}}private:std::unordered_map<std::string, User> _users;
};

server.cpp

#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <functional>
#include <memory>#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "User.h"using namespace std;typedef function<void(int, string, uint16_t, string)> func_t;
static string defaultIP = "0.0.0.0";class UdpServer
{
public:UdpServer(const func_t& func, const uint16_t& port, const string& ip = defaultIP): _cb(func), _port(port), _ip(ip){}void Init(){// 1. 创建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){cerr << "socket error: " << errno << strerror(errno) << endl;exit(errno);}// 2. 绑定 ip:portstruct sockaddr_in local;local.sin_family = AF_INET;local.sin_addr.s_addr = htonl(INADDR_ANY);    // Address to accept any incomit message -------> 任意地址绑定// local.sin_addr.s_addr = inet_addr(_ip.c_str());     // string ---> uint32_t ---> htonllocal.sin_port = htons(_port);int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));if (n < 0){cerr << "bind error: " << errno << strerror(errno) << endl;exit(errno);}}void Start(){char buf[1024];while (true){struct sockaddr_in peer;socklen_t peer_len = sizeof(peer);ssize_t n = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&peer, &peer_len);if (n > 0){buf[n] = 0;string client_ip = inet_ntoa(peer.sin_addr);     // 网络序列 ---> 整型 ---> 点分十进制uint16_t client_port = ntohs(peer.sin_port);   string message = buf;cout << client_ip << "[" << client_port << "]# " << message << endl;_cb(_sockfd, client_ip, client_port, message);}}}private:string _ip;uint16_t _port;int _sockfd;func_t _cb;
};// 聊天群在线用户,静态全局变量
static OnlineUser g_online_users;// 处理message,server与业务逻辑解耦
void RouteMessage(int sockfd, string client_ip, uint16_t client_port, string message)
{if (message == "online")g_online_users.AddUser(client_ip, client_port);if (message == "offline")g_online_users.DelUser(client_ip, client_port);if (g_online_users.IsOnline(client_ip, client_port)){g_online_users.BroadcastMessage(sockfd, message, client_ip, client_port);}else{struct sockaddr_in client;bzero(&client, sizeof(client));client.sin_family = AF_INET;client.sin_addr.s_addr = inet_addr(client_ip.c_str());client.sin_port = htons(client_port);string response = "你还没有登录,请输入online登录加入聊天群";sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&client, sizeof(client));}
}void Usage()
{cout << "Usage: \n\t" << "server port" << endl;exit(1);
}int main(int args, char* argv[])
{if (args != 2)Usage();uint16_t port = atoi(argv[1]);unique_ptr<UdpServer> udp_server(new UdpServer(RouteMessage, port));udp_server->Init();udp_server->Start();return 0;
}

client.cpp

#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <functional>
#include <pthread.h>
#include <memory>#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "User.h"using namespace std;class UdpClient
{
public:UdpClient(const string& server_ip, const uint16_t& server_port): _server_ip(server_ip), _server_port(server_port), _sockfd(-1), _quit(false){}void Init(){// 1. 创建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);assert(_sockfd != -1);cout << "socket seccess: " << _sockfd << endl;// 2. 绑定bind,不需要显示绑定,由os分配}static void* ReadMessage(void* args){int sockfd = *(static_cast<int*>(args));pthread_detach(pthread_self());     // 线程分离while (true){char buf[1024];struct sockaddr_in tmp;socklen_t tmp_len = sizeof(tmp);ssize_t n = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&tmp, &tmp_len);if (n >= 0)buf[n] = 0;cout << "\r" << buf << "\n";}return nullptr;}void Run(){pthread_create(&_reader, nullptr, ReadMessage, (void*)&_sockfd);struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(_server_ip.c_str());server.sin_port = htons(_server_port);string message;while (!_quit){fprintf(stderr, "Enter:# ");fflush(stderr);getline(cin, message);message[message.size()] = 0;sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));usleep(100);}}private:string _server_ip;uint16_t _server_port;int _sockfd;bool _quit;pthread_t _reader;
};void Usage()
{cout << "Usage: \n\t" << "client ip port" << endl;
}int main(int args, char* argv[])
{if (args != 3)Usage();string server_ip = argv[1];uint16_t server_port = atoi(argv[2]);unique_ptr<UdpClient> udp_client(new UdpClient(server_ip, server_port));udp_client->Init();udp_client->Run();return 0;
}

运行效果:

 

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

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

相关文章

Qt中QMainWindow的相关介绍

目录 菜单栏 工具栏 状态栏&#xff1a; 停靠窗口&#xff1a; QMainWindow 是标准基础窗口中结构最复杂的窗口&#xff0c;其组成如下: 提供了菜单栏 , 工具栏 , 状态栏 , 停靠窗口 菜单栏&#xff1a;只能有一个&#xff0c;位于窗口的最上方 工具栏&#xff1a;可以有多…

AI制图工具丨Midjourney产品功能介绍

了解如何使用Discord上的Midjourney Bot通过简单的文本提示创建自定义图像 Midjourney是一款AI制图工具&#xff0c;只要关键字&#xff0c;就能透过AI算法生成相对应的图片&#xff0c;只需要不到一分钟。 可以选择不同画家的艺术风格&#xff0c;例如安迪华荷、达芬奇、达利…

基于单片机水质检测系统的设计与实现

功能介绍 以STM32单片机作为主控系统&#xff1b;液晶显示当前参数&#xff1b;PH模块采集当前水质PH酸碱度&#xff1b;DS18B20温度传感器采集当前水体温度&#xff1b;TDS传感器采集当前水体TDS值&#xff1b;浊度传感器采集当前水体浑浊度&#xff1b;按键设置PH、温度、TDS…

Elasticsearch 集群某一节点修改 IP 后无法启动问题复盘

1、集群环境及问题描述 集群版本&#xff1a;6.8.X集群节点&#xff1a;5节点&#xff08;三个节点为主数据节点&#xff0c;另外两个独立数据节点&#xff09;。问题描述&#xff1a;由于IP冲突&#xff0c;修改了一台服务器的IP&#xff0c;然后5台配置改了一下一次重启&…

Kafka 深度剖析

1、应用场景 1.1 kafka场景 Kafka最初是由LinkedIn公司采用Scala语言开发&#xff0c;基于ZooKeeper&#xff0c;现在已经捐献给了Apache基金会。目前Kafka已经定位为一个分布式流式处理平台&#xff0c;它以 高吞吐、可持久化、可水平扩展、支持流处理等多种特性而被广泛应用…

flask基本用法小白教程+按钮跳转到指定页面+python和pip安装(后附)

一、flask学习教程&#xff1a; 1.1 基本程序&#xff1a; 大家可以在pycharm中复制如下代码&#xff0c;先感受一下flask的基本用法&#xff1a; 点击链接可进入浏览器查看程序运行的结果&#xff0c;在127.0.0.1:5000后面添上/test1/等设定的文字&#xff0c;可查看不同函…

基于单片机的蓝牙音乐喷泉的设计与实现

功能介绍 以51单片机作为主控系统&#xff1b;通过HM-18蓝牙音频模块进行无线传输&#xff1b; 通过LM386功放模块对音频信号进行放大&#xff1b;手机端可以直接控制音频播放&#xff0c;并且最远距离可达20米&#xff1b;手机端可以进行任意音乐切换&#xff0c;播报、暂停&a…

微信小程序中常见组件的使用

文章目录 微信小程序中常见组件的使用视图组件viewscroll-viewswipermovable-area 基础组件icontextrich-textprogress 表单组件buttoncheckbox、checkbox-grouplabelforminputpicker单列选择器多列选择器时间选择器&日期选择器&地区选择器 picker-viewradiosliderswit…

MySQL数据备份与恢复

目录 ​编辑 一、数据备份 1.1物理备份 1.1.1冷备份 1.1.2热备份 1.1.3温备份 二、逻辑分区 2.1完全分区 2.2差异分区 2.3增量备份 三、数据备份恢复实验 3.1做一个数据 3.2物理冷备份与恢复 3.3mysqldump 备份与恢复&#xff08;温备份&#xff09; 3.4Mysql数据…

采集极验4滑块验证码图片数据

在网络安全领域&#xff0c;验证码是一种常见的用于验证用户身份或防止恶意机器人攻击的技术。而极验4滑块验证码作为一种广泛应用的验证码形式&#xff0c;其具有较高的安全性和防御能力。本文将以获取极验4滑块验证码图片数据为主题&#xff0c;介绍相关技术和方法。 一、极…

redis 相关

redis相关面试题 redis支持哪几种数据形式&#xff1f; String,hash,set,zset,list redis主要消费什么物理资源&#xff1f; 内存&#xff0c;key-value的形式&#xff0c; redis 具有快速和数据持久化的特征&#xff0c;如果不将数据放在内存中&#xff0c;磁盘 I/O 速度为严…

MySQL常用语句

目录 连接MySQL 数据库操作 表的操作 数据操作 进阶查询 源码等资料获取方法 连接MySQL -- 语法&#xff1a;mysql -u用户名 -p密码 注&#xff1a;--空格 起到注释的作用 mysql -uroot -p123456 数据库操作 -- 显示当前时间、用户名、数据库版本&#xff08;可以单独…

MATLAB 之 可视化图形用户界面设计

这里写目录标题 一、可视化图形用户界面设计1. 图形用户界面设计窗口1.1 图形用户界面设计模板1.2 图形用户界面设计窗口 2. 可视化图形用户界面设计工具1.1 对象属性检查器2.2 菜单编辑器2.3 工具栏编辑器2.4 对齐对象工具2.5 对象浏览器2.6 Tab 键顺序编辑器 3. 可视化图形用…

hibernate入门,springboot整合hibernate

Mybatis和Hibernate是我们常用的两大ORM框架&#xff0c;这篇文章主要介绍hibernate的使用&#xff0c;如何通过springboot整合hibernate&#xff0c;实现简单的crud功能。 添加依赖 首先&#xff0c;需要创建一个springboot项目&#xff0c;这里就取名为hibernate。项目创建完…

【Visual Studio Code】---自定义键盘快捷键设置

概述 一个好的文章能够帮助开发者完成更便捷、更快速的开发。书山有路勤为径&#xff0c;学海无涯苦作舟。我是秋知叶i、期望每一个阅读了我的文章的开发者都能够有所成长。 一、进入键盘快捷键设置 1、进入键盘快捷键设置方法1 使用快捷键进入键盘快捷键设置先按 Ctrl K再…

怎么做活码二维码?动态码在线生成技巧

现在制作二维码用户大多习惯使用活码二维码&#xff0c;其优势在于能够在二维码不变的情况下修改内容&#xff0c;能够生成二维码长期使用&#xff0c;还可以设置有效期、加密等其他功能可以使用。那么怎么生成活码二维码呢&#xff1f;可以使用二维码生成器&#xff08;免费在…

leetcode 445. Add Two Numbers II(两数相加)

用链表代表2个数字&#xff0c;这2个数字相加的和用链表返回。 最高位在链表的head. 思路&#xff1a; 1.链表逆序 数字相加是从低位到高位的&#xff0c;然而链表中的数字是从高位指向低位。 所以涉及到链表的逆序。 逆序之后只需从head到tail把两个链表的数字相加&#x…

克服 ClickHouse 运维难题:ByteHouse 水平扩容功能上线

前言 对于分析型数据库产品&#xff0c;通过增加服务节点实现集群水平扩容&#xff0c;并提升集群性能和容量&#xff0c;是运维的必要手段。 但是对于熟悉 ClickHouse 的工程师而言&#xff0c;听到“扩容”二字一定会头疼不已。开源 ClickHouse 的 MPP 架构导致扩容成本高&…

C++图形开发(16):绘制一个圆环和一根针

文章目录 绘制一个圆环和一根针1.1 绘制1.2 line()函数1.3 circle()函数1.4 setlinestyle()函数1.5 setlinecolor()函数 接下来&#xff0c;我会继续制作一些小游戏&#xff0c;但因为整个难度的上升&#xff08;毕竟我也是初学者&#xff09;&#xff0c;可能文章不会再像之前…

http连接处理(下)(四)

1.结合代码分析请求报文响应 下面我们将介绍服务器如何响应请求报文&#xff0c;并将该报文发送给浏览器端。首先介绍一些基础API&#xff0c;然后结合流程图和代码对服务器响应请求报文进行详解。 基础API部分&#xff0c;介绍stat、mmap、iovec、writev。 流程图部分&…