【网络】Socket套接字

目录

一、端口号

二、初识TCP/UDP协议

三、网络字节序

3.1 概念

3.2 常用API

四、Socket套接字

4.1 概念

4.2 常用API

(1)socket

(2)bind

sockaddr结构

(3)listen

(4)accept

(5)connect

(6)recvfrom

(7)sendto

4.3 地址转换函数

(1)inet_aton

(2)inet_addr

(3)inet_pton


一、端口号

网络协议栈中的下三层,主要解决的是如何将数据安全可靠的送到远端机器上的问题。

在上层,用户使用特定的应用层软件完成数据的发送和接收。而软件在启动后变为了进程,因此我们日常网络通信的本质,就是进程间通信!

问题1:一个进程通过网络将数据传输到远端主机上时,如何区分要把数据传给主机上的哪个进程呢?

实际上,每个进程都有属于自己的端口号(port)。IP地址用于标识主机的唯一性,而端口号则用于标识一个进程在该主机中的唯一性。所以网络通信中我们不止需要IP地址来找到目标主机,还需要端口号找到目标进程

端口号是传输层协议的内容,是一个2字节16位的整数,用于在主机中标识一个进程的唯一性。因此通过IP地址+端口号就能标识全网唯一的一个进程

问题2:进程PID也可以标识进程在主机中的唯一性,为什么还要有端口号?

  • 不是所有的进程都需要进行网络通信,但是所有进程都要有自己的PID
  • 将系统和网络功能解耦

一个端口号只能被一个进程绑定,但一个进程可以绑定多个端口号


二、初识TCP/UDP协议

传输层协议(TCP和UDP)的数据段中分别记录了源端口号和目的端口号,用来描述数据是从哪个进程发的、要发给哪个进程

关于TCP和UDP协议,我们首先对它们有一个简单且直观的认识,后续再进行深入了解

TCP(Transmission Control Protocol,传输控制协议):

  • 面向连接
  • 保证数据传输可靠性
  • 面向字节流

UDP(User Datagram Protocol,用户数据报协议):

  • 无连接
  • 不保证数据传输可靠性
  • 面向数据报


三、网络字节序

3.1 概念

内存中的多字节数据相对于内存地址而言有大端和小端的区别,因此主机也分为大端机和小端机

让我们回顾一下大端和小端的概念 

大端:数据的高位存储在内存的低位

小端:数据的高位存储在内存的高位

不止是内存,网络数据流中同样有大端小端之分。发送方在发送数据时通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收方将数据保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。

问题在于,不同类型的主机在跨网络互相传输数据时就可能导致问题。例如大端机将数据发送给小端机,就可能导致数据的错乱

因此TCP/IP协议规定,发送到网络中的数据流应统一按照大端字节序发送。也就是说不论是大端机还是小端机,都要按照TCP/IP规定的网络字节序来发送或接收数据

所以如果发送数据的主机是小端机,必须先将数据转换成大端字节序后再发送。在后面调用套接字相关API时,我们也通常需要对端口号和ip地址进行网络字节序转换。

3.2 常用API

为了让网络程序具有可移植性,我们可以使用下列库函数进行主机字节序和网络字节序的转换

#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);

其中h表示host,n表示network,l表示32位长整型,s表示16位短整型

例如htonl就是将32位长整型从主机字节序转为网络字节序,适用于转换IP地址

如果主机字节序本身是小端,调用对应库函数后则会将参数做相应大小端转换后返回;如果主机字节序已经是大端了,则不作改变


四、Socket套接字

4.1 概念

套接字(Socket)是一种独立于协议的网络编程接口,是对网络中不同主机的应用进程之间进行双向通信的端点的抽象。套接字上联应用进程,下接网络协议栈,是应用程序与网络协议栈进行交互的接口。

套接字包括 IP 地址和端口号两个部分,可以用来区分不同的进程之间的数据传输。传输层使用的协议不同,套接字的种类也会发生相应的改变。

在Linux中,套接字的本质也是文件,因此有对应的网络文件描述符,用户通过网络文件描述符对套接字进行操作。

4.2 常用API

(1)socket

#include <sys/types.h>
#include <sys/socket.h>int socket(int domain, int type, int protocol);

socket函数类似于打开文件的操作,会创建套接字并返回一个网络文件描述符,其中:

  • domain:协议域,又称协议族,例如AF_INET代表IPv4协议,AF_INET6代表IPv6协议
  • type:指定socket类型,例如流式套接字SOCK_STREAM(TCP)和数据报套接字SOCK_DGRAM(UDP)
  • protocal:指定协议信息,常见的有IPPROTO_TCP、IPPROTO_UDP等,通常设置为0代表自动选择套接字类型对应的默认协议

创建成功返回一个网络文件描述符,失败返回-1并设置环境变量errno

例如:

(2)bind

#include <sys/types.h>
#include <sys/socket.h>int bind(int socket, const struct sockaddr *address, socklen_t address_len);

bind函数用于将一个服务的ip地址和端口号绑定到一个套接字上,一般是服务端在绑定监听套接字时会用到。客户端则不必要调用bind绑定,因为客户端的端口号由内核自动分配

其中:

  • socket:待绑定的网络文件描述符
  • address:指向一个sockaddr结构体的指针,该结构体包含了要绑定的ip地址和端口号
  • address_len:address指向的结构体大小

成功绑定返回0, 失败返回-1并设置errno

例如:

uint16_t port = 8888; //端口号
string ip = "127.0.0.1"; //字符串格式的ip地址
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
if (sockfd < 0)
{// 创建套接字失败时//...
}
//填充结构体字段
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET; //IPv4协议
local.sin_port = htons(port);
inet_aton(ip.c_str(), &(local.sin_addr)); 
if (bind(sockfd, (struct sockaddr *)&local, sizeof(local)) < 0) // 绑定
{//绑定失败时//...
}

填充结构体字段时,需要对端口号进行网络字节序转换和对字符串格式的ip地址转四字节ip地址后再填充到sockaddr_in结构体中

关于ip地址的格式转换函数会在后面提及,这里先简单提一下sockaddr的结构

sockaddr结构

关于socket的API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6等,但是各种网络协议的地址格式并不相同。

例如IPv4的地址用sockaddr_in结构体表示,其中包含16位地址类型、16位端口号和32位ip地址

不同的结构体中,前16位都填充了ip地址的协议类型,因此我们可以统一用struct sockaddr*类型接收,取得结构体首地址后按位数获取地址类型字段就可以确定是哪一种结构体了。

在使用Unix域套接字进行本机进程间通信时,绑定时就得使用sockaddr_un结构

(3)listen

#include <sys/types.h> 
#include <sys/socket.h>int listen(int sockfd, int backlog);

listen函数常用于服务端监听来自客户端的TCP连接请求,通常在调用bind函数后使用,成功返回0,失败返回-1并设置errno

其中:

  • sockfd:将被设置为监听状态的网络文件描述符
  • backlog:设置全连接队列的长度(全连接队列用于临时维护未被上层accept的已经建立好的连接,长度为backlog+1)

例如:

uint16_t port = 8888; //端口号
string ip = "127.0.0.1"; //字符串格式的ip地址
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
if (sockfd < 0)
{// 创建套接字失败时//...
}
//填充结构体字段
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET; //IPv4协议
local.sin_port = htons(port);
inet_aton(ip.c_str(), &(local.sin_addr)); 
if (bind(sockfd, (struct sockaddr *)&local, sizeof(local)) < 0) // 绑定
{//绑定失败时//...
}
if (listen(sockfd, 10) < 0) // 将套接字设置为监听状态,全连接队列最多存放10+1个连接
{//监听失败时//...
}

(4)accept

#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数常用于服务端从全连接队列中接收来自客户端的TCP连接请求并创建一个新的套接字,通常用于listen函数后。成功会返回该套接字的文件描述符用来负责后续的数据通信服务,失败返回-1并设置errno。

如果全连接队列中暂时没有Tcp连接请求,accept函数将阻塞等待直到有客户端发起连接请求(除非服务器处于非阻塞状态)

其中:

  • sockfd:被绑定并设置为监听状态的套接字对应的文件描述符
  • addr:指向sockaddr结构体的指针,用于填充客户端对应的地址信息。设置为NULL表示不关心客户端地址
  • addrlen:指向socklen_t的指针,表示addr的大小

例如:

uint16_t port = 8888; //端口号
string ip = "127.0.0.1"; //字符串格式的ip地址
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
if (sockfd < 0)
{// 创建套接字失败时//...
}
//填充结构体字段
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET; //IPv4协议
local.sin_port = htons(port);
inet_aton(ip.c_str(), &(local.sin_addr)); 
if (bind(sockfd, (struct sockaddr *)&local, sizeof(local)) < 0) // 绑定
{// 绑定失败时//...
}
if (listen(sockfd, 10) < 0) // 将套接字设置为监听状态,全连接队列最多存放10+1个连接
{// 监听失败时//...
}
struct sockaddr_in client; // 存储客户端信息的结构体
socklen_t len = sizeof(client);
int newfd = accept(sockfd, (struct sockaddr *)&client, &len); // sockfd只负责获取连接,newfd负责后续的数据通信服务
if (newfd < 0)
{// 接收失败时//...
}

(5)connect

#include <sys/types.h>
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect函数常用于发起建立网络连接的请求,成功返回0,失败返回-1并设置errno

其中:

  • sockfd:调用socket函数创建套接字成功后返回的文件描述符
  • addr:指向sockaddr结构体的指针,其中包含了准备建立连接的目标服务器地址信息
  • addrlen:addr指向的结构体的大小

例如:

string serverip = "127.0.0.1";
uint16_t serverport = 8888;
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
if (sockfd < 0)
{// 创建套接字失败时//...
}
// 填充结构体字段
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));
// 发起连接
int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
if (n < 0)
{// 连接发起失败时//...
}

(6)recvfrom

#include <sys/types.h>
#include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

recvfrom常用于使用UDP协议(或其他无连接的数据报服务)时从套接字中读取数据,成功返回读取到的字节数,当套接字已经关闭时返回0,出错返回-1并设置errno

其中:

  • sockfd:已打开的套接字文件描述符
  • buf:指向用于存放接收到的数据的缓冲区的指针
  • len:缓冲区大小
  • flags:控制接收行为的标志,通常设置为0表示阻塞模式
  • src_addr:指向一个sockaddr结构体,存储数据来源方的地址信息
  • addrlen:代表sockaddr结构体的大小

例如:

int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字
if (sockfd < 0)
{//...
}
char buffer[1024];
sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t s = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0,(struct sockaddr *)&temp, &len); // 接收服务端返回的消息
//...

(7)sendto

#include <sys/types.h>
#include <sys/socket.h>ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

sendto函数常用于使用UDP协议时通过指定的socket将数据发送到目标主机,成功返回实际发送的字节数,失败返回-1并设置errno

其中:

  • sockfd:已打开的套接字文件描述符
  • buf:指向要发送的数据
  • len:要发送的数据长度
  • flags:标志位,通常设置为0
  • dest_addr:指向存储目标主机地址信息的sockaddr结构体
  • addrlen:结构体大小

4.3 地址转换函数

sockaddr_in结构体中的成员sin_addr表示32位的ip地址,但我们日常中见到的ip地址通常是点分十进制格式的字符串表示的。通过一些函数可以实现ip地址在两种格式间的转换。

字符串转32位ip地址:

(1)inet_aton

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int inet_aton(const char *cp, struct in_addr *inp);

其中:

  • cp:待转换的点分十进制ip地址字符串
  • inp:指向in_addr结构体的指针,存储转换后的网络字节序ip地址

in_addr内部存放了一个32位整型用于存储转换后的ip地址,其结构如下:

typedef uint32_t in_addr_t;
struct in_addr
{in_addr_t s_addr;
};

例如:

struct sockaddr_in addr;
inet_aton("127.0.0.1", &addr.sin_addr);

 

(2)inet_addr

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>in_addr_t inet_addr(const char *cp);

其中cp是待转换的点分十进制ip地址字符串

例如:

struct sockaddr_in addr;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");

(3)inet_pton

#include <arpa/inet.h>int inet_pton(int af, const char *src, void *dst);

其中:

  • af:协议族
  • src:指向点分十进制ip地址字符串的指针
  • dst:指向用于存储转换后ip地址的内存区域

网络字节序ip地址转点分十进制的函数有inet_ntoa、inet_ntop,有兴趣的可以自行查阅文档

完.

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

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

相关文章

内联函数/函数重载/函数参数缺省

一、内联函数 为了减少函数调用的开销 在函数定义前加“inline”关键字&#xff0c;即可定义内联函数 二、函数重载 1.名字相同 2.参数个数或者参数类型不同 编译器根据调用语句实参的个数和类型判断应该调用哪个函数 三、函数的缺省参数 定义函数的时候可以让最右边的连…

基于神经网络的文本分类的设计与实现

标题:基于神经网络的文本分类的设计与实现 内容:1.摘要 在信息爆炸的时代&#xff0c;大量文本数据的分类处理变得至关重要。本文旨在设计并实现一种基于神经网络的文本分类系统。通过构建合适的神经网络模型&#xff0c;采用公开的文本数据集进行训练和测试。在实验中&#x…

Baklib内容中台的核心定位是什么?

构建企业级知识中枢 在数字化转型趋势下&#xff0c;Baklib内容中台通过构建企业级知识中枢&#xff0c;实现了从碎片化信息到体系化资产的跃迁。其核心能力体现为对多源内容的智能聚合与结构化存储&#xff0c;支持从文档、图片到视频的全格式整合&#xff0c;并通过语义标签…

蓝耘平台API深度剖析:如何高效实现AI应用联动

目录 一、蓝耘平台简介 1.1 蓝耘通义大模型 1.2 蓝耘云计算资源 1.3 蓝耘API与微服务 二、 蓝耘平台应用联动场景 2.1 数据采集与预处理联动 2.2 模型推理与后端服务联动 2.3 跨平台联动 三、蓝耘平台注册体验功能 3.1 注册 3.2 体验蓝耘MaaS平台如何使用海螺AI生成视频…

《大语言模型赋能证券业开发安全:海云安技术方案在上交所专刊发表》

近日&#xff0c;海云安《大语言模型在证券业开发安全领域的探索与实践》技术方案经过上海证券交易所&#xff08;以下简称”上交所“&#xff09;行业专家评审后正式收录于《交易技术前沿——网络安全专刊&#xff08;2025年第1期 总第61期&#xff09;》。 证券信息技术研究…

第三课:Stable Diffusion图生图入门及应用

文章目录 Part01 图生图原理Part02 图生图基本流程Part03 随机种子作用解析Part04 图生图的拓展应用 Part01 图生图原理 当提示词不能足够表达用户需求的时候&#xff0c;加入图片能让AI更好的理解你的想法图片上的像素信息会在加噪和去噪的过程中&#xff0c;作为一种特征反映…

将网络安全和第三方风险管理与业务目标相结合

在网络安全风险领域&#xff0c;我们经常遇到与企业语言不通的问题。这可能导致网络安全风险管理计划得不到支持。当发现网络安全风险时&#xff0c;困难在于以符合组织语言和目标的方式来表达它。 第三方风险属于另一个灰色地带。在组织内部&#xff0c;许多利益相关者&#…

使用Github项目nghttp3的样例学习HTTP/3

文章目录 前言一、HTTP3测试 in Ubuntu1.1. 基本软件1.2. gcc/g1.2.1. Ubuntu221.2.2. Ubuntu201.2.2.1. 必备库1.2.2.1.1. gmp1.2.2.1.2. mpfr1.2.2.1.3. mpc 1.2.2.2. 安装 1.3. libev > 4.11&#xff08;备用&#xff09;1.3.1. 安装1.3.2. 测试 1.4. nghttp31.5. ngtcp2…

uniapp 在app上 字体如何不跟着系统字体大小变

在UniApp开发中&#xff0c;默认情况下App的字体可能会跟随系统字体设置而变化。如果你希望保持固定的字体样式&#xff0c;不随系统字体设置改变&#xff0c;可以采用以下几种方法&#xff1a; 方法一&#xff1a;全局CSS设置 在App.vue的样式中添加以下CSS&#xff1a; /*…

跨域问题的解决方案

一、跨域问题的本质 1.1 同源策略的三要素 浏览器的同源策略&#xff08;Same-Origin Policy&#xff09;要求请求的 协议、域名、端口 完全一致&#xff0c;否则视为跨域&#xff1a; 协议不同&#xff1a;http 与 https域名不同&#xff1a;a.com 与 b.com端口不同&#x…

Linux 上使用 Docker 部署 Kafka 集群

在 Linux 上使用 Docker 部署 Kafka 集群的步骤如下 1. 准备工作 确保已安装&#xff1a; Docker Docker Compose 2. 创建 Docker Compose 文件 (docker-compose.yml) version: 3.8services:zookeeper:image: wurstmeister/zookeepercontainer_name: zookeeperports:- &quo…

【性能优化点滴】odygrd/quill 中一个简单的标记位作用--降低 IO 次数

在 StreamSink 类中&#xff0c;成员变量 _write_occurred 的作用是 跟踪自上次刷新&#xff08;Flush&#xff09;以来是否有写入操作发生&#xff0c;其核心目的是 优化 I/O 性能。以下是详细解析&#xff1a; _write_occurred 的作用 1. 避免不必要的刷新&#xff08;Flush…

Ubuntu Linux安装PyQt5并配置Qt Designer

一 安装 PyQt5 借助 apt 包管理器来安装 PyQt5 及其相关的开发工具&#xff1a; sudo apt install python3-pyqt5 pyqt5-dev-tools 假如报错&#xff0c; You might want to run apt --fix-broken install to correct these. 直接执行&#xff1a; sudo apt --fix-…

2025清华大学:DeepSeek教程全集(PDF+视频精讲,共10份).zip

一、资料列表 第一课&#xff1a;Deepseek基础入门 第二课&#xff1a;DeepSeek赋能职场 第三课&#xff1a;普通人如何抓住DeepSeek红利 第四课&#xff1a;让科研像聊天一样简单 第五课&#xff1a;DeepSeek与AI幻觉 第六课&#xff1a;基于DeepSeek的AI音乐词曲的创造法 第…

容器C++

string容器 string构造函数 #include<iostream> using namespace std; #include<string.h> void test01() {string s1;//默认构造const char* str "hello world";string s2(str);//传入char*cout << "s2" << s2 << endl;s…

【2.项目管理】2.4 Gannt图【甘特图】

甘特图&#xff08;Gantt&#xff09;深度解析与实践指南 &#x1f4ca; 一、甘特图基础模板 项目进度表示例 工作编号工作名称持续时间(月)项目进度&#xff08;周&#xff09;1需求分析3▓▓▓░░░░░░░2设计建模3░▓▓▓░░░░░░3编码开发3.5░░░▓▓▓▓░░…

C++List模拟实现|细节|难点|易错点|全面解析|类型转换|

目录 1.模拟代码全部 2.四大块代码理解 1.最底层&#xff1a;ListNode部分 2.第二层&#xff1a;ListIterator部分 3.第三层&#xff1a;ReserveListIterator部分 4最终层&#xff1a;List 1.模拟代码全部 using namespace std; template<class T> struct ListNode …

【深度学习与实战】2.1、线性回归模型与梯度下降法先导

import numpy as np# 数据准备 X np.array([1, 2, 3]) y np.array([3, 5, 7])# 参数初始化 w0, w1 0, 0 alpha 0.1 n len(X)# 迭代10次 for epoch in range(10):# 计算预测值y_pred w1 * X w0# 计算梯度grad_w0 (1/n) * np.sum(y_pred - y)grad_w1 (1/n) * np.sum((y_…

锐捷EWEB路由器 timeout.php任意文件上传漏洞代码审计(DVB-2025-9003)

免责声明 仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 一:产品介绍 锐捷EWEB路由器是锐…

flask开发中设置Flask SQLAlchemy 的 db.Column 只存储非负整数(即 0 或正整数)

如果你想控制一个 Flask SQLAlchemy 的 db.Column 只存储非负整数&#xff08;即 0 或正整数&#xff09;&#xff0c;你可以在模型中使用验证来确保这一点。一种常见的方法是使用模型的 validate 方法或者在执行插入或更新操作时进行检查。 以下是实现这一目标的几种方法&…