【Linux】 Linux网络编程入门:Soket编程详解

前言:欢迎 各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!

IF'Maxue个人主页
🔥 个人专栏 :
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》

⛺️生活是默默的坚持,毅力是永久的享受。不破不立!

文章目录

    • 一、端口号:网络通信的"门牌号"
      • [1.1 底层实现原理](#1.1 底层实现原理)
      • [1.2 端口号 vs PID:解耦的两个标识](#1.2 端口号 vs PID:解耦的两个标识)
      • [1.3 IP+端口](#1.3 IP+端口)
      • [1.4 Socket:IP+端口的组合体](#1.4 Socket:IP+端口的组合体)
      • [1.5 端口号范围划分](#1.5 端口号范围划分)
      • [1.6 端口号和进程ID的关联](#1.6 端口号和进程ID的关联)
      • [1.7 源端口号 vs 目的端口号](#1.7 源端口号 vs 目的端口号)
      • [1.8 用"唐僧取西经"理解Socket](#1.8 用“唐僧取西经”理解Socket)
      • [1.9 传输层核心:TCP vs UDP](#1.9 传输层核心:TCP vs UDP)
    • 二、网络字节序:大端与小端
      • [2.1 大端/小端的定义](#2.1 大端/小端的定义)
      • [2.2 速记法](#2.2 速记法)
      • [2.3 解决大小端问题:网络字节序约定](#2.3 解决大小端问题:网络字节序约定)
      • [2.4 大小端转换的接口](#2.4 大小端转换的接口)
    • 三、Socket编程接口:网络通信的"工具集"
      • [3.1 常见API](#3.1 常见API)
      • [3.2 网络通信的本质](#3.2 网络通信的本质)
      • [3.3 三种套接字](#3.3 三种套接字)
      • [3.4 sockaddr结构:统一的地址格式](#3.4 sockaddr结构:统一的地址格式)
    • [四、UDP实战:实现Echo Server(回显服务器)](#四、UDP实战:实现Echo Server(回显服务器))

Socket(套接字)编程是让不同设备上的进程实现网络通信的核心方法,本质就是操作系统提供了一套标准化的 API,让我们能通过代码控制 "进程如何通过网络收发数据",全程不用关心底层网络硬件、协议细节,只需要按规则调用接口就行。

一、端口号:网络通信的"门牌号"

端口号说白了就是操作系统给网络进程分配的唯一标识,就像你家的门牌号

数据从网络过来,靠端口号才能精准找到要交给哪个程序处理,没它数据就成了"无家可归"的流浪包。

1.1 底层实现原理

端口号不是凭空来的,内核会通过专门的数据结构管理端口和进程的关联,简单说就是维护一张"端口-进程"映射表,数据来了先查这张表,再递交给对应进程,保证不会发错对象。

1.2 端口号 vs PID:解耦的两个标识

我一开始总把这俩弄混,后来找了个通俗比喻就懂了:

PID(进程ID)是进程在本机的"校内学号",只在这台机器上有用;

端口号是进程在网络中的"身份证号",跨设备通信全靠它。这种设计让网络和本地进程标识解耦,哪怕进程的PID变了,只要端口号不变,网络通信就不受影响。

1.3 IP+端口

IP地址负责定位网络中的设备(比如"北京市朝阳区XX小区"),端口号负责定位设备上的进程(比如"小区里的101室")。

一次完整的网络通信,必须靠{源IP,源port,目的IP,目的端口号}这四元组,才能唯一确定"谁给谁发数据"。

1.4 Socket:IP+端口的组合体

Socket(套接字)本质就是"IP地址+端口号"的组合,是应用程序和网络打交道的"接口"------有了Socket,

进程才能和外部设备建立连接、传输数据,没它程序就是"闭门造车",没法和外界通信。

1.5 端口号范围划分

端口号是16位整数,范围0~65535,不同区间有固定用途,避免端口占用冲突

具体划分看这张图就清楚了:

1.6 端口号和进程ID的关联

这里要记两个关键点:一个进程可以占用多个端口(比如一个服务同时监听TCP和UDP端口),但一个端口同一时间只能被一个进程占用

不然数据过来,内核都不知道该交给谁。

1.7 源端口号 vs 目的端口号

  • 源端口号:发送方随机分配的临时端口,作用是接收对方的响应数据(比如你给服务器发请求,源端口就是"你的回邮地址");

  • 目的端口号:接收方提供服务的固定端口(比如HTTP服务的80端口),是数据要到达的"最终目的地"。

1.8 用"唐僧取西经"理解Socket

要是还觉得抽象,就用这个例子类比:

西天 = 目的IP(定位"如来"所在的设备);如来的莲台 = 目的端口(定位"如来"这个进程);东土大唐 = 源IP;唐僧的行囊 = 源端口;Socket = 西天+莲台(或东土大唐+行囊)

保证唐僧能精准找到如来,完成"数据传递"。

1.9 传输层核心:TCP vs UDP

从这两张图能明确:传输层属于系统内核,我们要通过网络通信,就得调用它提供的 TCP/UDP 协议。

先看 TCP,它是传输层协议,核心特点是 "有连接、可靠传输、面向字节流"------ 通信前得先建立连接,能保证数据不丢、不错、不乱序,像 "稳定的管道" 一样连续传数据。

再看 UDP,同样是传输层协议,但它是 "无连接、不可靠传输、面向数据报"------ 不用建立连接直接发数据,不保证对方能收到,像 "对讲机喊话",简单直接但没保障。

字节流的读写特点

TCP是"字节流"协议,我总结下来就是:写数据像往水管里放水,连续写就行,特别简单;读数据像接水,不知道啥时候接完,还得处理"粘包",贼麻烦。

可靠传输 vs 不可靠传输
  • 可靠传输(TCP):会解决丢包、超时、乱序这些问题,靠校验、重传、确认等机制,保证数据一点不差到达;
  • 不可靠传输(UDP):不管这些,数据发出去就不管了,丢了也不重传,乱了也不调整。
为啥还要保留UDP?

我一开始也纳闷,既然UDP不可靠,为啥不都用TCP?

后来才懂:TCP为了可靠,协议复杂、占资源多(还要建立连接、维护状态);UDP简单、无连接、速度快,开发周期短,适合直播、语音通话这种"能容忍少量丢包,但要实时"的场景。

我们直播中也会选择udp or tcp的协议的选项

二、网络字节序:大端与小端

不同设备存储数据的方式不一样,就像有人从左写字,有人从右写字,网络通信必须统一"写字顺序",不然数据传过去就成了"乱码"。

2.1 大端/小端的定义

  • 大端:数据的高位存在低内存地址(比如数字12,"1"在低地址,"2"在高地址);
  • 小端:数据的低位存在低内存地址(数字12,"2"在低地址,"1"在高地址)。
    简单记:12存在最下面的是大端。

2.2 速记法

记不住大小端?看这张图的速记技巧,一眼就能分清:

2.3 解决大小端问题:网络字节序约定

市面上大小端设备都有,为了通信统一,规定:所有发往网络的数据,必须是大端(网络字节序) 。发送方要把本机字节序转成网络字节序,接收方再转回来,这样就不会解析错了。

补充一个关键点:

  • 网络传输规则:先发低地址数据,后发高地址数据;

2.4 大小端转换的接口

不用自己写转换逻辑,系统提供了现成的接口,直接调用就行:

三、Socket编程接口:网络通信的"工具集"

Socket是操作系统给的网络编程接口,核心要记住:网络通信的本质,其实是"跨设备的进程间通信"。

3.1 常见API

Socket编程的核心函数都在这张图里,是入门的基础:

cpp 复制代码
// 创建 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);

3.2 网络通信的本质

别被"网络通信"唬住,其实就是不同设备上的进程在说话。

大家看这张图,展示的是TCP三次握手的过程,这本质就是两个设备上的进程,通过网络建立连接、准备通信的过程

说到底,网络通信就是跨设备的进程间通信(IPC),和本地用管道、共享内存通信的核心目的一致,只是媒介换成了网络。

别把"三次握手"和"通信本质"搞混,三次握手是TCP实现可靠通信的手段,不是本质哦。

3.3 三种套接字

Socket分三类,但重点学网络套接字就行------学会网络套接字,本地套接字自然就会了,没必要单独花时间。

这张图展示的是TCP四次挥手,刚好对应上面的三次握手,是TCP断开连接的过程。大家可以结合着记:TCP通信有始有终,建立连接用三次握手,断开连接用四次挥手。

我们说的本地套接字和网络套接字,和这个挥手过程没关系

核心区别是通信范围------本地只能在同一台机器,网络能跨设备。新手千万别把"连接断开过程"和"套接字分类"混为一谈,这是很容易踩的坑!

3.4 sockaddr结构:统一的地址格式

为了兼容不同类型的套接字,系统设计了sockaddr基类结构,有点像C++的"继承多态",网络接口会自动区分你是要本地通信还是网络通信。

这张图是TCP的状态机,展示了TCP从建立连接到断开连接的各种状态(比如ESTABLISHED、TIME_WAIT)

虽然和sockaddr结构不直接相关,但能帮大家理解TCP的可靠性------正因为有这些状态管理,TCP才能保证数据可靠传输。

sockaddr结构的作用,是让内核知道你要用哪种方式通信:是AF_INET的网络通信,还是AF_UNIX的本地通信,内核会根据结构里的字段自动区分,不用我们手动指定,

sockaddr结构的设计思路

这里有个小知识点:为啥不用void*,非要用C语言模拟"基类"?

  • 一是可读性更高;
  • 二是设计这个结构的时候,void*还没普及。

四、UDP实战:实现Echo Server(回显服务器)

光懂理论没用,动手写个最简单的UDP回显服务器,就能把前面的知识点串起来------客户端发啥,服务器就回啥,新手入门超合适。

4.1 项目文件结构

先看整体文件组织,清晰的结构能少踩很多坑:

4.2 Makefile:一键编译

写个Makefile,不用每次手动敲编译命令,一键搞定客户端和服务器:

cpp 复制代码
.PHONY:all
all:udpclient udpserver

udpclient:UdpClient.cc
	g++ -o $@ $^ -std=c++17

udpserver:UdpServer.cc
	g++ -o $@ $^ -std=c++17

.PHONY:clean
clean:
	rm -f udpclient udpserver

4.3 核心代码文件

  • 客户端代码(udpClient.cc):负责发送数据到服务器,再接收回显;
cpp 复制代码
#include <iostream>
int main()
{
    return 0;
}
  • 服务器头文件(udpServer.hpp):封装UDP服务器的核心逻辑;
cpp 复制代码
#pragma once

class UdpServer
{
public:
    UdpServer()
    {}
    void Init()
    {}
    void Start()
    {}
    ~UdpServer()
    {}
private:
};

udpSever实现代码:

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"

using namespace LogModule;

const int defaultfd = -1;

class UdpServer
{
public:
    UdpServer():_sockfd(defaultfd)
    {}
    void Init()
    {
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            exit(1);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;
    }
    void Start()
    {}
    ~UdpServer()
    {}
private:
    int _sockfd;
};

UDP绑定Socket套接字

cpp 复制代码
LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;

// 2. 绑定socket信息,ip和端口,ip(比较特殊,后续解释)
// 2.1 填充sockaddr_in结构体
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(8080);  // 补充:指定端口(如8080),需转网络字节序
local.sin_addr.s_addr = INADDR_ANY;  // 补充:绑定所有本地IP
关键字段解析
字段	补充内容	说明
  • 服务器源文件(udpServer.cc):实现头文件声明的方法,核心是绑定端口、接收数据、回显数据;
cpp 复制代码
#include <iostream>
#include <memory>
#include "UdpServer.hpp"

int main()
{
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>();
    usvr->Init();
    usvr->Start();
    return 0;
}

4.4 核心接口详解

socket函数(UDP)

socket是创建 UDP 套接字的核心函数,其作用是为进程间通信创建一个 "端点"。

在 UDP 场景下,调用它时需指定三个关键参数:

  • domain填AF_INET(表示使用 IPv4 网络协议)
  • type填SOCK_DGRAM(表示创建 UDP 类型的套接字)
  • protocol填 0(默认使用与type匹配的协议)。

函数执行后,会返回一个套接字描述符(类似文件描述符),后续的绑定、收发数据等操作都需要通过这个描述符来完成,是 UDP 网络通信的基础入口

  • 返回值:成功返回套接字描述符,失败返回-1;
  • 头文件:使用socket函数必须包含的头文件;
sockaddr_in结构

存储网络地址的结构体,是sockaddr的"子类",必须掌握:

宏替换:简化字节序转换

系统提供的宏,能让字节序转换更简洁,不用自己写繁琐的转换逻辑:

1. ##的功能

##是宏定义中的 "符号连接运算符",可以将其两侧的符号拼接为一个新的标识符,用于从分离的文本片段创建自定义标识。

  1. 示例解析

宏定义:

cpp 复制代码
#define ADD_TO_SUM(num, value) \
    sum##num += value;

Socket使用的完整片段:

cpp 复制代码
LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;

// 2. 绑定socket信息,ip和端口,ip(比较特殊,后续解释)
// 2.1 填充sockaddr_in结构体
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
// 端口转换为网络字节序
local.sin_port = htons(_port);
// IP转换为网络字节序(将字符串格式的IP转为4字节网络序)
local.sin_addr.s_addr = inet_addr(_ip.c_str());

// 调用bind绑定Socket与IP、端口
int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
相关推荐
zfxwasaboy9 小时前
DRM KMS 子系统(4)Planes/Encoder/Connector
linux·c语言
Godspeed Zhao9 小时前
现代智能汽车中的无线技术25——Wi-Fi(13)
网络·汽车·智能路由器·信息与通信
暮色_年华9 小时前
随想 2:对比 linux内核侵入式链表和 STL 非侵入链表
linux·c++·链表
好学且牛逼的马9 小时前
【工具配置|docker】
运维·docker·容器
Bruce_Liuxiaowei10 小时前
基于HTA的Meterpreter反向Shell攻击实验
网络·windows·经验分享·网络安全·渗透测试
dnncool10 小时前
【Linux】操作系统发展
linux
文言一心10 小时前
LINUX离线升级 Python 至 3.11.9 操作手册
linux·运维·python
Dreamboat¿10 小时前
解析PHP安全漏洞:Phar反序列化、Filter链与文件包含的高级利用与防御
android·网络·php