计算机网络 --- Socket 编程

序言

 在上一篇文章中,我们介绍了 协议,协议就是一种约定,规范了双方通信需要遵循的规则、格式和流程,以确保信息能够被准确地传递、接收和理解。
 在这篇文章中我们将介绍怎么进行跨网络数据传输,在这一过程中相信大家肯定可以加深对协议的理解。


端口号

1. 端口号的作用

 我们已经理解了什么是 IPIP 用于标识互联网上的每个设备。我们可以通过他,将数据包能够从一个设备跨网络传输到另一个设备。但是数据发送到设备上,还需要正确地被处理这才是目的吧!
 举个栗子,大家平时也刷抖音吧!我们所看到的视频就是抖音平台所跨网络传输给我们的数据,但是有没有可能我们手机在刷抖音的同时还有其他程序也正在运行。那么数据是怎么正确地被抖音所接受的而不是其他程序。
 每一个运行的程序以进程的方式存在于内存中,所以抖音肯定也是。所以我们使用唯一的端口来标识内存中需要进行网络传输的进程,当数据到达设备时就会根据端口号选择进程

2. 再识端口号

 端口号存在于传输层协议层:

  • 端口号是一个 2 字节 16 位的整数
  • 端口号用来 标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理
  • IP 地址 + 端口号 能够标识网络上的某一台主机的某一个进程
  • 一个端口号只能被一个进程占用

第三点大家如何理解呢?一个 IP地址 标识的是网络是唯一的设备,而 端口号 标识的是设备中唯一的一个进程,两者一起就是标识 网络上的某一台主机的某一个进程
 所以实际上的网络传输,不就是跨设备跨网络的进程间通信吗?

3. 端口号的需求

 服务端在运行时需要指定一个固定的端口号,这样客户端才能根据你的 IP地址,端口号 来找到需要进行通信的进程。但是端口号也不是随便取的,是有要求的:

  • 0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的
    端口号都是固定的.
  • 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作
    系统从这个范围分配的.

所以说我们只能在 1024 - 65535 进行选择。

 客户端就稍显不同了,客户端就不需要指定一个固定的端口号,这又是为什么呢?对于一个服务器来说,他的设备上只是运行了他的业务程序,而对于我们客户端来说,我们的设备上可能同一时间运行着很多程序。如果每一个客户端都固定一个端口的话,很 可能不同的客户端之间就会造成冲突!所以为了避免这种情况,每次运行时操作系统都会为客户端需要跨网络通信的程序自动分配一个端口


简单认识传输层协议

在传输层有很多协议(不同的传输方式),我们主要简单介绍两种 UDP, TCP ,在这里只是简单介绍,在后面会详细原理。

1. UDP 协议

UDP协议 适用于 实时性要求较高、对数据可靠性 要求较低的应用场景,如音频、视频传输(流媒体)、DNS解析、广播和多播等:

  • UDP 是一种 无连接 的传输层协议,提供面向事务的简单不可靠信息传送服务。
  • 数据以数据报的形式独立发送,不保证数据的可靠性、顺序性和完整性。
  • UDP 协议开销小,传输速度快,适用于对实时性要求较高、对数据可靠性要求较低的应用场景。

2. TCP 协议

TCP协议 适用于对数据完整性、顺序性要求较高的应用场景,如网页浏览(HTTP)、文件传输(FTP)、邮件传输(SMTP)等:

  • TCP 是一种 面向连接的、可靠的、基于字节流 的传输层协议。
  • 在通信双方之间建立一个虚拟的连接,然后在这个连接上进行数据的传输和控制。连接的建立和释放需要经过三次握手和四次挥手的过程。
  • 通过 序号、确认号、重传机制、校验 和等手段,保证数据在传输过程中不会出现丢失、重复、乱序或错误的情况。

3. 总结

 现在大家就需要简单理解为 UDP 是不可靠的,数据传输可能会丢失部分信息,而 TCP 是可靠的,数据传输的完整性高。大家就会觉得,那我以后肯定选后后面的呀,因为他 可靠 嘛!不是这样的,可靠也是需要代价的,需要你有稳定且高速的网络服务!
 协议的选择要看具体的场景!就比如视频的传输就最好选在前者, 所以你看视频的时候偶尔会卡一下,但无关大雅!传文件就选后者,因为你需要你的文件是完好的,文件如果传过来丢失一部分数据那还怎么看!


网络字节序

1. 什么是网络字节序

 大家知道一个概念叫做 大小端 吗?大端机是指将数据的高位存储到内存的低地址,而小端机是指将数据的低位存储到地址的低地址:
在这里插入图片描述

所以很可能你的设备是大端机,而需要接受数据的设备是小端机,为了解决这个问题提出了 网络字节序

  • TCP/IP协议 规定,网络数据流应采用 大端字节序,即低地址高字节.
  • 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送 / 接收数据
  • 如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可

2. 相关接口

 当然这个过程不需要我们自主实现,已经存在相应的接口了:
在这里插入图片描述
我们怎么来方便的记忆呢?h 代表 host(主机)n 代表 network(网络)l 代表 longs 代表 short。我们现在随便选一个来解释,就第三个吧:代表 网络字节序转主机字节序,32位


sockaddr 结构

 该结构体用于定义和存储 IPv4地址 以及相关的端口信息:
在这里插入图片描述

我们主要使用第二个结构体,第三个是用于一个主机上的进程间通信,那第一个是干嘛的呢?这个结构体本身只提供了一个非常基础的框架,不能进行跨网络通信或者是进程间通信,但他在底层 提供一个统一的接口,根据你第一个参数判断你的通信类型。这不就是 C语言 的多态吗?


UDP 网络编程

 在这个版本我们将使用 UDP协议 来进行网络编程,我们将实现一个客户端用于发送信息,服务端用于接收消息:

1. Server 服务端

 首先我们需要指定,启动程序时需要指定 IP 端口 :

int main(int argc, char* argvs[])
{if(argc != 3){std::cout << "Usage: ./server ip port" << std::endl;exit(1);}
}

我们根据相应的内容来初始化 struct sockaddr_in 结构体的相关内容:

// 初始化结构体字段
struct sockaddr_in address;
address.sin_family = AF_INET; // 网络通信
address.sin_addr.s_addr = inet_addr(IP.c_str()); // 将点分十进制的字符串改为长整型 127.0.0.1 => 0x7F000001
address.sin_port = htons(PORT); // 将端口号转化位网络字节序

之后我们创建套接字文件,我们的读取和发送数据都需要经过该文件:

// AF_INET 代表网络通信
// SOCK_DGRAM 代表 UDP 协议
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{perror("socket:");
}

现在我们将套接字结构体和文件绑定,代表该文件服务于指定端口:

// 绑定socket到端口 
int n = bind(sockfd, (struct sockaddr*)(&address), sizeof(address));
if(n < 0)
{perror("bind:");exit(0);
}

接收消息处理函数稍微有点长,但是思路是很简单的:

// 不断地接受客户端的信息
char msg[1024];
struct sockaddr_in clientAddr;
socklen_t len = sizeof(clientAddr);
while(true)
{int n = recvfrom(sockfd, msg, sizeof(msg), 0, (struct sockaddr*)(&clientAddr), &len); // 接收消息if(n < 0){perror("recvfrom:");continue;}else if(n == 0){std::cout << "Client Quit..." << std::endl;exit(0);}msg[n] = '\0';printf("[%s:%s]# %s\n", inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),msg);
}

首先我们定义一个缓冲区 msg 接收返回值,之后通过 recvfrom 接受信息,之所以还需要传入一个 clientAddr 的原因是因为,这是发送方的信息,你总得知道谁发给你打吧?之后我们有三种情况:

  • 接受失败,返回 -1 。我们等待一下发送
  • 客户端退出,返回 0 。我们也退出
  • 成功接收,返回发送的字符数

inet_ntoa 这个该函数是将 IP地址 转化为我们熟悉的字符串形式,ntohs 将网络字节序的端口号转化为主机序列。

2. Client 客户端

 客户端有很多步骤是和服务端类似的,但是整体少简单因为:

  • client 端口不需要用户指定,OS 自动分配
  • client 不需要显示的绑定自己的端口和 IP
  • 在首次向服务器发送信息时,会自动绑定

所以我们直接先通过参数获取服务端的信息,并初始化对应结构体:

 if(argc != 3){std::cout << "Usage: ./server ip port" << std::endl;exit(1);}// 获取 IPstd::string IP = argv[1];// 获取端口号uint16_t PORT = std::stoi(argv[2]);// 初始化结构体字段struct sockaddr_in address;address.sin_family = AF_INET; // 网络通信address.sin_addr.s_addr = inet_addr(IP.c_str()); // 将点分十进制的字符串改为长整型 127.0.0.1 => 0x7F000001address.sin_port = htons(PORT); // 将端口号转化位网络字节序

现在我们还需要创建套接字文件:

 // AF_INET 代表网络通信// SOCK_DGRAM 代表 UDP 协议int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){perror("socket:");}

我们客户端不需要显示绑定端口和 IP,现在可以直接构造发送消息的逻辑:

// 不断地向服务端发送信息
std::string msg;
while(true)
{std::cout << "Please Enter# ";std::cin >> msg;ssize_t n = sendto(sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)(&address), sizeof(address));if(n < 0){perror("sendto:");continue;}
}

3. 总结

 其实很多时候服务端不需要指定 IP地址,因为一个设备可能有很多 IP地址,为了接受来自所有不同地址的请求,我们会设置:
address.sin_addr.s_addr = INADDR_ANY; 这代表接受所有 本设备 IP地址 的请求,处理数据。
 我们在之前提到过, UDP 是一种 无连接 的传输层协议。怎么体现呢?在这里我只是启动客户端程序,不启动服务端,然后发送消息:
在这里插入图片描述
可以看到即使是服务器不在线,我们依然能够发送消息!


TCP 网络编程

 我们将使用 TCP协议 来实现相同的功能:

1. Server 服务端

 前面的步骤都是一样的,接受端口号,初始化结构体字段,但是在创建套接字文件时,就需要更改一下选项了:

// SOCK_STREAM 代表 TCP 协议
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{perror("socket:");
}
std::cout << "Successful create sockfd..." << std::endl;

之后绑定也是一样的,但是我们不能直接使用该套接字进行收发消息!该文件用于监听,查看是否有客户端的请求:

// 监听,第二个参数大家先不管,后面理论会介绍
n = listen(sockfd, 3);
if(n < 0)
{perror("listen:");exit(1);
}
std::cout << "Successful listening..." << std::endl;

监听之后,当有链接请求发送时,我们需要接受该请求,系统会返回一个进行数据读写的文件描述符:

// 连接
struct sockaddr_in ClientAddress; // 用于接收客户端的信息
int newsockfd = accept(sockfd, (struct sockaddr*)(&ClientAddress), sizeof(ClientAddress));
if(newsockfd < 0)
{perror("connect:");exit(1);
}
std::cout << "Successful accept..." << std::endl;

之后便是接受信息哪些步骤:

// 不断地接受客户端的信息
char msg[1024];
while(true)
{int n = read(newsockfd, msg, sizeof(msg));if(n < 0){perror("read");continue;}else if(n == 0){std::cout << "Client Quit..." << std::endl;exit(0);}msg[n] = '\0';printf("[%s:%d]# %s\n", inet_ntoa(ClientAddress.sin_addr),ntohs(ClientAddress.sin_port),msg);
}

2. Client 客户端

 客户端的流程前面都一样,获取 IP地址,端口号,以及初始化结构体字段,创建套接字文件,但是他需要一次连接请求:

// 连接请求
int n = connect(sockfd, (struct sockaddr*)(&address), sizeof(address));
if(n < 0)
{perror("connect:");exit(1);
}
std::cout << "Successful connect..." << std::endl;

当连接成功时,就可以正常的通信了:

// 不断地向服务端发送信息
std::string msg;
while(true)
{std::cout << "Please Enter# ";std::cin >> msg;ssize_t n = send(sockfd, msg.c_str(), msg.size(), 0);if(n < 0){perror("send:");continue;}
}

3. 总结

TCP 是一种 面向连接的、可靠的、基于字节流 的传输层协议。现在我们不启动服务端,直接启动客户端发送消息:
在这里插入图片描述
可以看到直接拒绝我们的连接,和 UDP 截然不同。


总结

 在这篇文章中我们介绍了在实践上如何进行套接字编程,但是我们并没有深入的理解理论的知识,希望大家有所收获!

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

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

相关文章

CSS调整背景

一、设置背景颜色 通过 background-color 属性指定&#xff0c;值可以是十六进制 #ffffff&#xff0c;也可以是rgb(0, 255, 255)&#xff0c;或是颜色名称 "red" div {background-color: red; /* 通过颜色名称设置 */background-color: #ff0000; /* 通过十六进制设…

图像分割(九)—— Mask Transfiner for High-Quality Instance Segmentation

Mask Transfiner for High-Quality Instance Segmentation Abstract1. Intrudouction3. Mask Transfiner3.1. Incoherent Regions3.2. Quadtree for Mask RefinementDetection of Incoherent Regions四叉树的定义与构建四叉树的细化四叉树的传播 3.3. Mask Transfiner Architec…

GreenPlum与PostgreSQL数据库

*** Greenplum*** 是一款开源数据仓库。基于开源的PostgreSQL改造&#xff0c;主要用来处理大规模数据分析任务&#xff0c;相比Hadoop&#xff0c;Greenplum更适合做大数据的存储、计算和分析引擎 它本质上是多个PostgreSQL面向磁盘的数据库实例一起工作形成的一个紧密结合的数…

【数据结构中的哈希】

泛黄的春联还残留在墙上.......................................................................................................... 文章目录 前言 一、【哈希结构的介绍】 1.1【哈希结构的概念】 1.2【哈希冲突】 1.3【哈希函数的设计】 1.4【应对哈希冲突的办法】 一、…

神经网络(一):神经网络入门

文章目录 一、神经网络1.1神经元结构1.2单层神经网络&#xff1a;单层感知机1.3两层神经网络&#xff1a;多层感知机1.4多层神经网络 二、全连接神经网络2.1基本结构2.2激活函数、前向传播、反向传播、损失函数2.2.1激活函数的意义2.2.2前向传播2.2.3损失函数、反向传播2.2.4梯…

数据工程师岗位常见面试问题-1(附回答)

数据工程师已成为科技行业最重要的角色之一&#xff0c;是组织构建数据基础设施的骨干。随着企业越来越依赖数据驱动的决策&#xff0c;对成熟数据工程师的需求会不断上升。如果您正在准备数据工程师面试&#xff0c;那么应该掌握常见的数据工程师面试问题&#xff1a;包括工作…

Spring Cloud Gateway接入WebSocket:实现实时通信

在现代的微服务架构中&#xff0c;实时通信变得越来越重要。Spring Cloud Gateway作为Spring Cloud生态中的API网关&#xff0c;提供了动态路由、监控、弹性、安全等功能。本文将介绍如何通过Spring Cloud Gateway接入WebSocket&#xff0c;实现服务之间的实时通信。 为什么需…

Spring异常处理-@ExceptionHandler-@ControllerAdvice-全局异常处理

文章目录 ResponseBodyControllerAdvice最终的异常处理方式 异常的处理分两类 编程式处理&#xff1a;也就是我们的try-catch 声明式处理&#xff1a;使用注解处理 ResponseBody /*** 测试声明式异常处理*/ RestController public class HelloController {//编程式的异常处理&a…

Mitsuba 渲染基础

Mitsuba 渲染基础 0. Abstract1. 安装 Mitsuba21.1 下载 Mitsuba2 源码1.2 选择后端 (variants)1.3 编译 2. [Mitsuba2PointCloudRenderer](https://github.com/tolgabirdal/Mitsuba2PointCloudRenderer)2.1 Mitsuba2 渲染 XML2.2 Scene 场景的 XML 文件格式2.2.1 chair.npy to…

设计模式之装饰模式(Decorator)

前言 这个模式带给我们有关组合跟继承非常多的思考 定义 “单一职责” 模式。动态&#xff08;组合&#xff09;的给一个对象增加一些额外的职责。就增加功能而言&#xff0c;Decorator模式比生成子类&#xff08;继承&#xff09;更为灵活&#xff08;消除重复代码 & 减少…

JavaWeb招聘信息管理系统

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 spring-mybatis.xml3.5 spring-mvc.xml3.5 login.jsp 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优…

利用Langchain开发框架研发智能体Agent的过程,以及相关应用场景

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下本文主要介绍了利用langchain开发智能体agent的过程。文章首先阐述了项目背景&#xff0c;随后通过给出样例代码&#xff0c;详细展示了执行过程。此外&#xff0c;本文还探讨了该智能体agent在实际应用场景中的运用…

Excel根据一个值匹配一行数据

根据一个值从一个表中匹配一行数据&#xff0c;例如从左边的表中找到指定姓名的所有行数据 使用VLOOKUP函数&#xff0c;参数&#xff1a; Lookup_value&#xff1a;需要搜索的值&#xff0c;单个值 Table_array&#xff1a;被搜索的区域&#xff0c;是个表 Col_index_num&…

【Python基础(一)】

学习分享 一、基本语法1、输出print语句2、常量的写法3、运算符 (/) 与(//)4、字符串5、列表5.1、列表查询元素是否存在5.2、列表查询元素是否存在5.3、身份运算符5.4、列表的增删改查 6、元组6.1、tuple() 7、字典8、函数8.1、值传递8.2、引用传递8.3、函数的传参 二、文件的操…

AWS Network Firewall -NAT网关配置只应许白名单域名出入站

1. 创建防火墙 选择防火墙的归属子网(选择公有子网) 2. 创建规则白名单域名放行 3. 绑定相关规则

Spring JDBC及声明式事务

目录 Spring JDBC基础概念 Spring声明式事务 事务传播方式 Spring JDBC基础概念 Spring JDBC 封装了原生的JDBC API&#xff0c;使得处理关系型数据库更加简单。Spring JDBC的核心是JdbcTemplate&#xff0c;里面封装了大量数据库CRUD的操作。使用Spring JDBC…

[uni-app]小兔鲜-02项目首页

轮播图 轮播图组件需要在首页和分类页使用, 封装成通用组件 准备轮播图组件 <script setup lang"ts"> import type { BannerItem } from /types/home import { ref } from vue // 父组件的数据 defineProps<{list: BannerItem[] }>()// 高亮下标 const…

影响6个时序Baselines模型的代码Bug

前言 我是从去年年底开始入门时间序列研究&#xff0c;但直到最近我读FITS这篇文章的代码时&#xff0c;才发现从去年12月25号就有人发现了数个时间序列Baseline的代码Bug。如果你已经知道这个Bug了&#xff0c;那可以忽略本文&#xff5e; 这个错误最初在Informer&#xff0…

安科瑞Acrel-1000DP分布式光伏监控系统在鄂尔多斯市鄂托克旗巴音乌苏六保煤矿5MW分布式光伏项目中的应用

安科瑞 华楠 摘 要&#xff1a;分布式光伏发电就是将太阳能光伏板分散布置在各个区域&#xff0c;通过小规模、模块化的方式实现电能的并网或独立使用&#xff0c;这种发电方式具有就近发电、就近并网、就近转换、就近使用的特点。近年来&#xff0c;技术进步和政策支持推动了光…

Python在AI中的应用--使用决策树进行文本分类

Python在AI中的应用--使用决策树进行文本分类 文本分类决策树什么是决策树 scikit算法 使用scikit的决策树进行文章分类一个文本分类的Python代码使用的scikit APIs说明装入数据集决策树算法类类构造器&#xff1a; 构造决策树分类器产生输出评估输出结果分类准确度分类文字评估…