UDP 协议
UDP传输的过程类似于寄信。
- 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接。
- 不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息。
- 面向数据报: 不能够灵活的控制读写数据的次数和数量。
1. 面向数据报
数据报是独立的一整个,应用层交给 UDP 多长的报文,UDP原样发送,既不会拆分,也不会合并。
例如:用 UDP 传输 100 个字节的数据:
如果发送端调用一次 sendto,发送100个字节,那么接收端也必须调用对应的一次recvfrom,接收100字节。
而不能循环调用10次recvfrom, 每次接收10个字节。
2. UDP 协议端格式
UPD 的协议报头长度是 固定的 8字节。
16位 UDP 长度,表示整个数据报(UDP 首部 + UDP 数据)的 最大长度,即 216 = 64kb。
如果校验和出错, 就会直接丢弃。
报头(协议)的本质其实就是:结构化数据(结构体、位段)
// 结构体实现
struct udp_header
{
uint16_t src_port;
uint16_t dst_port;
uint16_t udp_len;
uint16_t check;
};
// 位段实现
struct udp_header
{
uint32_t src_port:16;
uint32_t dst_port:16;
//...
};
3. UDP 的封装和解包
🎯封装
- 应用层将信息拷贝给传输层,用
char* p
指针指向一个缓冲区,前面放 UDP 结构报头(固定 8 字节),后面放有效载荷,对报头内容的填充就可以写作:
((struct udp_header*)p)->src_port = xx;
((struct udp_header*)p)->dst_port = xx;
((struct udp_header*)p)->udp_len = xx;
((struct udp_header*)p)->check = xx;
🎯解包
- 传输层拿到信息,用
char* start
指针指向头部,对 upd 报头、有效数据的提取就可以写作:
// 读取报头信息
xx = ((struct udp_header*)p)->src_port;
xx = ((struct udp_header*)p)->dst_port;
xx = ((struct udp_header*)p)->udp_len;
xx =((struct udp_header*)p)->check;
// 找到有效载荷的起始
void* p = start + sizeof(struct udp_header);
4. UDP 的缓冲区
UDP 没有真正意义上的 发送缓冲区。调用 sendto 会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作。
UDP 具有接收缓冲区。但是这个接收缓冲区不能保证收到的 UDP 报的顺序和发送 UDP 报的顺序一致。如果缓冲区满了,再到达的UDP数据就会被丢弃。
UDP 的 socket 既能读,也能写, 这个概念叫做 全双工
接下来用 UDP 实现一个简单的服务器和客户端~
🔗一些前置知识及 API
UDP 通用服务器
udp_server.hpp
#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unordered_map>
#include "err.hpp"
#include "RingQueue.hpp"
#include "lockGuard.hpp"
#include "Thread.hpp"
namespace ns_server
{
const static uint16_t default_port = 8080;
using func_t = std::function<std::string(std::string)>;
class UdpServer
{
public:
UdpServer(uint16_t port = default_port) : port_(port)
{
std::cout << "server addr: " << port_ << std::endl;
pthread_mutex_init(&lock, nullptr);
p = new Thread(1, std::bind(&UdpServer::Recv, this));
c = new Thread(1, std::bind(&UdpServer::Broadcast, this));
}
void start()
{
//【1】创建 socket 接口,打开网络文件
sock_ = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_ < 0)
{
std::cerr << "create socket error: " << strerror(errno) << std::endl;
exit(SOCKET_ERR);
}
std::cout << "create socket success: " << sock_ << std::endl; // 3
//【2】给服务器指明IP地址(云服务器上不行)和Port
struct sockaddr_in local; // 这个 local 定义在 用户空间的特定函数的栈帧上,不在内核中!需要bind函数绑定socket
bzero(&local, sizeof(local)); // 等效于memset(&local,0,sizeof(local))
local.sin_family = AF_INET; // == PF_INET,是选择通信方式,这里选网络通信
local.sin_port = htons(port_); // 主机序列转成望楼序列
// inet_addr: 1,2
// 1. 字符串风格的IP地址,转换成为4字节int, "1.1.1.1" -> uint32_t -> 能不能强制类型转换呢?不能,这里要转化
// 2. 需要将主机序列转化成为网络序列
// local.sin_addr.s_addr = inet_addr(ip_.c_str());
// 实际上,云服务器,或者一款服务器,一般不要指明某一个确定的IP!!
// INADDR_ANY:让我们的 udpserver 在启动的时候,bind 本主机上的任意 IP
local.sin_addr.s_addr = INADDR_ANY;
if (bind(sock_, (struct sockaddr *)&local, sizeof(local)) < 0)
{
std::cerr << "bind socket error: " << strerror(errno) << std::endl;
exit(BIND_ERR);
}
std::cout << "bind socket success: " << sock_ << std::endl; // 3
p->run();
c->run();
}
void addUser(const std::string &name, const struct sockaddr_in &peer)
{
//?
// onlineuserp[name] = peer;
LockGuard lockguard(&lock);
auto iter = onlineuser.find(name);
if (iter != onlineuser.end())
return;
// onlineuser.insert(std::make_pair<const std::string, const struct sockaddr_in>(name, peer));
onlineuser.insert(std::pair<const std::string, const struct sockaddr_in>(name, peer));
}
void Recv()
{
char buffer[1024];
while (true)
{
// 收
struct sockaddr_in peer; // 要提取信息的缓冲区
socklen_t len = sizeof(peer); // 这里一定要写清楚,未来你传入的缓冲区大小
int n = recvfrom(sock_, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (n > 0)
buffer[n] = '\0';
else
continue;
std::cout << "recv done ..." << std::endl;
// 提取client信息 -- debug
std::string clientip = inet_ntoa(peer.sin_addr);
uint16_t clientport = ntohs(peer.sin_port); // 网络中读出来的是网络序列,需要转成主机序列
std::cout << clientip << "-" << clientport << "# " << buffer << std::endl;
// 构建一个用户,并检查
std::string name = clientip;
name += "-";
name += std::to_string(clientport);
// 如果不存在,就插入,如果存在,什么都不做
addUser(name, peer);
rq.push(buffer);
// // 做业务处理
// std::string message = service_(buffer);
// 发
// sendto(sock_, message.c_str(), message.size(), 0, (struct sockaddr*)&peer, sizeof(peer));
}
}
void Broadcast()
{
while (true)
{
std::string sendstring;
rq.pop(&sendstring);
std::vector<struct sockaddr_in> v; //
{
LockGuard lockguard(&lock);
for (auto user : onlineuser)
{
v.push_back(user.second);
}
}
for (auto user : v)
{
// std::cout << "Broadcast message to " << user.first << sendstring << std::endl;
sendto(sock_, sendstring.c_str(), sendstring.size(), 0, (struct sockaddr *)&(user), sizeof(user));
std::cout << "send done ..." << sendstring << std::endl;
}
}
}
~UdpServer()
{
pthread_mutex_destroy(&lock);
c->join();
p->join();
delete c;
delete p;
}
private:
int sock_; // 套接字
uint16_t port_; // 端口号
// func_t service_; // 我们的网络服务器刚刚解决的是网络IO的问题,要进行业务处理
std::unordered_map<std::string, struct sockaddr_in> onlineuser;
pthread_mutex_t lock;
RingQueue<std::string> rq;
Thread *c;
Thread *p;
// std::string ip_;
};
}
udp_server.cc
#include "udp_server.hpp"
#include <memory>
#include <string>
#include <cstdio>
using namespace ns_server;
using namespace std;
static void usage(string proc)
{
std::cout << "Usage:\n\t" << proc << " port\n" << std::endl;
}
//【业务处理】
// 上层的业务处理,不关心网络发送,只负责信息处理即可
std::string transactionString(std::string request) // request 就是一个string
{
std::string result;
char c;
for (auto &r : request)
{
if (islower(r))
{
c = toupper(r);
result.push_back(c);
}
else
{
result.push_back(r);
}
}
return result;
}
static bool isPass(const std::string &command)
{
bool pass = true;
auto pos = command.find("rm");
if(pos != std::string::npos) pass=false;
pos = command.find("mv");
if(pos != std::string::npos) pass=false;
pos = command.find("while");
if(pos != std::string::npos) pass=false;
pos = command.find("kill");
if(pos != std::string::npos) pass=false;
return pass;
}
// 需要实现:client把命令给server,server再把结果给client!
// ls -a -l
std::string excuteCommand(std::string command) // command用作一个命令
{
// 1. 安全检查
if(!isPass(command)) return "you are a bad man!";
// 2. 业务逻辑处理
FILE *fp = popen(command.c_str(), "r");
if(fp == nullptr) return "None";
// 3. 获取结果了
char line[1024];
std::string result;
while(fgets(line, sizeof(line), fp) != NULL)
{
result += line;
}
pclose(fp);
return result;
}
// 设置执行程序的命令为:./udp_server port
int main(int argc, char *argv[])
{
if (argc != 2)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
// unique_ptr<UdpServer> usvr(new UdpServer("120.78.126.148", 8082));
// unique_ptr<UdpServer> usvr(new UdpServer(transactionString, port));
// unique_ptr<UdpServer> usvr(new UdpServer(excuteCommand, port));
unique_ptr<UdpServer> usvr(new UdpServer(port));
// usvr->InitServer(); // 服务器的初始化
usvr->start();
return 0;
}
UDP 通用客户端
udp_client.cc文章来源:https://www.toymoban.com/news/detail-731527.html
#include <iostream>
#include <string>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include "err.hpp"
// 127.0.0.1: 本地环回,就表示的就是当前主机,通常用来进行本地通信或者测试
static void usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " serverip serverport\n" << std::endl;
}
// ./udp_client serverip serverport 客户端必须知道服务器的IP和端口号
int main(int argc, char *argv[])
{
if(argc != 3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if(sock < 0)
{
std::cerr << "create socket error" << std::endl;
exit(SOCKET_ERR);
}
// Q1:client 这里要不要bind呢?
// A1:要的!socket通信的本质[clientip:clientport, serverip:serverport]
// Q2:要不要自己bind呢?
// A2:不需要自己bind,也不要自己bind,OS自动给我们进行bind -- 为什么?client的port要随机让OS分配防止client出现
// 启动冲突 -- server 为什么要自己bind?1. server的端口不能随意改变,众所周知且不能随意改变的 2. 同一家公司的port号
// 需要统一规范化
// 明确server是谁
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
while(true)
{
//多线程化??
// 用户输入
std::string message;
std::cout << "[蛋哥的服务器]# ";
// std::cin >> message;
std::getline(std::cin,message);
// 什么时候bind的?在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP,1. bind 2. 构建发送的数据报文
//发送
sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
//接受
char buffer[2048];
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
int n = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&temp, &len);
if(n > 0)
{
buffer[n] = 0;
std::cout << buffer << std::endl;
}
}
return 0;
}
🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~文章来源地址https://www.toymoban.com/news/detail-731527.html
到了这里,关于【Linux】【网络】传输层协议:UDP的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!