【Linux网络】网络套接字编程

news2025/7/13 12:43:48

套接字编程

  • 一,理解端口号
  • 二,初识TCP/UDP协议
  • 三,网络字节序
  • 四,UDP套接字编程常用API
    • 4.1 struct sockaddr类型
    • 4.2 socket接口
    • 4.3 bind接口
    • 4.4 recvfrom
    • 4.5 sendto
  • 五,TCP套接字常用API
    • 5.1 listen接口
    • 5.2 accept接口
    • 5.3 connect接口
  • 六,补充
    • 6.1 心跳机制
    • 6.2 守护进程
    • 6.3 tcp底层发送细节
  • 七,总结

一,理解端口号

我们网络通信中,实际上是两台主机上的进程之间的通信

对通信双方而言:

  1. 要先能够把数据交给对方的机器上 (通过 ip 地址来找)
  2. 找到指定的进程 (通过 port:端口号 来找到)

端口号用来标识网络通信中指定机器中的一个进程的唯一性

所以(ip,port)用来标识互联网中进程的唯一性
ip + port 就是套接字socket


网络进程要和port进行绑定,但是进程pid也可以标识一个进程,为什么还要port来标识唯一性?

这是因为:

  • 其他模块(进程管理) 要和 网络 进行解耦
  • port是专门用来网络通信的

二,初识TCP/UDP协议

为了理解下面套接字编程,我们先来简单了解一下传输层的两个协议

tcp协议有下面几个特点:

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

udp有下面几个特点:

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

tcp是可靠传输,需要先建立连接,面向字节流等,udp是不可靠传输,适用于对数据可靠性不高的场景,比如视频传输,直播等等。

可靠不可靠体现在面对异常情况时,tcp会进行可靠性处理,比如:丢包重传,乱序后排序等。而udp不做处理,tcp保证了可靠性,所以它更加复杂,而udp相比更简单。

三,网络字节序

这里我们再看一下网络字节序,先来看这样一个例子
两个主机要进行网络通信,那么就要进行收发数据,而不同的机器有不同的存储策略,大端或者小端

而且还要知道:

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址

在这里插入图片描述

所以到达网络的数据,都必须是大端,这样所有主机收到数据时也都知道是大端

最后在使用时,转换为自己对应的存储模式即可


系统也会我们提供了网络字节序和主机序列的转换系统调用
在这里插入图片描述
很好理解,h表示host,也就是主机,n表示net网络,后面的s和l表示short或者long

四,UDP套接字编程常用API

常用的API有如下:

在这里插入图片描述

4.1 struct sockaddr类型

这里的接口都有一个 struct sockaddr的参数。这里我们说一下这个结构。
首先,socket有很多种类:

  1. unix socket,域间 socket ,用于本主机之间的通信,和命名管道类似
  2. 网络socket, 采用 ip + port
  3. 原始socket, 类似于在应用层绕过udp,tcp来进行网络通信,一般用来编写网络工具,不以传输数据为目的

这些不同的socket用的都是一套接口

在这里插入图片描述

struct sockaddr是一个通用的地址类型,struct sockaddr_in 和 struct sockaddr_un 传入套接字相关接口时,将他们强转为 struct sockaddr 类型,底层取出它们的前两个字节,也就是16位地址类型,判断这个具体是什么类型

所以这就是C风格的多态,struct sockaddr作为基类,让不同的套接字去基础,以达到实现不同的套接字可以用一套接口进行通信

4.2 socket接口

创建套接字,本质也是创建一个文件,所以对网络的操作就像对文件的操作

int socket(int domain, int type, int protocol);
  • 返回值是个文件描述符,Linux下一切皆文件,打开网卡也就相当于文件操作
  • 第一个参数表示设置的通信方式,域间套接还是网络通信,表示哪个域:比如 AF_INET 表示创建一个网络通信的文件
    在这里插入图片描述
  • 第二个参数表示套接字类型: SOCK_STREAM 表示流式套接,用于tcp,SOCK_DGRAM 用于udp
    在这里插入图片描述
  • 第三个表示用tcp还是udp,但是前两个固定好后,第三个缺省为0就可以了

使用时就可以:

// 创建socket
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);

4.3 bind接口

创建完后,就要进行绑定(如果是客户端,则不用显式绑定,一般由OS去随机绑定端口)

绑定套接字,指定网络信息

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 第一个参数表示要绑定的套接字,也就是刚刚 socket 所创建的网络套接字
  • 第二个表示使用的套接字结构体,我们实际上使用的是 struct sockaddr_in
  • 第三个表示结构体大小
  • 成功返回0,失败返回-1

在使用前,我们需要设置套接字结构

// 绑定套接字
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());

我们先定义出一个 sockaddr_in 结构,然后将其清空,接下来就去填充。
我们来看看 sockaddr_in 结构:
在这里插入图片描述
这里的inet_addr是将4字节IP转换为网络序列
在这里插入图片描述


接下来就要把设置好的套接字设置进内核中

  // 将填充的内容设置进内核中
int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n != 0)
{
    lg.LogMessage(Fatal, "bind err,%d :%s\n", errno, strerror(errno));
    exit(Bind_Err);
}

注意:这里要将local强转为struct sockaddr结构

4.4 recvfrom

udp中绑定完后就可以直接收发消息了

udp中收消息用recvfrom

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
  • 第一个参数表示收哪个套接字的消息
  • 第二个表示收到数据放在的缓冲区,用户级缓冲区
  • 第三个表示用户级缓冲区大小,也表示期望收到的字节大小
  • 第四个表示收消息的方式,我们设置为0阻塞式收消息
  • 第五个和第六个参数是一个输出型参数,可以存储客户端的信息,可以得到client的ip和port
  • 返回值是实际收到的字节大小,失败返回-1

4.5 sendto

udp发消息常用sendto

ssize_t sendto(int sockfd,const void *buf,size_t len,int flags,const struct sockaddr *dest_addr,socklen_t addrlen);
  • 前四个参数和收消息一样,第一个表示通过哪个套接字发
  • 第五个和第六个表示收消息中 获取到发消息的 client的IP和port 的结构体
  • 返回值表示实际发过去的字节数,失败返回-1
这里有我写的几个udp套接字的例子,大家可以去我的gitee仓库看看:
UDPecho
远程执行命令
udp简单聊天室

五,TCP套接字常用API

tcp和udp使用起来大同小异,只是多了些步骤。

所以在前面几个步骤和udp没有任何差别,唯一的区别的就在创建套接字时传入的第二个参数改为 SOCK_STREAM

    1. 创建套接字
 // 1.创建套接字
 _listensock = socket(AF_INET, SOCK_STREAM, 0);
    1. 绑定套接字
// 2.填充本地服务器网络信息
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
// 3.绑定网络套接字
bind(_listensock, CONV(&local), sizeof(local))

5.1 listen接口

tcp协议是可靠连接,c/s双方要进行数据通信,必须要先建立连接

但是一般都是先由客户端发起连接,tcp服务器就需要一直等待客户端建立连接
所以tcp第一个不同就是要设置监听

将打开的socket设置为监听状态,以便它可以接受来自客户端的连接请求

int listen(int sockfd, int backlog);
  • 第一个参数是创建好的套接字
  • 第二个参数是全连接队列长度,表示在队列中等待处理的最大连接数
  • 返回值,成功返回0,失败返回-1

使用:

const static int default_backlog = 5;
// 4.设置socket为监听状态,tcp特有的操作
listen(_listensock, default_backlog)

5.2 accept接口

设置为监听状态后,如何知道有没有连接到来呢?
所以就要用accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 第一个参数是服务端的套接字描述符
  • 第二,三个参数是输入输出型参数,和udp一样,会带出客户端的信息
  • 返回的是一个新的文件描述符,表示返回一个新的sockfd,用来给客户端提供服务

tcp是面向连接的,服务器端需要accept,这里会生成新的sockfd用来提供服务,而socket创建套接字返回的sockfd用来获取连接

所以后面我们就通过accept返回的sockfd来进行发送或者接收数据。tcp是面向字节流的,所以tcp这里我们使用writeread来进行对套接字的读写。

使用:

int sockfd = accept(_listensock, CONV(&peer), &len);

5.3 connect接口

还有一个和udp不同的是,因为tcp是面向连接的,所以tcp客户端在创建好套接字后,不能和udp一样直接recvfrom和sendto,而是先发起连接。

初始化一个连接,在connect成功后自动绑定

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 这个sockfd是客户端创建的套接字描述符
  • 第二个addr表示要向哪个服务点发起连接
  • 成功返回0,失败返回-1

使用:

// 3.客户端向服务器发起connect
// 填充服务器信息
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); // 1. 字符串ip->4字节IP 2. 网络序列
n = connect(sockfd, (struct sockaddr*)server, sizeof(server));

六,补充

6.1 心跳机制

心跳机制:为了检查我们的服务器在任意时刻是否都是健康的,我们就每隔一段时间向服务器发送最小请求,如果得到回复则说明是健康的


6.2 守护进程

守护进程

网络服务器实际上是在Linux后台运行的,而不是bash前台,是以后台守护进程(精灵进程)的方式执行的
在这里插入图片描述

基本概念:

  • 进程组默认是在一个会话中的

  • 同时启动多个进程时,可以同属于一个进程组,组id一般是多个进程中的第一个

  • 每次登录Linux时,OS会给我们提供 一个bash和 一个终端(用来给用户提供命令解析), 这个bash和终端会话

  • 命令行中所有的进程都默认在当前会话中的一个进程组中(一个进程也可以是一个进程组)

    任何时候,一个会话内部可以存在很多个进程组,但是默认任意一个时刻,只允许一个进程组在前台

    前台任务—和终端和键盘相关的,可以I/O的

给指令后加 &变成后台

jobs 可以查看当前会话内的所有任务

  fg 后台任务编号 ---可以再将这个任务变为前台

ctrl + z暂停任务,并切换到后台,
bg编号 :将后台暂停的任务唤醒


所写的服务器都要受当前的会话影响,如果退出当前会话,则服务器也会退出。如果让其不受影响,就守护进程化

守护进程是一个独立的会话,不隶属于任何一个bash

int setsid(void);

这个接口会将调用这个函数的进程变为守护进程

同时这个进程不能是进程组的组长如果是自成进程组,一般通过创建子进程,再让父进程退出

守护进程一般是孤儿进程,其父进程是系统进程1号进程

(一般守护进程的命名上以 d 结尾)

启动后通过下面的指令查找守护进程:

ps ajx | head-1 && ps ajx | grep 进程名  

6.3 tcp底层发送细节

tcp协议中,客户端和服务端在内核中都有一个发送缓冲区接收缓冲区

  • write / send 的作用只是将用户要发送的内容拷贝到发送缓冲区中,当发送缓冲区满的时候,write/send就会阻塞
  • read / recv 的作用也只是将客户端发送到服务端的接收缓冲区的内容拷贝到用户自己的缓冲区中,接收缓冲区为空时,read/recv就会阻塞

在这里插入图片描述

而什么时候真正发送,发送失败了怎么办都是由 内核–TCP传输控制协议 决定的,所以TCP实际通信的时候是双方的OS之间的通信

这个也就是一种生产者消费者模型

发送方和接收方可以同时地把自己发送缓冲区的内容向对方的接收缓冲区拷贝(因为双方都有发送缓冲区和接收缓冲区),所以TCP是全双工的,udp全双工是类似的。

  • 在上层用户层面,我们只调用send/recv就可以,但是在底层,发送了多少数据,接收方不一定会全部接受(因为接收方的接收缓冲区可能不够了),tcp是面向字节流的,只关心字节数,所以是按照字节数来发送的,不知道是否是一个完整的报文。所以就要需要我们自己来明确报文之间的边界(粘包问题),然后再进行序列反序列化
  • 而udp要么不发送,发就发完整的报文,其在内核中已经明确了报文之间的边界(udp面向数据报

七,总结

这一节介绍了网络套接字编程中接口的使用,我们可以用这些接口来编写一些简单的服务器程序,下面是我写的几个tcp服务器程序,大家可以来参考一下:
tcp_echo服务器
tcp网络计算机

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

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

相关文章

【data】上海膜拜数据

数据初始样貌 一、数据预处理 1. 数据每5分钟栅格统计 时间数据的处理 path"mobike_shanghai.csv" dfpd.read_csv(path) # 获取时间信息,对于分钟信息,5分钟取整 def time_info(df,col): df[datetime] pd.to_datetime(df[col])df[wee…

DDS(数据分发服务) 和 P2P(点对点网络) 的详细对比

1. 核心特性对比 维度 DDS P2P 实时性 微秒级延迟,支持硬实时(如自动驾驶) 毫秒至秒级,依赖网络环境(如文件传输) 架构 去中心化发布/订阅模型,节点自主发现 完全去中心化,节…

【LeetCode 热题 100】动态规划 系列

📁 70. 爬楼梯 状态标识:爬到第i层楼梯时,有多少种方法。 状态转移方程:dp[i] dp[i-1] dp[i-2],表示从走一步和走两步的方式。 初始化:dp[1] 1 , dp[2] 2。 返回值:dp[n],即走到…

计网实验笔记(一)CS144 Lab1

Lab0 ByteStream : 实现一个在内存中的 有序可靠字节流Lab1 StreamReassembler:实现一个流重组器,一个将字节流的字串或者小段按照正确顺序来拼接回连续字节流的模块Lab2 TCPReceiver:实现入站字节流的TCP部分。Lab3 TCPSender:实…

使用 OpenCV 将图像中标记特定颜色区域

在计算机视觉任务中,颜色替换是一种常见的图像处理操作,广泛用于视觉增强、目标高亮、伪彩色渲染等场景。本文介绍一种简单而高效的方式,基于 OpenCV 检测图像中接近某种颜色的区域,并将其替换为反色(对比色&#xff0…

智源联合南开大学开源Chinese-LiPS中文多模态语音识别数据集

2025年5月6日,智源研究院在法国巴黎举行的GOSIM全球开源创新论坛上发布Chinese-LIPS中文多模态语音识别数据集,该数据为智源研究院联合南开大学共同构建。 在语音识别技术飞速发展的背景下,多模态语音识别正逐步成为学术界和工业界的研究热点…

RabbitMQ最新入门教程

文章目录 RabbitMQ最新入门教程1.什么是消息队列2.为什么使用消息队列3.消息队列协议4.安装Erlang5.安装RabbitMQ6.RabbitMQ核心模块7.RabbitMQ六大模式7.1 简单模式7.2 工作模式7.3 发布订阅模式7.4 路由模式7.5 主题模式7.6 RPC模式 8.RabbitMQ四种交换机8.1 直连交换机8.2 主…

python爬虫实战训练

前言:哇,今天终于能访问豆瓣了,前几天爬太多次了,网页都不让我访问了(要登录)。 先来个小练习试试手吧! 爬取豆瓣第一页(多页同上篇文章)所有电影的排名、电影名称、星…

Redis(三) - 使用Java操作Redis详解

文章目录 前言一、创建项目二、导入依赖三、键操作四、字符串操作五、列表操作六、集合操作七、哈希表操作八、有序集合操作九、完整代码1. 完整代码2. 项目下载 前言 本文主要介绍如何使用 Java 操作 Redis 数据库,涵盖项目创建、依赖导入及 Redis 各数据类型&…

【全网首发】解决coze工作流批量上传excel数据文档数据重复的问题

注意:目前方法将基于前一章批量数据库导入的修改!!!!请先阅读上篇文章的操作。抄袭注明来源 背景 上一节说的方法可以批量导入文件到数据库,但是无法解决已经上传的条目更新问题。简单来说,不…

xss-labs靶场第11-14关基础详解

前言: 目录 第11关 第12关 第13关前期思路: 第十四关 内容: 第11关 也和上一关一样,什么输入框都没有,也就是 也是一样的操作,先将这里的hidden属性删掉一个,注意是删掉一个 输入1111&a…

ConcurrentSkipListMap的深入学习

目录 1、介绍 1.1、线程安全 1.2、有序性 1.3、跳表数据结构 1.4、API 提供的功能 1.5、高效性 1.6、应用场景 2、数据结构 2.1、跳表(Skip List) 2.2、节点类型: 1.Node 2.Index 3.HeadIndex 2.3、特点 3、选择层级 3.1、随…

XML简要介绍

实际上现在的Java Web项目中更多的是基于springboot开发的,所以很少再使用xml去配置项目。所以我们的目的就是尽可能快速的去了解如何读懂和使用xml文件,对于DTD,XMLSchema这类约束的学习可以放松,主要是确保自己知道这里面的大致…

什么是直播美颜SDK?美颜技术底层算法科普

当下,不论是社交直播、电商直播,还是线上教学、虚拟主播场景,都离不开美颜技术的加持。虽然大家在日常使用直播APP时经常体验到美颜效果,但背后的技术原理却相对复杂。本篇文章小编将为大家揭开直播美颜SDK的神秘面纱,…

【pbootcms】打开访问首页显示未检测到您服务器环境的sqlite3数据库拓展,请检查php.ini中是否已经开启该拓展

【pbootcms】新建网站,新放的程序,打开访问首页显示未检测到您服务器环境的sqlite3数据库拓展,请检查php.ini中是否已经开启该拓展。 检查目前网站用到哪个版本的php,然后打开相关文件。 修改一下内容: 查找sqlite3,…

MySQL——十、InnoDB引擎

MVCC 当前读: 读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。 -- 当前读 select ... lock in share mode(共享锁) select ... for update update insert delete (排他锁)快照读:…

visual studio生成动态库DLL

visual studio生成动态库DLL 创建动态库工程 注意 #include “pch.h” 要放在上面 完成后点击生成 创建一个控制台项目 设置项目附加目录为刚才创建的动态库工程Dll1: 配置附加库目录: 配置动态库的导入库(.lib):链…

IDEA中git对于指定文件进行版本控制

最近在自己写代码的时候遇到了和公司里面不一样的,自己写的代码推到码云上是,会默认对于所有修改都进行提交,这样再提交的时候很不方便。 问了问ai,表示可以手动创建脚本实现,但是ai曲解了我的意思,它实现…

用Python绘制梦幻星空

用Python绘制梦幻星空 在这篇教程中,我们将学习如何使用Python创建一个美丽的星空场景。我们将使用Python的图形库Pygame和随机库来创建闪烁的星星、流星和月亮,打造一个动态的夜空效果。 项目概述 我们将实现以下功能: 创建深蓝色的夜…

GEE计算 RSEI(遥感生态指数)

🛰️ 什么是 RSEI?为什么要用它评估生态环境? RSEI(遥感生态指数,Remote Sensing Ecological Index) 是一种通过遥感数据计算得到的、综合反映区域生态环境质量的指标体系。 它的设计初衷是用最少的变量&…