Linux网络——UDP的运用

news/2024/12/28 17:56:38/

Linux网络——UDP的运用


文章目录

  • Linux网络——UDP的运用
    • 一、引入
    • 二、服务端实现
      • 2.1 创建socket套接字
      • 2.2 指定网络接口并bind
      • 2.3 接收数据并处理
      • 2.4 整体代码
      • 2.5 IP的绑定的细节
    • 三、用户端实现
      • 3.1 创建套接字
      • 3.2 指定网络接口
      • 3.3 发生数据并接收
      • 3.4 绑定问题
    • 四、代码
    • 五、UDP实现网络聊天室(简易版)


一、引入

在上一篇Linux网络——网络套接字中我们简述了TCP/UDP协议

其实网络通信的本质就是进程间通信,而进程间通信无非就是读和写(IO)

这篇文章我们借助UDP实现服务端(server)和客户端(client)进行通信
将所学理论运用到实践中

二、服务端实现

由于服务端是面向广大用户的,如果我们将IP地址写死,那么服务器将只能从我们写的哪个IP地址中接收信息
所以启动服务器是不需要IP地址的,它默认接收所以IP地址发来的信息

//static const std::string default_ip = "0.0.0.0";
static const uint16_t default_port = 8888;

2.1 创建socket套接字

调用系统接口socket函数,帮助我们创建套接字,本质是把文件和网卡关联起来
在这里插入图片描述

参数介绍:

domain:一个域,标识了这个套接字的通信类型(网络或者本地)
在这里插入图片描述

只用关注上面三个类,第一个与第二个AF_UNIX/AF_LOCAL表示本地通信,而AF_INET表示网络通信
type:套接字提供服务的类型
在这里插入图片描述
我们用UDP实现,所以使用SOCK_DGRAM
protocol:想使用的协议,默认为0即可,因为前面的两个参数决定了,就已经决定了是TCP还是UDP协议了

返回值:

成功则返回打开的文件描述符(指向网卡文件,其实就是文件描述符),失败返回-1

创建套接字的本质其实就是创建了一个文件描述符,并返回该文件描述符的值
只是该文件描述符是用于对应服务的网路数据传输

	_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){lg.LogMessgae(Fatal, "socket error, sockfd : %d\n", _sockfd);exit(1);}lg.LogMessgae(Info, "socket success, sockfd : %d\n", _sockfd);

2.2 指定网络接口并bind

在这里插入图片描述

参数介绍:

socket:创建套接字的返回值
address:通用结构体(上一章Linux网络——网络套接字有详细介绍)
address_len:传入结构体的长度

我们要先定义一个sockaddr_in结构体,将结构体内对应的字段填充好,再将结构体作为参数传递
在这里插入图片描述

struct sockaddr_in {short int sin_family;           // 地址族,一般为AF_INET或PF_INETunsigned short int sin_port;    // 端口号,网络字节序struct in_addr sin_addr;        // IP地址unsigned char sin_zero[8];      // 用于填充,使sizeof(sockaddr_in)等于16
};

创建结构体后要先清空数据(初始化),我们可以用memset,也可以用系统接口:

#include <strings.h>void bzero(void *s, size_t n);

填充端口号的时候要注意端口号是两个字节的数据,涉及到大小端问题
在计算机中的普遍规定:在网络中传输的数据都是大端的
所以为了统一,无论我们机器是大端还是小端,在调用接口的时候,都将IP与端口号从主机序列转化为网路序列

端口号的接口

#include <arpa/inet.h>
// 主机序列转网络序列
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
// 网络序列转主机序列
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

IP的接口
对于IP,其实有两步:首先将字符串转换为整型,再解决大小端问题
系统给了直接能解决这两个问题的接口

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int inet_aton(const char *cp, struct in_addr *inp);in_addr_t inet_addr(const char *cp);// 点分十进制字符串in_addr_t inet_network(const char *cp);char *inet_ntoa(struct in_addr in);struct in_addr inet_makeaddr(int net, int host);in_addr_t inet_lnaof(struct in_addr in);in_addr_t inet_netof(struct in_addr in);

这里的inet_addr就是把一个点分十进制的字符串转化成整数再进行大小端处理

我们在启动服务器的时候只需要传入端口号即可,IP默认为INADDR_ANY
即默认接收所有的IP

代码

        // 2. 指定网络接口// 初始化结构体struct sockaddr_in local;bzero(&local, sizeof(local)); // memsetlocal.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n != 0){lg.LogMessgae(Fatal, "bind error\n");exit(2);}

2.3 接收数据并处理

服务端要获取到用户端发送过来的数据

在这里插入图片描述

参数说明:

sockfd:从哪个套接字读
buf:数据放入的缓冲区
len:缓冲区长度
flags:读取方式, 0代表阻塞式读取
src_addraddrlen:输出型参数,返回对应的消息内容是从哪一个客户端发出的。第一个是自己定义的结构体,第二个是结构体长度

    void Start(){char buffer[1024];for (;;){struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (n > 0){InetAddr addr(peer);buffer[n] = 0;std::cout << "client[" << addr.PrintDebug() << "]# " << buffer << std::endl;std::string task = _BackMessage(buffer);sendto(_sockfd, task.c_str(), task.size(), 0, (struct sockaddr *)&peer, len);}}}

现在我们想要知道是谁发送过来的消息,信息都被保存到了peer结构体中,我们知道IP信息在peer.sin_addr.s_addr
这是一个网络序列,要转成主机序列,其次为了方便观察,要把它转换成点分十进制
而这两个操作系统给了一个接口能够解决:

char *inet_ntoa(struct in_addr in);

同样获取端口号的时候也要由网络序列转成主机序列:

uint16_t ntohs(uint16_t netshort);

这里我对struct sockaddr_in进行了封装
单独写了一个类InetAddr用来专门读取struct sockaddr_in内的ip和端口号

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>class InetAddr
{public:InetAddr(struct sockaddr_in &addr):_addr(addr){_ip = inet_ntoa(addr.sin_addr);_port = ntohs(addr.sin_port);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}sockaddr_in Addr(){return _addr;}std::string PrintDebug(){std::string info = _ip;info += ":";info += std::to_string(_port);  // "127.0.0.1:4444"return info;}~InetAddr(){}private:std::string _ip;uint16_t _port;struct sockaddr_in _addr;
};

2.4 整体代码

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include "nocopy.hpp"
#include "Log.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include <strings.h>
#include <string.h>
#include "InetAddr.hpp"
#include "stdlib.h"
#include <functional>//static const std::string default_ip = "0.0.0.0";
static const uint16_t default_port = 8888;
static const int default_fd = -1;
static const int default_size = 1024;using func_t = function<std::string(std::string)>;class UdpServer : public nocopy
{
public:UdpServer(func_t BackMessage,const uint16_t port = default_port): _port(port), _sockfd(default_fd),_BackMessage(BackMessage){}void Init(){ // 1. 创建socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){lg.LogMessgae(Fatal, "socket error, sockfd : %d\n", _sockfd);exit(1);}lg.LogMessgae(Info, "socket success, sockfd : %d\n", _sockfd);// 2. 指定网络接口// 初始化结构体struct sockaddr_in local;bzero(&local, sizeof(local)); // memsetlocal.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n != 0){lg.LogMessgae(Fatal, "bind error\n");exit(2);}}~UdpServer(){}private:std::string _ip;uint16_t _port;int _sockfd;func_t _BackMessage;
};

2.5 IP的绑定的细节

如果我们将IP绑定为127.0.0.1,这个IP地址叫做本地环回地址

它的作用就是用来做服务器代码测试的
意思就是如果我们绑定的IP是127.0.0.1的话,在应用层发送的消息不会进入物理层,也就不会发送出去
在这里插入图片描述

当我们运行起来后想要查看网络情况就可以用指令netstat
后边也可以附带参数:

-a:显示所有连线中的Socket;
-e:显示网络其他相关信息;
-i:显示网络界面信息表单;
-l:显示监控中的服务器的Socket;
-n:直接使用ip地址(数字),而不通过域名服务器;
-p:显示正在使用Socket的程序识别码和程序名称;
-t:显示TCP传输协议的连线状况;
-u:显示UDP传输协议的连线状况;

在这里插入图片描述

在这里插入图片描述

三、用户端实现

要发送数据给服务器,就得知道服务器的IP和端口号
这里的IP就必须指明,用来表示我是连接哪台服务器上的哪个进程

uint16_t _serverport;
std::string _serverip;
int _sockfd;

这里的IP和port指的是要发送给谁,也就是需要服务器的IP和端口号

创建套接字就跟前面的一样:

    //1. 创建socketint _sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){lg.LogMessgae(Fatal, "socket error, sockfd : %d\n", _sockfd);exit(1);}lg.LogMessgae(Info, "socket success, sockfd : %d\n", _sockfd);

3.1 创建套接字

  	//1. 创建socketint _sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){lg.LogMessgae(Fatal, "socket error, sockfd : %d\n", _sockfd);exit(1);}lg.LogMessgae(Info, "socket success, sockfd : %d\n", _sockfd);

3.2 指定网络接口

    //2. 指定网络接口struct sockaddr_in send;memset(&send,0,sizeof(send));send.sin_family = AF_INET;send.sin_port = htons(stoi(argv[2]));send.sin_addr.s_addr = inet_addr(argv[1]);

3.3 发生数据并接收

在这里插入图片描述

这里的参数和上面的recvfrom差不多,而这里的结构体内部我们要自己填充目的IP和目的端口号
也就是服务器的IP和端口号

 	//3. 发送消息while(true){std::string buffer;std::cout<<"Please Enter# ";getline(std::cin,buffer);//cout<<buffer<<endl;ssize_t n = sendto(_sockfd,buffer.c_str(),buffer.size(),0,(struct sockaddr*)&send,sizeof(send));if(n == -1){cout<<"error"<<endl;}if(n > 0){char rec[1024];//没用,但有必要struct sockaddr_in tmp;socklen_t len = sizeof(tmp);ssize_t m = recvfrom(_sockfd, rec, sizeof(rec) - 1, 0, (struct sockaddr *)&tmp, &len);if(m>0){rec[m] = 0;InetAddr addr(tmp);std::cout<<"server["<<addr.PrintDebug()<<"]# "<<rec<<std::endl;}else{break;}}else{break;}}

3.4 绑定问题

首先bind的作用是允许应用程序指定一个端口号用于监听传入的数据报或数据流

对于服务端:
需要绑定一个公开的端口号,允许大家访问,如果是随机的,其他人不知道,也就访问不了,所以服务端需要绑定

对于客户端:
客户端在给服务器发信息的同时,服务器也可能给客户端发信息,所以客户端为了监听服务器有没有给自己回信息也需要bind一个端口号用于监听,但客户端不需要显式的bind,因为客户端的端口号不会被所有人访问,别人不需要知道,所以OS自动帮我们bind一个随机的端口号,另外也是为了防止端口号重复,避免破坏唯一性

那么为什么前面服务端必须显示的绑定port呢?

因为服务器的端口号是众所周知的,不能改变,如果变了就找不到服务器了

而客户端只需要有就可以,只用标识唯一性即可
举个例子:

我们手机上有很多的app,而每个服务端是一家公司写的,但是客户端却是多个公司写的
如果我们绑定了特定的端口,万一两个公司都用了同一个端口号呢?这样就直接冲突了

OS会自动填充主机IP和随机生成端口号进行绑定(在发送数据的时候自动绑定)
所以创建客户端我们只用创建套接字即可

四、代码

UdpServer

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include "nocopy.hpp"
#include "Log.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include <strings.h>
#include <string.h>
#include "InetAddr.hpp"
#include "stdlib.h"
#include <functional>//static const std::string default_ip = "0.0.0.0";
static const uint16_t default_port = 8888;
static const int default_fd = -1;
static const int default_size = 1024;using func_t = function<std::string(std::string)>;class UdpServer : public nocopy
{
public:UdpServer(func_t BackMessage,const uint16_t port = default_port): _port(port), _sockfd(default_fd),_BackMessage(BackMessage){}void Init(){ // 1. 创建socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){lg.LogMessgae(Fatal, "socket error, sockfd : %d\n", _sockfd);exit(1);}lg.LogMessgae(Info, "socket success, sockfd : %d\n", _sockfd);// 2. 指定网络接口// 初始化结构体struct sockaddr_in local;bzero(&local, sizeof(local)); // memsetlocal.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n != 0){lg.LogMessgae(Fatal, "bind error\n");exit(2);}}void Start(){char buffer[1024];for (;;){struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (n > 0){InetAddr addr(peer);buffer[n] = 0;std::cout << "client[" << addr.PrintDebug() << "]# " << buffer << std::endl;std::string task = _BackMessage(buffer);sendto(_sockfd, task.c_str(), task.size(), 0, (struct sockaddr *)&peer, len);}}}~UdpServer(){}private:std::string _ip;uint16_t _port;int _sockfd;func_t _BackMessage;
};#include "UdpServer.hpp"
#include <memory>void Usage(const std::string s)
{std::cout << "Usagr: " << s << " local_port" << std::endl;
}std::string BackMessage(const string s)
{return "Back Message# " + s;
}int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);return 1;}std::unique_ptr<UdpServer> usver(new UdpServer(BackMessage,std::stoi(argv[1])));usver->Init();usver->Start();return 0;
}

UdpClient

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include <memory.h>
#include "InetAddr.hpp"
#include <cstdio>
#include <string>
void usage(std::string s)
{std::cout<<"Usagr: "<<s<<" server_ip server_port"<<std::endl;
}int main(int argc,char *argv[])
{if(argc != 3){usage(argv[0]);return 0;}//1. 创建socketint _sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){lg.LogMessgae(Fatal, "socket error, sockfd : %d\n", _sockfd);exit(1);}lg.LogMessgae(Info, "socket success, sockfd : %d\n", _sockfd);//2. 指定网络接口struct sockaddr_in send;memset(&send,0,sizeof(send));send.sin_family = AF_INET;send.sin_port = htons(stoi(argv[2]));send.sin_addr.s_addr = inet_addr(argv[1]);//3. 发送消息while(true){std::string buffer;std::cout<<"Please Enter# ";getline(std::cin,buffer);//cout<<buffer<<endl;ssize_t n = sendto(_sockfd,buffer.c_str(),buffer.size(),0,(struct sockaddr*)&send,sizeof(send));if(n == -1){cout<<"error"<<endl;}if(n > 0){char rec[1024];//没用,但有必要struct sockaddr_in tmp;socklen_t len = sizeof(tmp);ssize_t m = recvfrom(_sockfd, rec, sizeof(rec) - 1, 0, (struct sockaddr *)&tmp, &len);if(m>0){rec[m] = 0;InetAddr addr(tmp);std::cout<<"server["<<addr.PrintDebug()<<"]# "<<rec<<std::endl;}else{break;}}else{break;}}return 0;
}

运行图:

在这里插入图片描述

在这里插入图片描述

五、UDP实现网络聊天室(简易版)

篇幅有限,只放gitee链接:UDP实现网络聊天室(简易版)


http://www.ppmy.cn/news/1558849.html

相关文章

ByConity BSP 解锁数据仓库新未来

文章目录 前言BSP 模式简介基于 TPC-DS 的 ELT 活动测试环境登录 ECS数据查询配置 执行 02.sqlsql解释&#xff1a;1. 第一步&#xff1a;创建 wscs 临时表2. 第二步&#xff1a;创建 wswscs 临时表3. 第三步&#xff1a;对比 2001 年和 2002 年的数据子查询 1&#xff1a;提取…

【ETCD】【实操篇(十七)】 etcd 集群定期维护指南

目录 概述Raft 日志保留键空间历史压缩&#xff1a;v3 API 键值数据库碎片整理空间配额快照备份 概述 为了保持 etcd 集群的可靠性&#xff0c;需要定期进行维护。根据 etcd 应用程序的需求&#xff0c;这些维护通常可以自动化进行&#xff0c;并且不会导致停机或性能显著下降…

深度学习-论文即插即用模块1

[深度学习] 即插即用模块详解与实践 深度学习近年来已经成为人工智能的核心驱动力&#xff0c;各种模型和技术被广泛应用于图像处理、自然语言处理、语音识别等领域。然而&#xff0c;构建深度学习模型的过程通常复杂且耗时。为了提高开发效率并降低技术门槛&#xff0c;“即插…

SpringCloudAlibaba实战入门之路由网关Gateway断言(十二)

上一节课中我们初步讲解了网关的基本概念、基本功能,并且带大家实战体验了一下网关的初步效果,这节课我们继续学习关于网关的一些更高级有用功能,比如本篇文章的断言。 一、网关主要组成部分 上图中是核心的流程图,最主要的就是Route、Predicates 和 Filters 作用于特定路…

idea 安装插件(在线安装、离线安装)

目录 在线安装 离线安装 在线安装 1、打开IntelliJ IDEA 2024.x软件&#xff0c; 点击file-Settings 2、点击搜索框&#xff0c;输入plugins&#xff0c;找到plugins列&#xff0c;输入xxx软件--点击install 安装 3、重启idea 离线安装 1、在官网上下载插件包 &#xff08;1&…

操作002:HelloWorld

文章目录 操作002&#xff1a;HelloWorld一、目标二、具体操作1、创建Java工程①消息发送端&#xff08;生产者&#xff09;②消息接收端&#xff08;消费者&#xff09;③添加依赖 2、发送消息①Java代码②查看效果 3、接收消息①Java代码②控制台打印③查看后台管理界面 操作…

Niushop开源商城(漏洞复现)

文件上传漏洞 注册一个账号后登录 在个人中心修改个人头像 选择我们的图片马 #一句话(不想麻烦的选择一句话也可以) <?php eval($_POST["cmd"]);?> #生成h.php文件 <?php fputs(fopen(h.php,w),<?php eval($_POST["cmd"]);?>); ?&…

流架构的读书笔记(2)

流架构的读书笔记&#xff08;2&#xff09; 一、建模工具之一沃德利地图 推测技术的发展,交流和辩论思想的最有力的方法是沃德利地图 沃德利地图的制作步骤 1确定范围和用户需求 2确定满足用户需求所需的组件 3在一条范围从全新到被人们接受的演进轴上评估这些组成 部分的演…