安庆高端网站建设公司,发布软文是什么意思,云脑网络科技网站建设,必要是什么网站目录
前言
一. 端口号的认识
1.1 端口号的作用
二. 初识TCP协议和UDP协议
2.1 TCP协议
TCP的特点
使用场景
2.2 UDP协议
UDP的特点
使用场景
2.3 TCP与UDP的对比
2.4 思考
2.5 总结
三. 网络字节序
3.1 网络字节序的介绍
3.2 网络字节序思考
四. socket接口 …目录
前言
一. 端口号的认识
1.1 端口号的作用
二. 初识TCP协议和UDP协议
2.1 TCP协议
TCP的特点
使用场景
2.2 UDP协议
UDP的特点
使用场景
2.3 TCP与UDP的对比
2.4 思考
2.5 总结
三. 网络字节序
3.1 网络字节序的介绍
3.2 网络字节序思考
四. socket接口
4.1 socket 常见API
4.2 套接字编程的种类
4.3 sockaddr结构
五. UDP
5.1 服务端的实现
5.1.1 Init-创建服务端 1. 详谈sockaddr_in结构体
5.1.2 Run-服务器启动
1. 接收客户端信息
2. 发送信息到客户端
5.2 客户端的实现
5.2.1 先去装快递盒
5.2.2 将快递发送到邮局 5.2.3. 查收快递发出信息
六. 关于port和ip
查看所有活动连接
七. 结尾 前言 在计算机网络中端口号和 IP 地址是用于网络通信的基本元素它们在实现网络应用和服务时发挥着至关重要的作用。通过理解 TCP 和 UDP 协议网络字节序socket 接口以及端口和 IP 地址的工作方式我们能够深入掌握网络通信的原理和实现。 通过本篇文章的学习您将能够对端口号、TCP 和 UDP 协议、socket 编程等概念有更深入的理解为后续的网络编程打下坚实的基础。 一. 端口号的认识
目前我们已经接触了网络的基础在学习中我们肯定会有或多或少的疑惑 在进行网络通信的时候是不是我们两台机器在进行通信呢用户使用应用层软件(先把这个软件启动起来也就是变成了进程比如说刷抖音逛淘宝)完成数据的发送和接受的 先再思考一个问题当传输层把数据返回给上层(应用)的时候是怎么能够准确的发送给对应的应用呢就好比你正在浏览淘宝为什么传输层不把数据发送给抖音而是发给了淘宝呢这在其中起关键作用的就是端口号了
想象你住在一个大楼里每个住户都有自己的门牌号。当你寄信时邮递员需要知道楼房地址和正确的房间号才能把信送到。网络中的IP地址就像楼房的地址而端口号就像房间号它帮助数据找到正确的服务。比如访问网站时浏览器会通过端口80连接到服务器这样数据才能正确到达目标服务。
1.1 端口号的作用
端口号的作用是为了让一台设备能够同时处理来自不同应用或服务的数据请求。在计算机中设备如服务器可能运行多个程序或服务每个服务需要接收不同的数据流。IP地址就像设备的“门牌号”而端口号则像“房间号”它告诉数据包应该送到哪个特定的服务或程序。
没有端口号设备无法区分不同的应用和服务数据就会混乱无法正确到达目标应用。
举个例子
服务器可能同时在运行一个网站使用端口80和一个邮件服务使用端口25端口号帮助区分并确保数据能到达正确的服务。
端口号无论对于服务端还是客户端都能唯一的标识该主机上的一个网络应用层的进程 端口号是用于区分同一台设备上不同应用程序或服务的数字标识符。在计算机网络中设备通过IP地址进行识别但一台设备上可能同时运行多个应用或服务每个服务需要通过不同的端口号来接收数据。 简单来说端口号就像是设备的“房间号”它帮助数据包找到具体的应用程序或服务。每个端口号对应一种特定的协议或服务例如 80端口用于HTTP协议常见于网页浏览。443端口用于HTTPS协议安全的网页浏览。25端口用于SMTP协议发送电子邮件。 在公网上IP地址能标识唯一的一台主机。端口号port能标识该主机上的唯一的进程。也就是IP Port 能够标识全网唯一的一个进程
根据上面谈论的我们之前学习的有个进程pid 它也是唯一的标识咱又说网络通信的本质就是进程间的通信那么端口号和进程pid有什么联系呢 pid已经能够标识一台主机上进程的唯一性了为什么还要搞一个端口号呢 1.并不是所有的进程都要进行网络通信但是所有的进程都要有pid 1首先从技术角度绝对是可以的但是如果我们把网络和系统都用这个pid那么一旦系统改了网络也要跟着改牵一发而动全身. 所以不如单独设计一套专属于网络的数据来让系统和网络功能解耦有点像生活中我们有唯一的身份证但是在学校要有学号在公司要有工号 也就是说我们存在的意义相似但这并不代表我就得和你一样 2不是所有的进程都需要网络通信但是所有的进程都要有pid 再深入思考两个问题 一个进程可以绑定多个端口号吗 一个进程可以绑定多个端口号例如一个应用程序可能同时提供多个服务或功能每个服务都可能绑定到不同的端口上。举个例子一个服务器程序可能会同时处理HTTP请求端口80和HTTPS请求端口443这时它会分别监听这两个端口。虽然是同一个进程但它通过不同的端口号处理不同类型的通信。 一个端口号可以被多个进程绑定吗 不可以从哈希表就可以看出来如果一个端口号绑定了多个进程那当传输层向应用层传输数据的时候它到底要往哪个应用发呢比如说你抖音和淘宝都绑定的一个端口号那我此时正在浏览抖音短视频呢它把数据全都发送给了淘宝这肯定是乱套了的 二. 初识TCP协议和UDP协议 TCP协议Transmission Control Protocol传输控制协议和UDP协议User Datagram Protocol用户数据报协议是两种常见的网络传输协议它们都属于传输层用于在网络中传输数据。然而它们在数据传输的方式、可靠性、速度和适用场景上有显著的不同。 2.1 TCP协议 TCP协议是面向连接的协议意味着在传输数据之前通信的双方需要建立一个可靠的连接确保数据能够可靠地传输到对方。TCP协议提供了数据的顺序控制、错误检测和重传机制因此它是一种可靠的协议。 TCP的特点
可靠性数据包的传输是可靠的确保数据正确无误地到达目的地。连接性传输数据之前需要建立连接通过三次握手结束时需要关闭连接通过四次挥手。顺序控制数据按顺序到达接收方根据序列号将数据按正确的顺序重新组装。流量控制和拥塞控制TCP协议可以根据网络的状态调整数据的发送速率避免网络过载。
使用场景
适用于需要高可靠性的数据传输如文件传输FTP、网页浏览HTTP、电子邮件SMTP、远程登录SSH等。
2.2 UDP协议 UDP协议是无连接的协议在发送数据之前不需要建立连接也不保证数据的顺序和可靠性。它只是将数据包发送出去接收方是否成功接收和顺序如何发送方并不关心。 UDP的特点
无连接性无需建立连接直接发送数据。不可靠性不保证数据的顺序也不保证数据的正确性。如果数据丢失或出错接收方无法得知。速度快由于没有建立连接和额外的错误检查UDP的传输速度较快适用于实时性要求较高的场景。不进行流量控制和拥塞控制UDP没有流量控制机制因此不能像TCP那样保证数据的顺利传输。
使用场景
适用于对速度要求高但对可靠性要求不高的应用如实时视频流、在线游戏、VoIP语音通信等。
2.3 TCP与UDP的对比
特性TCPUDP连接类型面向连接无连接可靠性提供可靠的数据传输确保数据无误不保证数据可靠性数据可能丢失数据顺序保证数据按顺序到达不保证数据顺序传输速度相对较慢由于需要建立连接和确认较快没有连接和错误确认机制流量控制有确保不会发生网络拥塞无适用场景文件传输、网页浏览、邮件传输等实时视频、在线游戏、语音通信等
2.4 思考 问题1. 有连接和无连接怎么理解 连接就好比我们打电话的时候会先“喂”其实就是确保连接了之后我们的沟通才是有效的而无连接就好比我们发送邮件要么不发要么就发一整块反正我发了就行至于你收到没有我并不关心 问题2. 为什么tcp看起来比udp好这么多那为啥udp还得存在呢 计算机中很多词语都是中性的并无褒贬之意比如可靠和不可靠 就好比我们物理学的惰性气体惰性金属只不过是在描述它的特征而已 可靠是有成本的而不可靠会更简单。 比如tcp为了可靠所以要能够制定一个策略能够知道数据包是否丢失。然后进行重传而你在数据包丢出之后在还没确保传输成功之前你必然会把报文信息在传输层一直维护着否则你拿什么重传呢又或者需要重传几次呢万一数据乱序了你是不是还得排序、编序号 而udp就是一拿到数据包就转手往下扔他也懒得维护报文信息因为他压根不关心数据包的发送情况。 所以UDP在设计和维护上会变得非常简单 注意可靠的前提是网络必须连通
2.5 总结
TCP适用于那些对数据可靠性和顺序有较高要求的场合虽然它在传输速度上较慢但能够确保数据完整无误。UDP适用于那些要求低延迟、高实时性但对数据完整性要求不高的应用场景。
三. 网络字节序
3.1 网络字节序的介绍 我们已经知道 ,内存中的多字节数据相对于内存地址有大端和小端( 是指在计算机内存中存储多字节数据如整数、浮点数等时字节的存储顺序问题 )之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分 . 那么如何定义网络数据流的地址呢 ? 特性大端模式 (Big Endian)小端模式 (Little Endian)存储顺序高位字节在低地址低位字节在高地址低位字节在低地址高位字节在高地址示例0x12345678 存储为12 34 56 780x12345678 存储为78 56 34 12 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出; 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存; 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址. TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节. 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据; 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可; 为使网络程序具有可移植性使同样的C代码在大端和小端计算机上编译后都能正常运行可以调用以下库函数做网络字节序和主机字节序的转换 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回 ;如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
3.2 网络字节序思考 上述介绍了那么多归根溯源为什么要有网络字节序呢 在网络还没出现之前就已经有大端和小端之说了但是他们谁也说服不了谁因为都没有一个很成熟的方案能够证明大端好还是小端好而且当时这个标准即使制定了也没啥收益所以大家并没有这个动力因此在市面上大端和小端的机器都是有的在以往单机的情况下其实都不会有很大的影响 但是后来网络产生后通信双方并不清楚对方是小端还是大端所以在解析对方的信息的时候就会出现问题因为大端和小端的解析方法不一样从而导致发送方和接收方数据不一致的问题。 所以网络说既然我无法改变你们那么我就做个规定我发送报文的时候必须包含当前机器是大端还是小端的信息这样对方在收到这个数据包之后就可以根据这个字段来采取不同的解析方法。 可是这样也是不行的因为大端还是小端决定了解析的方法所以即使你在报文里提示了当前数据是大端还是小端我的解析方式如果是错的我也压根提取不到 所以我们网络又说了既然这样那我规定不管你机器是大端还是小端的只要你把这个数据发到网络上那就必须得是大端的所以这就要求小端机器如要想要进行网络通信就必须得先把自己的数据变成大端的才能往网络里发 四. socket接口
4.1 socket 常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 服务器)
int socket(int domain, int type, int protocol);// 绑定端口号 (TCP/UDP, 服务器 ) int bind(int socket, const struct sockaddr *address, socklen_t address_len);// 开始监听socket (TCP, 服务器) int listen(int socket, int backlog); // 接收请求 (TCP, 服务器 ) int accept(int socket, struct sockaddr* address,socklen_t* address_len); // 建立连接 (TCP, 客户端 ) int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen); 4.2 套接字编程的种类 套接字Socket 是计算机网络中用于通信的一个抽象概念。它是网络中两个设备之间数据传输的端点作为应用程序与网络协议之间的接口提供了应用程序与操作系统之间进行网络通信的手段。 套接字为应用程序提供了一个发送和接收数据的通道它通过指定通信协议如 TCP 或 UDP、IP 地址和端口号来实现不同设备之间的通信。 4.3 sockaddr结构 1.域间套接字编程同一个机器内 struct sockaddr_un 2.原始套接字编程编写网络工具原始套接字一般不关心传输层的东西他一般是绕过传输层去考虑网络层和链路层所以他一般被用来封装一些网络工具比如网络装包、网络诊断…… 3.网络套接字编程用来进行用户间通信struct sockaddr_in 我们想将网络接口统一抽象化就必须让参数的类型必须是统一的。所以我们统一使用sockaddr这个类型然后根据他的前16位来分辨他是哪一种类型的套接字所以在使用接口的时候要做一个强转 if(address-type AF_INET) {// 处理 IPv4 地址
}
else if(address-type AF_UNIX) {// 处理 Unix 域套接字地址
}结合所学的知识我们就会又有疑问了Linux接口是据C语言写的。 为什么要用sockaddr这个结构用void*不好吗C语言不是经常用他来做任意类型转换吗然后我们再用short强转一下不就拿到前面的数据了吗
因为网络接口出来的时候C语言的标准还没有void* 之后再想改也很难改回来了
五. UDP的实现
通过上面学习我们知道UDP是无连接、不可靠的。这就好比你要发送一个包裹给朋友。你使用的是一种简单的邮递服务不需要确认包裹是否成功送达。你只需要把包裹投递到邮局然后邮局会尽力将包裹送到朋友家当然你不会得到一个“送达确认”的反馈。即使包裹丢失了或者在送达途中损坏你也不会收到任何提醒。
所以当我们想实现一个UDP就可以模仿这如果实现邮局我们将 UDP 服务器 视为一个 邮局而 客户端 就是寄件人邮局接收和处理包裹数据并发送回邮寄人响应回应。
对于邮局来说它需要两个步骤
第一步先创建打好地基第二步也就是开始运行
5.1 服务端的实现
那么初始化需要什么呢对于邮局来说初始化意味着需要创建员工对于服务端来说初始化需要
5.1.1 Init-创建服务端
首先要创建套接字创建 UDP 套接字 就是为这个邮局准备一个“窗口”或“通道”用于接收和发送包裹数据。
//头文件
#include sys/socket.h
#include sys/types.h int socket(int domain, int type, int protocol);
//参数解释
//domain指定协议族Address Family。
//常见值
//AF_INET表示使用 IPv4 地址族。
//AF_INET6表示使用 IPv6 地址族。
//AF_UNIX表示使用 Unix 域套接字本地进程间通信。
//AF_PACKET表示在链路层上进行通信通常用于低层的网络编程。//type指定套接字类型Socket Type。
//常见值
//SOCK_STREAM表示使用 TCP 套接字提供可靠的、面向连接的通信。
//SOCK_DGRAM表示使用 UDP 套接字提供无连接的、不可靠的通信。
//SOCK_RAW表示使用原始套接字通常用于捕获或发送原始网络数据包。//protocol指定使用的协议。通常可以设置为 0让操作系统自动选择适当的协议。
//如果选择 SOCK_STREAM通常使用 IPPROTO_TCP。
//如果选择 SOCK_DGRAM通常使用 IPPROTO_UDP。
//对于原始套接字可能会使用 IPPROTO_RAW用于原始 IP 数据包。
//返回值
//成功返回一个非负的整数值文件描述符。这个文件描述符可以用来引用套接字。
//失败返回 -1并设置 errno 以标识错误类型。通常是因为系统资源不足或参数无效。
代码实例
sockfd_ socket(AF_INET, SOCK_DGRAM, 0); // PF_INET
if(sockfd_ 0)
{lg(Fatal, socket create error, sockfd: %d, sockfd_);exit(SOCKET_ERR);
}
lg(Info, socket create success, sockfd: %d, sockfd_); 使用 sockaddr_in 结构体来设置一个本地套接字地址并为其配置相关属性。
下面你就会有新的疑问了sockaddr是个什么东西呢struct sockaddr_in local...;是干嘛的呢就好比是登记信息比如说小明 男 负责东区小刚 男 负责西区...
struct sockaddr_in local;
bzero(local, sizeof(local));
local.sin_family AF_INET;
local.sin_port htons(port_); //需要保证我的端口号是网络字节序列因为该端口号是要给对方发送的
local.sin_addr.s_addr inet_addr(ip_.c_str());
struct sockaddr_in 是一个用于存储 IPv4 地址和端口号的结构体通常在网络编程中用来指定本地或远程的地址信息。它是 sockaddr 结构的一个扩展专门处理 IPv4 地址。 1. local.sin_family AF_INET; 表明这个通用类型是属于网络套接字还是域间套接字 sin_family 是 struct sockaddr_in 中的一个字段用于指定地址族。AF_INET 表示使用 IPv4 地址族。如果是使用 IPv6则需要将其设置为 AF_INET6。 2. local.sin_port htons(port_); sin_port 是 struct sockaddr_in 中存储端口号的字段。网络协议要求端口号以 网络字节序大端字节序传输。因为端口号必须在两个主机之间流通所以必须传输到网络中因此要转成字节序因此端口号需要通过 htons() 函数转换为网络字节序。 3. local.sin_addr.s_addr inet_addr(ip_.c_str()); 但是我们用户一般习惯输出的是 点分十进制所以我们必须把他转化成 4字节类型的整数 给小明分配好了地点之后然后跟小明说你在我们这工作了要打扮的干干净净的。所以也就是在创建好结构体之后把结构体清空归零
#include strings.hvoid bzero(void *s, size_t len);//s指向需要清零的内存区域的指针。
//len需要清零的内存区域的大小以字节为单位。
2.绑定套接字就好比邮局招收了一些邮递员并将他们分配到指定区域比如说小明性别男被分配到东区取快递东区的范围有50公里
//头文件
#include sys/socket.h
#include sys/types.h int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//参数解释
//sockfd//套接字文件描述符是通过 socket() 系统调用创建的套接字。
//这是一个整数值它标识了一个由操作系统管理的网络通信端点。
//addr//这是一个指向 sockaddr 结构体的指针它描述了要绑定的本地地址信息包括 IP 地址和端口。
//通常对于 IPv4 地址会使用 struct sockaddr_in 类型来存储地址信息。
//addrlen//这个参数表示 addr 结构体的大小通常传入 sizeof(struct sockaddr_in)。
//它告诉 bind() 函数地址结构体的大小以便正确解析结构体中的内容。//返回值
//成功返回 0表示成功将套接字与指定的地址和端口绑定。
//失败返回 -1表示绑定失败。如果发生错误errno 会被设置为相应的错误代码。
代码实例
if(bind(sockfd_, (const struct sockaddr *)local, sizeof(local)) 0){lg(Fatal, bind error, errno: %d, err string: %s, errno, strerror(errno));exit(BIND_ERR);}lg(Info, bind success, errno: %d, err string: %s, errno, strerror(errno)); (const struct sockaddr *)这里为什么要强转呢 在 bind 函数中强制类型转换 (const struct sockaddr *)local 是因为 bind 函数的参数要求是 struct sockaddr * 类型而实际传入的 local 变量是一个特定类型例如 struct sockaddr_in 类型的结构体。在 C 语言中struct sockaddr_inIPv4 地址结构和 struct sockaddr 是不同的类型但它们有相同的内存布局。 1. 详谈sockaddr_in结构体
我们进入sockaddr_in定义里面会发现有三个字段 sin_family。sin_port。sin_addr 问题如何快速将整数IP和字符串IP相转化 上述是如何实现相互转换的原理但是我们的库里面提供了这样的方法 aton() inet_addr()。用于将一个 IPv4 地址以字符串形式表示转换为网络字节序的 32 位无符号整数uint32_t。它将 IP 地址字符串例如 192.168.1.1转化为一个 32 位的整数这个整数表示了该 IP 地址在网络中的大端字节序。 其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。 关于inet_ntoa inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是 否需要调用者手动释放呢? man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放. 那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码: 因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果.
in_addr_t aton(const char *cp);
//cp指向以空字符结尾的字符串表示要转换的 IPv4 地址例如 192.168.1.1。
//成功返回一个 in_addr_t 类型的值它是一个 32 位的整数表示转换后的网络字节序 IP 地址。
//失败返回 0表示输入的 IP 地址无效。inet_addr()
//用途inet_addr() 将点分十进制格式的 IPv4 地址字符串例如 192.168.1.1转换为一个 in_addr_t 类型即 uint32_t 类型数值表示网络字节序的 IPv4 地址。
//返回值返回一个 in_addr_t 类型32 位无符号整数的 IP 地址。
//错误处理如果 IP 地址无效inet_addr() 返回 INADDR_NONE通常是 0xFFFFFFFF这通常表示一个无效的地址并非 0.0.0.0但是它并不会抛出错误或给出详细的错误信息。local.sin_addr.s_addr inet_addr(ip_.c_str()); 这里为什么要带s._addr呢
s_addr: sin_addr 结构体本身并没有直接存放 IP 地址而是通过 s_addr 成员来存储。s_addr 是一个 uint32_t 类型的变量它用于存储一个 32 位的 IP 地址。这个成员表示的是 IPv4 地址的具体数值。
整体代码 void Init()//创建服务器{//1、首先第一步是创建套接字 第一个是套接字的域 第二个是面向数据段 第三个是协议类型_sockfdsocket(AF_INET, SOCK_DGRAM, 0);if(_sockfd0)//创建失败{lg(Fatal,socket creat error,sockfd:%d,_sockfd);exit(SOCKET_ERR);}lg(Info,socket creat success,sockfd:%d,_sockfd);//2、 绑定套接字 要先把里面的字段给初始化了//先初始化一下字段struct sockaddr_in local; //创建套接字类型 bzero(local, sizeof(local)); //将类型都清空 然后我们再填local.sin_familyAF_INET;//family是用来表明这个类型是网络套接字还是域间套接字local.sin_porthtons(_port);//端口号必须要先变成网络字节序local.sin_addr.s_addrinet_addr(_ip.c_str());// inet_addr () 函数的作用是将点分十进制 的IPv4地址转换成网络字节序列的长整型。//bind绑定一下if(bind(_sockfd,(const struct sockaddr *)local, sizeof(local)) 0)// socelen_t 类型{lg(Fatal, bind error, errno: %d, err string: %s, errno, strerror(errno));exit(BIND_ERR);}lg(Info, bind success, errno: %d, err string: %s, errno, strerror(errno));}
5.1.2 Run-服务器启动
邮局建好了快递员也招聘登记了下一步就是开始启动了。那要怎么启动呢肯定是要先通知大家我们开业了然后收到客户发来的邮件和填的快递单然后发出去。那对于UDP也是一样的先运行起来将客户端的套接字信息收回来拿到客户端的信息进行加工处理。然后再把信息发给客户端。
那UDP该通过什么方式收到客户端的信息又该怎么把它发出去呢
1. 接收客户端信息
recvfrom() 是一个用于接收数据的系统调用它常用于网络编程特别是在使用无连接协议如 UDP时也可以用于带连接协议如 TCP的套接字接收数据。它不仅用于接收数据还能够返回发送数据的源地址信息因此它对于需要知道数据源的应用非常有用。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);//参数说明
//sockfd套接字文件描述符通过调用 socket() 创建。
//这个套接字描述符用于标识通信通道。它用于指定你想接收数据的套接字。//buf指向接收数据的缓冲区的指针。
//数据将存储在这个缓冲区中接收的数据会填充到这里。//len缓冲区的大小。
//这应该是缓冲区 buf 能容纳的最大字节数。如果接收到的数据大于该大小多余的数据将被丢弃。//flags接收数据的标志。
//常用标志有0没有标志普通接收、MSG_PEEK查看数据但不移除数据等。一般情况下可以传递 0。//src_addr指向 sockaddr 结构体的指针用来存储源地址信息。
//这是一个指向结构体的指针调用者提供的结构体类型应该根据协议族来定义如 sockaddr_in 用于 IPv4。该字段将填充为接收到的数据的源地址。//addrlen指向 socklen_t 类型的变量表示 src_addr 结构的大小。
//在调用前addrlen 应该包含 src_addr 结构的大小调用后它会被更新为实际的地址长度。//返回值
//成功时返回接收到的字节数。
//失败时返回 -1并将 errno 设置为错误代码。
代码实例
// 1、第一步是要将客户端端的套接字信息(输出型参数)收回来
struct sockaddr_in client; // 定义一个 sockaddr_in 结构体来存储客户端地址信息
socklen_t len sizeof(client); // 获取 client 结构体的大小在调用 recvfrom 时需要用到// 调用 recvfrom 接收数据
ssize_t n recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)client, len);// 检查接收是否出错
if (n 0) {// 输出错误信息包括 errno 错误码和错误描述lg(Warning, recvfrom error, errno: %d, err string: %s, errno, strerror(errno));continue; // 错误发生时跳过当前循环继续接收数据
}这里也可以用回调func的方法处理信息。这样可以的好处可以对代码进行分层
2. 发送信息到客户端
sendto() 是一个用于发送数据的系统调用通常用于无连接的套接字如 UDP或连接型套接字如 TCP。在使用 sendto() 时可以指定目标地址用于发送数据到特定的客户端或服务器。它通常在使用 UDP无连接协议时使用因为在 UDP 中数据发送不需要事先建立连接。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
//参数说明//sockfd套接字文件描述符通常通过调用 socket() 函数创建。
//用于标识用于发送数据的套接字。//buf指向要发送的数据的指针。
//这是一个指向缓冲区的指针缓冲区中包含将被发送的数据。//len数据的长度以字节为单位。
//这是要发送的数据的字节数必须小于等于 buf 指向缓冲区的大小。//flags发送标志。
//通常设置为 0表示没有任何特殊标志。可以使用 MSG_DONTWAIT 等标志来调整发送行为。//dest_addr指向目标地址的指针。
//这是一个指向 sockaddr 结构的指针包含接收方的地址信息。对于 IPv4通常使用 sockaddr_in 结构。//addrlen目标地址的大小字节数。
//通常设置为 sizeof(struct sockaddr_in)用于指定目标地址结构的大小。//返回值
//成功时返回实际发送的字节数。
//失败时返回 -1并设置 errno 来指示错误原因。
代码实例
// 2、我们将接收到的消息当作字符串处理一下然后返回给客户端
inbuffer[n] 0; // 确保接收到的消息以 \0 结尾变成有效的 C 字符串// 创建一个初始的字符串表示服务器接收到了一条消息
string res Server get a message: ; // 将接收到的消息 (inbuffer) 转换成 string 类型
string info inbuffer; // 将接收到的消息追加到 res 字符串后面
res info;// 使用 sendto 函数将处理后的消息返回给客户端
sendto(_sockfd, res.c_str(), res.size(), 0, (const sockaddr*)client, len);整体代码
void Run(func_t func) // Run 函数接受一个函数指针 func这个函数会处理接收到的消息并返回一个结果
{isrunning_ true; // 标记运行状态为 true表示服务器正在运行char inbuffer[size]; // 定义一个接收数据的缓冲区 inbuffer大小为 sizewhile (isrunning_) // 当 isrunning_ 为 true 时持续运行{struct sockaddr_in client; // 定义一个 sockaddr_in 结构体来存储客户端地址信息socklen_t len sizeof(client); // 获取客户端地址结构体的大小以便传递给 recvfrom// 调用 recvfrom 接收来自客户端的数据ssize_t n recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)client, len);if (n 0) // 如果接收失败n 会小于 0{// 记录接收错误并打印错误码和错误信息lg(Warning, recvfrom error, errno: %d, err string: %s, errno, strerror(errno));continue; // 继续下次循环不做其他处理}inbuffer[n] 0; // 确保接收到的数据是以 \0 结尾的 C 字符串std::string info inbuffer; // 将接收到的缓冲区数据转换为 std::string 类型方便处理std::string echo_string func(info); // 调用传入的函数 func 对消息进行处理并获取返回结果// 调用 sendto 发送处理后的数据 back 给客户端sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)client, len);}
}5.2 客户端的实现
5.2.1 先去装快递盒
还是拿上面那个邮递的故事来讲对于客户来说也是需要一个盒子去装你的物品的。对于UDP来说就是也需要套接字
代码实例
// 第一步 创建套接字
int sockfd socket(AF_INET, SOCK_DGRAM, 0); // 创建一个 UDP 套接字
if (sockfd 0) { // 检查套接字是否创建失败cout socket error endl; // 输出错误信息return 1; // 如果创建失败返回 1结束程序
}5.2.2 填写快递单 服务端进行套接字绑定是为了匹配信息客户端需要绑定吗 客户端也需要自己的IP和端口要不然服务器怎么找到你呢就好比你不仅需要用盒子去包装你的物品你还需要在盒子上贴上快递信息以便为了邮局能找到你核对信息 但是不需要用户显示的bind一般是由OS自由随机选择(因为我们多个app的客户端都会在同一个手机上如果需要自己绑定ip地址的话那么还需要各大开发商互相协商因此我们都同意由OS来给我们随机分配) 系统什么时候给我bind呢 首次发送数据的时候即当sendto的时候就绑定了。 // 服务器套接字类型
struct sockaddr_in server; // 输出型参数
bzero(server, sizeof(server)); // 清空 server 结构体中的内容
server.sin_family AF_INET; // 设置协议族为 AF_INET (IPv4)
server.sin_port htons(serverport); // 设置端口号使用 htons 转换为网络字节序
server.sin_addr.s_addr inet_addr(serverip.c_str()); // 设置 IP 地址使用 inet_addr 转换为网络字节序
socklen_t len sizeof(server); // 获取 server 结构体的大小用于后续传递给 sendto 或 bind 等函数一个端口号只能被一个进程bind对server是如此对于client也是如此
其实client的port是多少其实不重要只要能保证主机上的唯一性就可以为什么要求我们的服务器的端口要由用户来绑定呢对于服务器来讲端口号是几必须是确定的用户要关心将来用户是要链接访问我们的服务器的所以必须要知道服务器的IP地址和端口号如果你让OS去绑定那就可能今天一变明天一变。服务器昨天还好着今天就不能访问了
5.2.3 将快递发送到邮局
当你包装好快递盒填写了快递单那么接下来你要干什么呢当然是要把这些东西送到具体邮局手上比如说你要发顺丰你就寄顺丰要发韵达就韵达。你不送给他们咋知道你要干嘛呢对于UDP来说步骤就是用户输入数据然后将数据和套接字类型发给服务端
代码实例
cout please enter; // 提示用户输入
getline(cin, message); // 从标准输入获取一行用户输入的消息并存储到 message 变量中// 1. 数据 2. 给谁发 目标机
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)server, len);5.2.4 查收快递发出信息
当你把快递给了邮局之后你肯定想知道你的快递到哪里在路上了没。也就是要接收邮局给你发来的消息。对于UDP来说就是客户端接收服务端发来的消息
代码实例
struct sockaddr_in temp; // 定义一个 sockaddr_in 结构体用于存储客户端的地址信息
socklen_t len sizeof(temp); // 获取 temp 结构体的大小用于传递给 recvfrom 函数// 调用 recvfrom 接收数据并将发送方的地址信息存储在 temp 中
ssize_t s recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)temp, len);// 检查接收的字节数如果大于 0则表示成功接收到数据
if (s 0) {buffer[s] 0; // 确保接收到的数据是一个以 \0 结尾的有效 C 字符串cout buffer endl; // 输出接收到的消息
}整体代码
#include iostream
#include cstdlib
#include unistd.h
#include strings.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.husing namespace std;void Usage(std::string proc)
{std::cout \n\rUsage: proc serverip serverport\n std::endl;
}// ./udpclient serverip serverport
int main(int argc,char* argv[]) //必须知道服务器的ip和端口号
{if(argc!3){Usage(argv[0]);exit(0);}string serverip argv[1];uint16_t serverport std::stoi(argv[2]);//1、第一步 创建套接字int sockfdsocket(AF_INET, SOCK_DGRAM, 0);if(sockfd0){cout socker error endl;return 1;}//传服务器套接字类型struct sockaddr_in server;//输出型参数bzero(server, sizeof(server));server.sin_family AF_INET;server.sin_port htons(serverport); //?server.sin_addr.s_addr inet_addr(serverip.c_str());socklen_t len sizeof(server);//将数据发给服务端 然后再接受回来string message;char buffer[1024];while(true){coutplease enter;getline(cin,message);// 1. 数据 2. 给谁发 目的机sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)server, len);struct sockaddr_in temp;socklen_t len sizeof(temp);ssize_t s recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)temp, len);//输出型参数//会将服务端的套接字带回来if(s 0){buffer[s] 0;cout buffer endl;}}
}
5.3 主函数 将外部的方法传进去
#includeUdpServer.hpp
#include memory
void Usage(std::string proc)
{std::cout \n\rUsage: proc port[1024]\n std::endl;
}
string Handler(const string str)
{std::string res Server get a message: ;res str;std::cout res std::endl;return res;
}int main(int argc,char*argv[])
{if(argc ! 2){Usage(argv[0]);exit(0);}uint16_t port std::stoi(argv[1]);unique_ptrUdpServer svr(new UdpServer(port));svr-Init();svr-Run(Handler);return 0;
}
六. 关于port和ip 对于port(端口号)来说[0-1023]一般是系统内定的端口号有固定的应用协议使用。普通用户一般绑定1024以上的端口 对于ip来说禁止直接bind公网ip 在虚拟机上可以因为服务端的机器可能会有多个网 卡、多个ip所以我们不能只绑定一个 查看所有活动连接
netstat显示所有当前的网络连接和侦听的端口。
netstat -nulp//-n以数字格式显示地址和端口号避免 DNS 查询。这意味着 IP 地址和端口号将以数字形式显示而不是解析为主机名和服务名。//-l显示正在监听的套接字仅显示正在监听的端口。//-u显示 UDP 连接。如果不加 -unetstat 会默认显示 TCP 连接。//-p显示每个连接或套接字对应的进程 IDPID和进程名称。 七. 总结 通过本文的学习我们深入探讨了网络通信中的一些核心概念和技术包括端口号、TCP 和 UDP 协议、网络字节序、以及 socket 编程接口等内容。了解这些基础概念不仅帮助我们掌握了如何实现网络通信还为我们进一步深入学习更复杂的网络应用和服务打下了坚实的基础。
无论是实现一个简单的 UDP 服务端还是理解如何在不同网络环境下进行数据传输掌握 TCP 和 UDP 的特性及其适用场景都是网络编程中不可或缺的一部分。在实际应用中正确选择协议并理解网络字节序能够大大提高程序的可靠性和效率。
随着网络技术的不断发展网络通信的需求也在不断变化。希望通过这篇文章您能够对网络编程有一个清晰的认识并能够在实际项目中运用所学知识解决更多复杂的网络问题。网络编程是计算机科学中的一项重要技能掌握它将为您开辟更多的技术领域和应用场景帮助您更好地应对未来的挑战。