计算机网络 --- Socket 编程
序言
在上一篇文章中,我们介绍了 协议,协议就是一种约定,规范了双方通信需要遵循的规则、格式和流程,以确保信息能够被准确地传递、接收和理解。
在这篇文章中我们将介绍怎么进行跨网络数据传输,在这一过程中相信大家肯定可以加深对协议的理解。
端口号
1. 端口号的作用
我们已经理解了什么是 IP
,IP
用于标识互联网上的每个设备。我们可以通过他,将数据包能够从一个设备跨网络传输到另一个设备。但是数据发送到设备上,还需要正确地被处理这才是目的吧!
举个栗子,大家平时也刷抖音吧!我们所看到的视频就是抖音平台所跨网络传输给我们的数据,但是有没有可能我们手机在刷抖音的同时还有其他程序也正在运行。那么数据是怎么正确地被抖音所接受的而不是其他程序。
每一个运行的程序以进程的方式存在于内存中,所以抖音肯定也是。所以我们使用唯一的端口来标识内存中需要进行网络传输的进程,当数据到达设备时就会根据端口号选择进程
。
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
代表 long
,s
代表 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
截然不同。
总结
在这篇文章中我们介绍了在实践上如何进行套接字编程,但是我们并没有深入的理解理论的知识,希望大家有所收获!
相关文章:

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

git笔记之在多个分支中复用某个分支提交的更改
git笔记之在多个分支中复用某个分支提交的更改 code review! 文章目录 git笔记之在多个分支中复用某个分支提交的更改1.实现该功能的 Bash 脚本示例2.这个脚本是否可以处理新添加的文件?3.该脚本使用前,应先使用下述脚本重置本地仓库所有分支与远程保持一…...

HTML、CSS
初识web前端 web标准 Web标准也称为网页标准,由一系列的标准组成,大部分由W3C (World Wide Web Consortium,万维网联盟) 负责制定。三个组成部分: HTML: 负责网页的结构(页面元素和内容)。CSS: 负责网页的表现(页面元素的外观、位置等页面样…...
数据文件(0)
一、使用场景 1、字典数据 对于一些数据量不大的配置类数据,放到数据库中占用数据库资源,可以放到代码中维护。比如 (1)字段少业务单一:做成枚举; (2)字段多业务复杂:…...
Go语言并发模式详解:深入理解管道与上下文的高级用法
解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 在Go语言中,并发编程是其最强大的特性之一。合理地使用并发模式,可以让我们的程序高效而优雅地处理复杂的任务。在本文中,我们将深入探讨Go语言中的一些高级并发模式,包括管道的技巧和上下文包的应用。通过丰…...

标准文档流解析及 CSS 中的相关特性
目录 非 VIP 用户可前往公众号回复“css”进行免费阅读 标准文档流特点 空白折叠现象 高矮不齐、底边对齐 自动换行,一行写不满,换行写 标准文档流中的元素等级 HTML 与 CSS 中的标签分类总结 块级元素和行内元素的相互转换 块级转行内 行内转块级 display 非 VIP…...
水下攻防面试题
水下攻防面试题通常涉及对水下环境的理解、水下安全操作、水下技术应用以及攻防策略等多个方面。由于具体的面试题可能因组织、职位和目的的不同而有所差异,以下是一些可能出现在水下攻防面试中的典型问题及其参考答案框架: 一、基础概念与理解 什么是水下攻防? 水下攻防是…...

vmware 虚拟机多屏幕或添加屏幕
vmware 虚拟机多屏幕或添加屏幕 前置条件 vmware 安装 vmware tools 虚拟机系统支持多屏幕 物理上有至少两个屏幕,就是物理机上接至少一个屏幕 方法 虚拟机上点设置,需要在虚拟机关机时进行 ctrl alt enter 让当前虚拟机全屏 鼠标移动到屏幕虚拟机…...
鹏哥C语言49-51---第6次作业:循环语句 for 和 while
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> //-----------------------------------------------------------------------------------------------第六次作业:for循环等 //--------------------------------------------------------------------…...

springboot中药材进存销管理系统
基于springbootvue实现的中药材进存销管理系统 (源码L文ppt)4-079 4 系统总体设计 4.1系统功能结构设计图 根据需求说明设计系统各功能模块。采用模块化设计方法实现一个复杂结构进行简化,分成一个个小的容易解决的板块,然…...

GitHub上图像超分开源项目推荐【持续更新】
SRCNN 介绍:SRCNN(Super-Resolution Convolutional Neural Network)是一种用于图像超分辨率的卷积神经网络。它由Dong等人在2014年提出,是早期的深度学习方法之一,用于提高图像的分辨率。SRCNN通过学习低分辨率&#…...

浅谈软件测试的基础知识(1)
文章目录 一、什么是测试1.1、生活中的测试案例1.2、为什么需要进行软件测试 二、测试和开发的区别2.1、调试和测试的区别 四、测试人员需具备哪些素质五、软件的生命周期六、软件测试的生命周期七、设计测试用例的方法[!]7.1、什么是测试用例7.2、测试用例作用 八、走测试岗位…...

Mac 上哪个剪切板增强工具比较好用? 好用剪切板工具推荐
在日常文字编辑中,我们经常需要重复使用复制的内容。然而,新内容一旦复制,旧内容就会被覆盖。因此,选择一款易用高效的剪贴板工具成为了许多人的需求。本文整理了一些适用于 macOS 系统的优秀剪贴板增强工具,欢迎大家下…...

基于opencv的车牌检测和识别系统(代码+教程)
车牌检测与识别技术详解 车牌检测和识别(License Plate Recognition, LPR)是一项重要的计算机视觉任务,它在交通管理、安全监控以及智能门禁系统等多个领域都有着广泛的应用。随着深度学习技术的发展,LPR系统的准确性和鲁棒性得到…...

list(二) (list模拟实现)
首先进行大框架 先写基本的结点类 有data next prev template<class T>class ListNode//或者使用struct 就不用在写public声明公有{public://这里不仅仅是成员函数 成员变量也要公有化 ListNode<T>* _next;ListNode<T>* _prev;T _data;}之后是链表list类…...

[Linux]从零开始的泰山派系统安装与远程教程
一、前言 泰山派买回来也有一阵子了,最近慢慢开始研究。当然,学习这种Linux的开发板的第一步就是安装系统,对于RK系列的芯片系统安装有专门的软件,所有在系统安装方面比较简单。更多的还是我们应该怎么去编译系统,这一…...
Python国产新 ORM 框架 fastzdp_sqlmodel 快速入门教程
创建模型 from typing import Optional from sqlmodel import Field, SQLModel import fastzdp_sqlmodel as fasmclass Hero(SQLModel, tableTrue):id: Optional[int] Field(defaultNone, primary_keyTrue)name: strsecret_name: strage: Optional[int] None创建表 from ty…...
面试速通宝典——3
51. 野指针和内存泄漏是什么?如何避免? 内存泄漏:是指程序中以动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。 避免&…...
每天一个数据分析题(四百七十三)- 元数据
下列哪些元素属于元数据内容? () A. 名称 B. 长度 C. 类型 D. 取值范围 数据分析认证考试介绍:点击进入 题目来源于CDA模拟题库 点击此处获取答案 数据分析专项练习题库 内容涵盖Python,SQL,统计学…...
产品经理面试整理-练习常见面试问题
练习常见面试问题是准备产品经理面试的重要环节。掌握这些问题的回答思路,不仅能帮助你在面试中更加自信,还能展示你对产品管理的深入理解。以下是一些常见的产品经理面试问题,以及如何高效准备这些问题的指南。 1. 常见面试问题及回答思路 1.1 你如何定义产品成功? ●...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...

Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...