Linux网络基础

目录

一、协议

二、OSI七层模型

物理层

数据链路层

网络层

传输层

应用层

三、OSI

[四、网络协议TCP/IP vs OSI](#四、网络协议TCP/IP vs OSI)

(1)协议的约定是如何做到的

(2)局域网通信原理(以太网为例)

(3)认识MAC地址

(4)链路层发送消息,从哪里来,谁让发的

五、重谈局域网通信

六、跨网络传输

[(1)IP 地址的核心定义](#(1)IP 地址的核心定义)

(2)MAC和ip地址的区别

七、传输路径

(1)mac地址为什么一直在变化

(2)ip协议存在意义

[八、Socket 编程预备](#八、Socket 编程预备)

九、认识端口号

(1)如何理解通过端口号找到目标进程

十、理解socket

(1)认识TCP协议

(2)认识UDP协议

[(3)socket 编程接口](#(3)socket 编程接口)

[(4)UDP socket](#(4)UDP socket)

EchoServer

(5)TCP的实现


一、协议

协议本质:就是约定,没有约定,即便是两台计算机能够相互联通,对方也不能理解

二、OSI七层模型

物理层

物理层负责光信号与电信号的传递,核心是确定信号的传输介质。常见介质包括以太网通用的双绞线、早期以太网使用的同轴电缆(现多用于有线电视)、光纤,以及 WiFi 无线网络使用的电磁波。物理层直接决定网络的最大传输速率、传输距离和抗干扰能力,集线器(Hub)是工作在该层的设备。

数据链路层

数据链路层的核心任务是实现设备间数据帧的传送与识别。具体工作包含网卡设备驱动管理、帧同步(明确从网线信号中识别新帧开始的标准)、冲突检测(检测到冲突时自动重发数据),以及数据差错校验。该层有以太网、令牌环网、无线 LAN 等标准,交换机(Switch)是工作在该层的设备。

网络层

网络层主要负责地址管理与路由选择。以 IP 协议为例,它通过 IP 地址标识主机身份,并借助路由表规划两台主机之间的数据传输线路(即路由)。路由器(Router)是工作在该层的核心设备。

传输层

传输层的功能是保障两台主机之间的数据传输。其中传输控制协议(TCP)是典型协议,能确保数据从源主机可靠地发送到目标主机。

应用层

应用层负责应用程序之间的沟通,提供具体的网络应用服务。常见协议包括简单电子邮件传输协议(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等。日常的网络编程主要针对应用层展开。

三、OSI

  • OSI(Open System Interconnection,开放系统互连)七层⽹络模型称为开放式系统互联参考
  • 模型,是⼀个逻辑上的定义和规范;
  • 把网络从逻辑上分为了7层. 每⼀层都有相关、相对应的物理设备,比如路由器,交换机;
  • OSI 七层模型是⼀种框架性的设计方法,其最主要的功能使就是帮助不同类型的主机实现数据传输;
  • 其实没有网络,即便单机情况,计算机内部也会有自己的协议
  • 计算机内部走线十分的紧凑,所以会产生相互干扰所以需要校验协议
  • 网络通信和单机的区别就是距离变成了(会产生新的问题,需要新的解决问题,这就是协议)
  • TCP/IP是一种解决网络通信的具体方案,而问题是层状的所以解决方案也必须是层状的

四、网络协议TCP/IP vs OSI

总结:

1、协议本质是一个约定

2、OS一般用C语言来写的

3、操作系统需要先描述在组织

3、协议就是结构体(在协议栈内部的)

4、协议相同,不同的操作系统就可以通信

(1)协议的约定是如何做到的

  • 遵守相同的标准
  • 网络传输的基本流程

(2)局域网通信原理(以太网为例)

首先回答,两台主机在同⼀个局域⽹,是否能够直接通信?

  • 原理类似点名(所有人都听到名字,但是只有对应的人才会答到,其他人会自动忽视)
  • 每台主机在局域⽹上,要有唯⼀的标识来保证主机的唯⼀性:mac地址

(3)认识MAC地址

  • MAC地址用来识别数据链路层中相连的节点;长度为48比特位,即6个字节,一般用16进制数字加上冒号的形式来表示(例如:08:00:27:03:fb:19)
  • 在网卡出厂时就确定了,不能修改,mac地址通常是唯一的(虚拟机中的mac地址不是真实的mac地址,可能会冲突;也有些网卡支持用户配置mac地址)

(4)认识以太网

  • 很多主机都开始互相发生消息,会互相碰撞,造成信息丢失
  • 所以主机采用碰撞检测和碰撞避免 (让对应的主机等一下,在重新发送)
  • 以太网 :是基于碰撞检查和碰撞避免的通信模式目前应有范围最广,被镶嵌在数据链路层

换一个角度

  • 以太网是一个公共资源,碰撞检查和碰撞避免(任何一个时刻,只允许一台,使用以太网,向目标主机发送数据)就是锁的功能

为什么局域网会存在这么多的技术(以太网,令牌环网)

局域网最先出现

(4)链路层发送消息,从哪里来,谁让发的

用户发的

  • 每一层都有协议,所以当我进行上述传输的时候,需要进行封装和解包
  • 每一层的报头类似于贴在快递上的快递单
  • 报头部分是对应协议层的结构体字段,一般叫做报头;
  • 除了报头,剩下的叫做有效载荷,
  • 故报文=报头+有效载荷。
  • 不同的协议层对数据包有不同的称谓:在传输层叫做段(segment),在网络层叫做数据报(datagram),在链路层叫做帧(frame)。
  • 应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),称为封装(Encapsulation)。
  • 首部信息中包含了一些类似于首部有多长、载荷(payload)有多长、上层协议是什么等信息。
  • 数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,根据首部中的'上层协议字段'将数据交给对应的上层协议处理。
  • 逻辑上同层之间都以为自己在和对方同层协议在直接通信,物理上是先从上到下,从下到上

任何协议(除了应用层)都要解决两个问题

(1)将报头和有效载荷分离

(2)把数据交付到上一层

五、重谈局域网通信

局域网通信,是主机通信吗

本质是协议栈之间在进行通信

六、跨网络传输

  • 为了标识长距离的主机我们使用ip
  • IP 协议有两个版本,分别是 IPv4 和 IPv6。在整个课程中,凡是提到 IP 协议且无特殊说明的,默认均指 IPv4。

(1)IP 地址的核心定义

  • IP 地址是 IP 协议中用于标识网络中不同主机的地址。
  • 对于 IPv4 而言,IP 地址是一个 4 字节(即 32 位)的整数。
  • IPv4 地址通常采用 "点分十进制" 的字符串形式表示,例如 192.168.0.1。这种表示法中,用点分割的每一个数字对应一个字节,其取值范围为 0-255。

(2)MAC和ip地址的区别

IP 地址和 Mac 地址的区别主要体现在路由过程中的变化情况与功能定位上:

  • IP 地址在整个路由过程中一直不变(当前阶段暂作此说明,后续会进一步修正)。
  • Mac 地址在整个路由过程中一直变化。
  • 目标 IP 是一种长远目标,Mac 地址是下一阶段目标;其中,目标 IP 是路径选择的重要依据,Mac 地址是局域网转发的重要依据。

七、传输路径

(1)mac地址为什么一直在变化

  • 每经历一个路由器,就要向上交付,解包,向下支付,封装的过程,mac 地址只在局域网有效
  • 上图我们会发现网络层向上,大家看到的数据报都是一样的,所以我们的互联网,是建立在ip网络的基础上的

(2)ip协议存在意义

虚拟化了底层局域网的差异

八、Socket 编程预备

  • 数据传输到主机是目的吗?不是,因为数据是给人看的
  • 但是⼈是怎么看到聊天信息的呢?怎么执行下载任务呢?怎么浏览网页信息呢?通过启动进程(例如: QQ ,微信),所以进程是人的代表,数据传输到主机不是目的,而是手段,到达主机内部将数据交给进程才是目的(网络通信就是进程通信)
  • 数据到达主机后如何发给目标进程? 端口号(标识进程的唯一性)

九、认识端口号

端口号是一个 2 字节 16 位的整数;

  • 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理;
  • IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用。
  • 一个进程可以多个端口号

(1)如何理解通过端口号找到目标进程

操作系统中存在一张哈希表,将端口号和TCP一一对应,通过TCP找到端口号

端口号划分

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

十、理解socket

综上,IP 地址用于标识互联网中唯一的一台主机,端口(port)则用于标识该主机上唯一的一个网络进程。

  • IP 地址与端口(port)结合,就能表示互联网中唯一的一个进程。
  • 因此通信时,本质是互联网中的两个进程代表用户进行交互,而 {源 IP(srcIp)、源端口(srcPort)、目的 IP(dstIp)、目的端口(dstPort)} 这样的 4 元组,可精准标识互联网中这两个通信的进程。
  • 由此可见,网络通信的本质,其实也是进程间通信。
  • 我们将 IP 地址与端口(port)的组合,称为套接字(socket)。

(1)认识TCP协议

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

(2)认识UDP协议

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

系统中同时存在大端和小端通信,网络通信必须使用大端(先发的是低地址,后发的是高地址)

(3)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);

网络通信是有不同的场景的,也是有不同的解决方案的

1、unix 域间socket

2、网络socket

3、原始socket

我们使用一套socket 解决上述问题,所以我们socket 接口的参数中有一个struct sockaddr *address

(4)UDP socket

Echo server 接口服务

recvform

flags :设置读写状态,0为阻塞读

src _addr : 输入输出性参数,获取对方的ip+socket

addrlen: 输入输出性参数,获取对方的结构体大小

返回值:读取多少字节

dest addr: 目标主机

addrlen : 目标主机结构体大小

EchoServer

Logger.cc

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include "mutex.hpp"
#include <filesystem>
#include <fstream> 
#include <ctime>
#include <sstream>
#include <memory>
#include <unistd.h>
enum class Loglevel
{
    DEBUG,
    INFO,
    WARNING,
    ERROR,
    FATAL
};
std::string LogLevelToString(Loglevel level)
{
    switch (level)
    {
    case  Loglevel::DEBUG:
        return "DEBUG";
    case  Loglevel::INFO:
        return "INFO";
    case  Loglevel::WARNING:
        return "WARNING";
    case  Loglevel::ERROR:
        return "ERROR";
    case  Loglevel::FATAL:
        return "FATAL";
    default:
        return "UNKNOWN";
    }

}
std::string GetCurrTime()
{
    time_t tm = time(nullptr);
    struct tm curr;
    localtime_r(&tm,&curr);
    char timebuffer[64];
    snprintf(timebuffer,sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",
    curr.tm_year + 1900,
    curr.tm_mon + 1,
    curr.tm_mday,
    curr.tm_hour,
    curr.tm_min,
    curr.tm_sec
    );
    return timebuffer;
}
class LogStrategy
{
public:
    virtual ~LogStrategy() = default;
    virtual void SyncLog(const std::string messgase) = 0;
};
class ConsoleLogStrategy : public LogStrategy
{
public:
    ~ConsoleLogStrategy()
    {

    }
    void SyncLog(const std::string messgase) override
    {
        {
            Lockguard lockguard(&_lock);
            std::cout << messgase << std::endl;
        }
    }
private:
    Mutex _lock;
};
const std::string path ="log";
const std::string filename = "test.log";
class FileLogStrategy: public LogStrategy
{
public:
    FileLogStrategy(const std::string logpath = path, const std::string logfilename = filename)
    :_logpath(logpath),
    _logfilename(logfilename)
    {
        { 
            Lockguard lockguard(&_lock);
            if (std::filesystem::exists(_logpath))
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }
        
    }
    ~FileLogStrategy()
    {

    }
    void SyncLog(const std::string messgase) override
    {
        {
            Lockguard lockguard(&_lock);
            std::string target = _logpath;
            target += '/';
            target += _logfilename;
            std::ofstream out(target.c_str(), std::ios::app);
            if (!out.is_open())
            {
                return;
            }
            out << messgase << std::endl;
            out.close();
        }
    }
private:
    std::string _logpath;
    std::string _logfilename;
    Mutex _lock;
};
class Logger
{
public:
    Logger()
    {

    }
    void EnableFileLogStrategy()
    {
        _strategy = std::make_unique<FileLogStrategy>();
    }
    void EnableConsoleLogStrategy()
    {
        _strategy = std::make_unique<ConsoleLogStrategy>();
    }
    class LogMessage
    {
    public:
        LogMessage(Loglevel level, std::string &filename, int line,  Logger &logger)
        :_time(GetCurrTime()),
         _level(level),
         _pid(getpid()),
         _filename(filename),
         _line(line),
         _logger(logger)
        {
            std::stringstream ss;
            ss << "[" << _time << "]"
               << "[" << LogLevelToString(_level) << "]"
               << "[" << _pid << "]"
               << "[" << _filename << "]"
               << "[" << _line << "]"
               << "-";
            _loginfo = ss.str();
        }
        template<typename T>
        LogMessage & operator<<(const T& info)
        {
            std::stringstream ss;
            ss << info;
            _loginfo += ss.str();
            return *this;
        }
        ~LogMessage()
        {
            if(_logger._strategy)
            {
                _logger._strategy->SyncLog(_loginfo);
            }
        }
    private:
        std::string _time;
        Loglevel _level;
        pid_t _pid;
        std::string _filename;
        int _line;
        std::string _loginfo;
        Logger &_logger;
    };
    LogMessage operator()(Loglevel type, std::string filename, int line)
    {
        return LogMessage(type, filename, line,*this);
    }
    ~Logger()
    {

    }
private:
    std::unique_ptr<LogStrategy> _strategy; 
};
Logger logger;
 #define LOG(type) logger(type, __FILE__, __LINE__)
 #define FileLogStrategy() logger.EnableFileLogStrategy()
 #define ConsoleLogStrategy() logger.EnableConsoleLogStrategy()

Makefile

cpp 复制代码
.PHONY:all
all: udp_cient udp_server

udp_cient:UdpClient.cc
	g++ -o $@ $^ -std=c++17
udp_server:UdpServer.cc
	g++ -o $@ $^ -std=c++17

.PHONY:clean
clean:
	rm -rf udp_cient udp_server

mutex.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&lock,nullptr);
    }
    void Lock()
    {
        pthread_mutex_lock(&lock);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&lock);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&lock);
    }
    pthread_mutex_t *GetMutexOriginal()
    {
        return &lock;
    }
private:
    pthread_mutex_t lock;
};
class Lockguard
{
public:
    Lockguard(Mutex* mutexgurd):_mutexgurd(mutexgurd)
    {
        _mutexgurd->Lock();
    }
    ~Lockguard()
    {
        _mutexgurd->Unlock();
    }
private:
   Mutex *_mutexgurd;
};

Udpclient.cc

cpp 复制代码
#include <iostream>
#include <string> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <cstring>
void Usage(std::string proc)
{
    std::cout<< "usage fail" <<std::endl; 
}
int main(int argc,char*argv[])
{
     if(argc!=3)
    {
        Usage(argv[0]);
        exit(0);
    }
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        return 1;
    }
    uint16_t port =std::stoi(argv[2]);
    const std::string ip = argv[1];
    struct sockaddr_in serv_addr;
        bzero(&serv_addr,sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(port);
       serv_addr.sin_addr.s_addr = inet_addr(ip.c_str());
    while(true)
    {
        std::cout<<"please enter@";
        std::string line;
        std::getline(std::cin,line);
        ssize_t n = sendto(sockfd,line.c_str(),line.size(),0,(struct sockaddr*)&serv_addr, sizeof(serv_addr));
        char buffer [1024];
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t a = recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);
    }
    return 0;
}

Udpserver.cc

cpp 复制代码
#include <iostream>
#include <string> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <cstring>
void Usage(std::string proc)
{
    std::cout<< "usage fail" <<std::endl; 
}
int main(int argc,char*argv[])
{
     if(argc!=3)
    {
        Usage(argv[0]);
        exit(0);
    }
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        return 1;
    }
    uint16_t port =std::stoi(argv[2]);
    const std::string ip = argv[1];
    struct sockaddr_in serv_addr;
        bzero(&serv_addr,sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(port);
       serv_addr.sin_addr.s_addr = inet_addr(ip.c_str());
    while(true)
    {
        std::cout<<"please enter@";
        std::string line;
        std::getline(std::cin,line);
        ssize_t n = sendto(sockfd,line.c_str(),line.size(),0,(struct sockaddr*)&serv_addr, sizeof(serv_addr));
        char buffer [1024];
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t a = recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);
    }
    return 0;
}

Udpserver.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <cstring>
#include "Logger.hpp"
static const int gdefaultsockfd = -1;
class UdpServer
{
public: 
    UdpServer(uint16_t port)
    :_sockfd(gdefaultsockfd),
     _running(false),
     _port(port)
    {

    }
    void Init()
    {
        
         _sockfd =  socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd < 0)
        {
            LOG(Loglevel::FATAL)<<"creat socket fail";
            exit(1);
        }
        LOG(Loglevel::INFO) <<"creat socket success";
        struct sockaddr_in local;
        bzero(&local,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;//绑定任意ip
        //不明确具体IP,只要是发给我对应的主机,对应的port 我都接受
        int n = bind(_sockfd,(struct sockaddr*)& local,sizeof(local));
        if(n < 0)
        {
            LOG(Loglevel::FATAL)<<"bind socket error";
            exit(2);
        }
        LOG(Loglevel::INFO)<<"bind socket success :" << _sockfd;
        
    }
    void Start()
    {
        _running = true;
        while(_running)
        {
            char buffer[1024];
            buffer[0] = 0;
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buffer[n] = 0;
                LOG(Loglevel::DEBUG) << "client say ###" << buffer;
                std::string echo_string = "sever say ###";
                echo_string += buffer;
                ssize_t n = sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)&peer, len);
                if (n < 0)
                {
                    LOG(Loglevel::FATAL) << "sento fail";
                }
            }
        }
        _running = false;
    }
    void Stop()
    {
        _running = false;
    }
    ~UdpServer()
    {

    }
private:
    int _sockfd;
    bool _running;
    uint16_t _port;
   // std::string _ip;//云服务器,禁止用户bind公网IP
};

(5)TCP的实现

backlog :

listen()声明sockfd处于监听状态, 并且最多允许有backlog个客⼾端处于连接等待状态, 如果接收

到更多的连接请求就忽略, 这⾥设置不会太大(⼀般是5), 具体细节同学们课后深⼊研究;

返回值:成功返回0,失败返回-1;

作用:获取新链接,没有新连接,默认是阻塞的,有新连接,获取的新连接

返回值:如果成功返回一个新的文件描述符

_socktd vs accpet return fd

例子:

一个饭店中,服务员一负责拉课,服务员二负责服务客人

饭店代表服务器,服务员一是_listensocktd作用是获取新链接,服务员二:服务新连接

connect

作用:发起链接请求

返回值:成功返回0,失败返回-1

作用:发送信息

inet_aton : 将主机地址转化成字符串

最佳实践:inet_pton

相关推荐
ZY小袁2 小时前
LVS(Linux virual server)实验
linux·运维·lvs
blockrock2 小时前
Linux Virtual Server (LVS)
linux·运维·lvs
hoududubaba2 小时前
ORAN中NB-IoT的基本概念
网络·网络协议
蜡笔小炘2 小时前
Haproxy -- 高级功能配置及实用案例
linux·运维·服务器·haproxy
礼拜天没时间.2 小时前
Linux运维实战:巧用mv命令管理多版本Go环境,避免采坑
linux·运维·golang·centos
期待のcode2 小时前
Kubernetes与Minikube
运维·容器·kubernetes
鸠摩智首席音效师2 小时前
如何在 Ubuntu 上安装 phpMyAdmin ?
linux·运维·ubuntu
lihao lihao2 小时前
页面自动化常见函数重点说明
运维·自动化
五阿哥永琪2 小时前
HTTP中,GET和POST的区别
网络·网络协议·http