当前位置: 首页 > news >正文

Socket 套接字的学习--UDP

上次我们大概介绍了一些关于网络的基础知识,这次我们利用编程来深入学习一下

一:套接字Socket

1.1什么是Socket

socket API 是一层抽象的网络编程接口,适用于各种底层网络协议,IPv4IPv6,. 然而, 各种网络协议的地址格式并不相同。
1.2套接字的分类
套接字变成socket 的种类会变得多一些
<1> 本地socket (unix 域间socket)- > sockaddr_un
<2>网络socket :本地加网络 - > 用来通信 - > sockaddr_in
<3>最后都统一成了一个套接字,这个套接字作为统一的接口 ,sockaddr
为什么要统一接口呢?如果不同的厂商使用不同的网络接口,那么任何进程或者是客户端服务器之间的通信,将会十分麻烦,所以OS 提供了系统调用。所以使用本地或者网络的时候,要设置它的AF_INET还是AF_UNIX,也就是图片中的前十六位。然后设置完一些内部的类型等之后,最后都要强转成sockaddr这个类型的接口,去使用
!c语言中经常使用一些void* ,然后作强转,那为什么这没有用void* 最后去强转成想要的类型,而是直接使用了 这个sockaddr 这个结构体?
当时研究出来网络接口的时候,c语言还没有 void * ,其次就是 使用这个 其他类型,因为内部结构可以看出其实是很像c++里面的继承与多态,这样的话关联关系明显。
:UDP 服务通信
当然,通信的话肯定是两者以上在通信,我们今天用一个客户端和一个服务端作为通信的双方,然后使用UDP 协议进行 云平台之间的网络通信 和 云平台与Windows下的网络通信。
我们先梳理一下程序流程:
1.1 服务端流程
首先按照我们日常写的类的话,肯定就是先要初始化一些接口(网络通信的接口)
其次初始化接口结束后,我们作为服务端,就要等待客户端的一些需求,我们今天的任务是简单的通信交流,所以就是等待客户端的消息,然后收到消息之后就要回话,要知道是谁给我们发的消息,回话的话是给谁发,然后接着等待以后就重复就行了
1.2客户端流程
作为客户端,我们要知道给谁发消息,然后等待接受对方回过来的消息,接着重复就好了。细节我们慢慢了解。
1.3 认识创建套接字函数 --socket 
首先呢,我们明确一下头文件,在学习网络的过程中,有四个头文件每次都要包
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

socket 

我们可以用man 直接查这个socket,第一个参数是域或者协议家族,什么意思呢,就是以下这些

看它第二列目的,抵押给是本地通信,第二哥就是域间通信,第三个是IPV4,我们今天就用IPv4规定我们的IP地址。

第二个参数是代表套接字类型,也就是我们使用的TCP协议,还是UDP协议,我们今天用的UDP协议

UDP协议是不连接的,不可靠的,有最大长度的,所以就是 SOCK_DGRAM
第三个参数 默认为 0 就可以了
我们看一下这个函数的返回值
它说这个函数成功,就会返回一个套接字的文件描述符,其实也就是文件描述符,只不过是在网络中的一个文件,相当于你要是想要在网络中通信,就必须要在这个文件里写或者读。
LOG是我封装了一个日志,以后会更新这个的。
1.4 bind--绑定网络信息
我们说了socket 只是给我们创建了一个网络上通信用的文件,接着我们就要对于这个文件绑定对应的网络信息,什么信息呢,你要发送,是不是要告诉对方你是谁,对方才能给你回消息。
我们看这个bind,第一个参数不就是我们刚才用socket创建好的套接字返回的描述符吗
第二个参数 ,struct sockaddr* 这个结构体指针代表者什么呢

这个是什么呢,##这个符号,叫做拼接符,什么啥意思呢,就是前面的参数拼接我后面的family,也就是现在它现在传的这个参数是sa_ ,那么拼接起来就是sa_family。

也就是sa_family 代表 用AF_INET 等类型初始化

但是我们刚才也说了sockaddr ,是作为最后的网络的统一的接口的,也就是说,我们在UDP通信的时候今天申请的是网络通信,所以用的是sockaddr_in,所以我们要用sockaddr_in这个结构体把网络信息存进去,最后强转成sockaddr。

我们看这个结构体,第一个就是 sin_family(刚才说的拼接)

第二个 sin_port ->端口号

第三个 sin_addr -> IP地址

所以我们要把我们这个服务端的协议族,端口号,IP地址存进去。

 struct sockaddr_in local;bzero(&local, sizeof(local)); // 最好是先清零再去给它赋值 用memset也可以local.sin_family = AF_INET;local.sin_port = ::htons(_port); // 因为字节序的问题 ,最好是要给转换成网络的大端local.sin_addr.s_addr = inet_addr(_ip.c_str());

当大家写的时候会发现,并不能直接将端口号直接赋值给sin_port,因为什么呢?因为我们上次说了网络中也是存在大小端的,就是有字节序的问题,所以我们要先转换成大端,也就是用htons函数

也不能直接把IP地址直接赋值给sin_addr,为什么呢?因为sin_addr它本身是一个结构体,我们还要在结构体里面找结构体成员进行赋值。

,因为我申请的IP用的是string 存放的,所以我想把这个村给s_addr(类型是32位4字节),就要转换,用inet_addr()把char*转换成uint32_t 。

把网络信息存好,就可以进行绑定了。

//int n = bind(_sockfd, CONV(&local), sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind: " << strerror(errno);Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success";

1.5 接受网络消息--recvfrom

因为我们的UDP的传输报文方式是数据报形式的,所以用recvfrom

第一个参数依旧是老朋友,文教描述符,

第二个是一个void* buffer,意思就是存放信息的地方,

第三个是长度,

第四个默认位0就行,

第五个依旧就是统一接口sockaddr,代表发送端的网络信息(IP+Port)

第六个的socklen_t 代表sockaddr 的大小是多大的,用一个socklen_t的变量存起来然后取地址就可以。

 ssize_t n = ::recvfrom ( _sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);

1.6 sendto 发送消息

依旧是因为UDP传输的数据报,选择sendto

第一个参数依旧是老朋友,文教描述符,

第二个是一个void* buffer,意思就是存放信息的地方,

第三个是长度,

第四个默认位0就行,

第五个依旧就是统一接口sockaddr,代表是发送端的网络信息

第六个的是sockaddr的大小,直接用sizeof就可以

然后我们处理一下接受到的消息,之后再给客户端发送过去即可。

void Start(){LOG(LogLevel::INFO) << "start";while(true){//要接受客户端的信息//ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr,  *addrlen);char inbuffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer) ;ssize_t n = ::recvfrom ( _sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);if(n>0){uint16_t clientport = ::ntohs(peer.sin_port);std::string clientip = ::inet_ntoa(peer.sin_addr);inbuffer[n] = 0;std::string clientinfo = clientip + ":" + std::to_string(clientport) + "#" + inbuffer;LOG(LogLevel::DEBUG) << clientinfo;//接受消息后 还有给客户端发送数据std::string echo_string = "echo#" ; echo_string += inbuffer;::sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,CONV(&peer),len);}else{LOG(LogLevel::FATAL) << "recvfrom: " << strerror(errno);}}

我们再说一个转换函数,这个要比inet_ntoa()函数转换更加安全

_ip = ::inet_ntop(AF_INET, &_local.sin_addr, ipbuffer, sizeof(ipbuffer));

inet_ntoa 本质呢是在内部有一个静态存储区,如果是多线程调用它,很容易进行覆盖,所以它并不安全,inet_ntop 这个函数是让你自己申请一个静态区,所以由程序员自己掌握,相对安全。

1.6 是否要绑定固定的 IP 和端口号

端口号有一定的定义范围

IP地址的定义范围


IPv4地址是一个32位的二进制数,通常被分为四段表示,每段8位,并以点分十进制(dotted-decimal notation)的形式表示,即每个字节转换为对应的十进制数字,各字节间用点号隔开。范围:0.0.0.0~255.255.255.255
特殊用途的IPv4地址范围包括:
私有地址:这些地址范围专门保留用于内部网络,不会在全球互联网路由中出现。10.0.0.0 ~ 10.255.255.255    172.16.0.0 ~ 172.31.255.255     192.168.0.0 ~ 192.168.255.255
回环地址:用于本机回环测试,通常使用127.0.0.1代表本地主机。
127.0.0.0 到 127.255.255.255
自动专用IP寻址:当无法从DHCP服务器获得配置时,系统可能会自动配置此范围内的地址。
169.254.0.0 到 169.254.255.255。

我们要不要再服务器中,把某个进程也就是它的端口号,和IP绑定在一起呢

答案是,不要,因为一个公司里的服务器,会有很多的IP地址,我们要给同一个端口发送消息,那么经过不同的IP,同一个端口号,都能给这个进程发送信息,但是如果你绑定了特定的IP,那么只有通过这个IP,才能给这个进程发送消息,所以我们不能绑定IP。

1.7 客户端

在我们学习完服务端之后,客户端就很简单了。

首先依旧是创建一个网络通信的文件,和上面一样。

然后区别来了:客户端用不用绑定网络信息呢??、

答案是:不用。为什么呢?难道客户端发送不需要对方知道自己的网络信息吗?并不是这样的,只是不用绑定,是因为系统回自动给客户端自由随机分配一个,为什呢?假设你的淘宝进程 你给绑定了80的端口号,而抖音也要这个端口号,那么不就起了冲突吗?而让系统自己分配,就可以避免这种冲突。只是不用手动绑定,而不是没有网络信息。

接着是一样的发送消息,接受消息。

这个的设计思路是,直接默认手输服务端的IP和端口,所以我们要用 argc 和 argv 两个参数。

 if (argc != 3){std::cerr << "Usage: " << argv[0] << " localport" << std::endl;Die(USAGE_ERR);}ENABLE_CONSOLE_LOG();// ip + portstd::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(sockfd < 0){std::cerr << "socket error" << std::endl;Die(SOCKET_ERR);}struct sockaddr_in tmp;memset(&tmp, 0, sizeof(tmp));tmp.sin_family = AF_INET;tmp.sin_port=::htons(serverport);tmp.sin_addr.s_addr = inet_addr(serverip.c_str());while(true){std::cout<<"输入你要说的话"<<std::endl;std::string tstring;std::getline(std::cin,tstring);int n = ::sendto(sockfd,tstring.c_str(),tstring.size(),0,CONV(&tmp),sizeof(tmp));if(n<0){LOG(LogLevel::FATAL)<<"Client send to false";}char inbuffer[1024];struct sockaddr_in server;socklen_t len = sizeof(server);int m = ::recvfrom(sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&server),&len);if(m>0){inbuffer[m] = 0;std::cout<<inbuffer<<std::endl;}}

1.8 实现云平台和云平台直接的通信

那么你直接开两个终端,一个运行客户端,一个运行服务端,默认服务端 的端口号8080或者8888都可以。然后直接通信就可以了

1.9 云平台和Windows 的通信

首先你要在你的云平台的管理器那里,申请UDP通信,要保证你的UDP端口是可以用的。接下来给大家分享一个Windows端作为用户端的代码,云平台依旧使用我们刚才的服务端的代码就可以

#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h>
#include <Windows.h>
#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32.lib")
std::string serverip = ""; //自己云平台的IP
uint16_t serverport = 8080;//默认一个端口号就可以
int main()
{WSADATA wsd;WSAStartup(MAKEWORD(2, 2), &wsd);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());SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == SOCKET_ERROR){std::cout << "socker error" << std::endl;return 1;}std::string message;char buffer[1024];while (true){std::cout << "Please Enter@ ";std::getline(std::cin, message);if (message.empty()) continue;sendto(sockfd, message.c_str(), (int)message.size(), 0,(struct sockaddr*)&server, sizeof(server));struct sockaddr_in temp;int len = sizeof(temp);int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << buffer << std::endl;}}closesocket(sockfd);WSACleanup();return 0;
}

网络的代码一样,只是在Windows下,某些文件需要提前配置和打开关闭。

2.0奉上全部代码

UDPClient.cc

#include<iostream>
#include "Log.hpp"
#include "UdpServer.hpp"
#include <string>
#include "Common.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace LogModule;
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " localport" << std::endl;Die(USAGE_ERR);}ENABLE_CONSOLE_LOG();// ip + portstd::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(sockfd < 0){std::cerr << "socket error" << std::endl;Die(SOCKET_ERR);}struct sockaddr_in tmp;memset(&tmp, 0, sizeof(tmp));tmp.sin_family = AF_INET;tmp.sin_port=::htons(serverport);tmp.sin_addr.s_addr = inet_addr(serverip.c_str());while(true){std::cout<<"输入你要说的话"<<std::endl;std::string tstring;std::getline(std::cin,tstring);int n = ::sendto(sockfd,tstring.c_str(),tstring.size(),0,CONV(&tmp),sizeof(tmp));if(n<0){LOG(LogLevel::FATAL)<<"Client send to false";}char inbuffer[1024];struct sockaddr_in server;socklen_t len = sizeof(server);int m = ::recvfrom(sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&server),&len);if(m>0){inbuffer[m] = 0;std::cout<<inbuffer<<std::endl;}}return 0;
}

UDPServer.hpp

#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <strings.h>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "Log.hpp"
#include "Common.hpp"using namespace LogModule;const static  std::string fault_ip = "127.0.0.1";
const static  uint16_t fault_port = 8080;
class UDPServer
{
public:UDPServer(std::string ip = fault_ip, uint16_t port = fault_port): _ip(ip), _port(port), _isrunning(false), _sockfd(-1){}void InitServer(){_sockfd = socket(AF_INET, SOCK_DGRAM, 0); //这是创建好了一个网络上的文件if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket create false";Die(USAGE_ERR);}LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;struct sockaddr_in local;bzero(&local, sizeof(local)); // 最好是先清零再去给它赋值 用memset也可以local.sin_family = AF_INET;local.sin_port = ::htons(_port); // 因为字节序的问题 ,最好是要给转换成网络的大端local.sin_addr.s_addr = inet_addr(_ip.c_str());//要绑定网络上的信息  你要知道是谁传的 ip + portint n = bind(_sockfd, CONV(&local), sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind: " << strerror(errno);Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success";}void Start(){LOG(LogLevel::INFO) << "start";_isrunning = true;while(true){//要接受客户端的信息// ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,//                   struct sockaddr *src_addr,  *addrlen);char inbuffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer) ;ssize_t n = ::recvfrom ( _sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);if(n>0){uint16_t clientport = ::ntohs(peer.sin_port);std::string clientip = ::inet_ntoa(peer.sin_addr);inbuffer[n] = 0;std::string clientinfo = clientip + ":" + std::to_string(clientport) + "#" + inbuffer;LOG(LogLevel::DEBUG) << clientinfo;//接受消息后 还有给客户端发送数据std::string echo_string = "echo#" ; echo_string += inbuffer;::sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,CONV(&peer),len);}else{LOG(LogLevel::FATAL) << "recvfrom: " << strerror(errno);}}}~UDPServer(){}private:int _sockfd; // 建立的网络文件描述符std::string _ip;uint16_t _port;bool _isrunning;
};#endif

UDPServer.cc

#include<iostream>
#include "Log.hpp"
#include "UdpServer.hpp"
#include <string>
#include "Common.hpp"
using namespace LogModule;
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " localport" << std::endl;Die(USAGE_ERR);}ENABLE_CONSOLE_LOG();std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);std::unique_ptr<UDPServer> svr_uptr = std::make_unique<UDPServer>(ip,port);svr_uptr->InitServer();svr_uptr->Start();return 0;
}

Common.hpp

#pragma once#include <iostream>#define Die(code)   \do              \{               \exit(code); \} while (0)#define CONV(v) (struct sockaddr *)(v)enum 
{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR
};

http://www.xdnf.cn/news/1293139.html

相关文章:

  • 【H5】禁止IOS、安卓端长按的一些默认操作
  • java中在多线程的情况下安全的修改list
  • Win11和Mac设置环境变量
  • 一键自动化:Kickstart无人值守安装指南
  • [ Mybatis 多表关联查询 ] resultMap
  • 【SpringBoot系列-02】自动配置机制源码剖析
  • RabbitMQ面试精讲 Day 21:Spring AMQP核心组件详解
  • ARM 实操 流水灯 按键控制 day53
  • 部署 Docker 应用详解(MySQL + Tomcat + Nginx + Redis)
  • SQL详细语法教程(二)--DML(数据操作语言)和DQL(数据查询语言)
  • 【IntelliJ IDEA】如何在pom.xml中去除maven中未使用的依赖
  • 存量竞争下的破局之道:品牌与IP的双引擎策略|创客匠人
  • LeetCode 分类刷题:1004. 最大连续1的个数 III
  • PHP imagick扩展安装以及应用
  • 机器学习-Cluster
  • Java项目中地图功能如何创建
  • 机器学习阶段性总结:对深度学习本质的回顾 20250813
  • csp知识基础——贪心算法
  • 类和对象(中下)
  • 图像分类-动手学计算机视觉10
  • JDK17下载与安装图文教程(保姆级教程)
  • 基于DDPG的车辆纵向速度控制优化:兼顾速度与乘坐舒适性
  • 《Python学习之基础语法1:从零开始的编程之旅》
  • k8s资源管理
  • GPT-o3回归Plus用户,GPT5拆分三种模式,对标Grok
  • 什么是HTTP的无状态(举例详解)
  • 【C++详解】用红黑树封装模拟实现mymap、myset
  • 【C++】哈希的应用:位图和布隆过滤器
  • Query通过自注意力机制更新(如Transformer解码器的自回归生成)的理解
  • 【Java web】HTTP 与 Web 基础教程