云计算百科
云计算领域专业知识百科平台

Linux 网络 (7)

netstat

netstat 是一个用来查看网络状态的重要工具.

语法netstat [选项]

功能:查看网络状态

常用选项

n 拒绝显示别名,能显示数字的全部转化成数字

l 仅列出有在 Listen (监听) 的服務状态

p 显示建立相关链接的程序名

t (tcp)仅显示 tcp 相关选项

u (udp)仅显示 udp 相关选项

a (all)显示所有选项,默认不显示 LISTEN 相关

pidof

在查看服务器的进程 id 时非常方便.

语法pidof [进程名]

功能:通过进程名, 查看进程 id

TCP抓包

sudo tcpdump -i any tcp

注意:-i any 指定捕获所有网络接口上的数据包,tcp 指定捕获 TCP 协议的数据

包。i 可以理解成为 interface 的意思

如果你只想捕获某个特定网络接口(如 eth0)上的 TCP 报文,可以使用以下命令:

sudo tcpdump -i eth0 tcp

使用 host 关键字可以指定源或目的 IP 地址。例如,要捕获源 IP 地址为

192.168.1.100 TCP 报文,可以使用以下命令:

$ sudo tcpdump src host 192.168.1.100 and tcp

同时指定源和目的 IP 地址,可以使用 and 关键字连接两个条件:

sudo tcpdump src host 192.168.1.100 and dst host 192.168.1.200
and tcp

捕获特定端口的 TCP 报文

使用 port 关键字可以指定端口号。

例如,要捕获端口号为 80 TCP 报文(通常是 HTTP 请求),可以使用以下命令:

sudo tcpdump port 80 and tcp

保存捕获的数据包到文件

使用 -w 选项可以将捕获的数据包保存到文件中,以便后续分析。

sudo tcpdump -i eth0 port 80 -w data.pcap

这将把捕获到的 HTTP 流量保存到名为 data.pcap 的文件中。

了解:pcap 后缀的文件通常与 PCAPPacket Capture)文件格式相关,这是一

种用于捕获网络数据包的文件格式

从文件中读取数据包进行分析

使用 -r 选项可以从文件中读取数据包进行分析

tcpdump -r data.pcap

这将读取 data.pcap 文件中的数据包并进行分析。
注意事项
• 使用 tcpdump 时,请确保你有足够的权限来捕获网络接口上的数据包。通常,你
需要以 root 用户身份运行 tcpdump。
• 使用 tcpdump 的时候,有些主机名会被云服务器解释成为随机的主机名,如果不
想要,就用-n 选项

• 主机观察三次握手的第三次握手,不占序号

自旋锁

1. 概述

自旋锁是一种多线程同步机制,用于保护共享资源。线程获取锁失败时,会 ** 循环自旋(不断检查锁状态)** 而非进入休眠,以此减少线程切换开销,适用于短时间锁竞争;但不合理使用会造成 CPU 资源浪费。

2. 原理
  • 用共享标志位表示锁状态:true表示锁被占用,false表示锁可用。
  • 线程尝试获取锁时:
    • 若标志位为false,则将其设为true,占用锁并进入临界区。
    • 若标志位为true,则在循环中持续自旋等待,直到锁被释放。
3. 优缺点
  • 优点:
    • 低延迟:避免线程休眠与唤醒,提升锁操作效率。
    • 减少系统调度开销:等待线程不阻塞,无需上下文切换。
  • 缺点:
    • CPU 资源浪费:锁持有时间较长时,自旋线程会持续占用 CPU。
    • 可能引发活锁:多线程同时自旋且无退避策略时,可能都无法获取锁。
4. 使用场景
  • 锁占用时间极短的场景(如多线程对共享数据进行简单读写)。
  • 系统底层,用于同步多个 CPU 对共享资源的访问。
5. 实现原理

依赖原子操作(如 CAS 指令)保证锁状态修改的原子性,避免并发冲突

注意事项

  • 锁持有时间要短:使用自旋锁时,必须确保锁被释放的时间尽可能短,否则等待线程会持续自旋,造成大量 CPU 资源浪费。
  • 多 CPU 环境下的效率问题:在多 CPU 环境中,自旋锁可能不如其他锁机制高效,因为等待线程可能在不同的 CPU 核心上持续自旋等待,无法释放计算资源。

  • 结论

    自旋锁是一种适用于短时间锁竞争的同步机制,它通过避免线程休眠和上下文切换,显著降低了锁操作的开销,提升了效率。但它也存在明显缺点:长时间持有锁会导致 CPU 资源浪费,且在无退避策略的

    1. pthread_spin_init() —— 初始化自旋锁

    int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

    项说明
    功能 初始化一个自旋锁,必须在使用前调用
    参数 1 lock 输入输出参数:指向pthread_spinlock_t变量的指针,用于存储初始化后的锁状态
    参数 2 pshared 输入参数:锁的共享范围- PTHREAD_PROCESS_PRIVATE:仅当前进程内线程共享(最常用)- PTHREAD_PROCESS_SHARED:可跨进程共享(需配合共享内存)
    返回值 成功返回0;失败返回非 0 错误码(如EINVAL、ENOMEM)
    注意 未初始化的锁不能使用;同一锁不能重复初始化

    2. pthread_spin_destroy() —— 销毁自旋锁

    int pthread_spin_destroy(pthread_spinlock_t *lock);

    项说明
    功能 销毁已初始化的自旋锁,释放资源
    参数 lock 输入输出参数:指向要销毁的pthread_spinlock_t变量的指针
    返回值 成功返回0;失败返回非 0 错误码(如EINVAL表示锁无效)
    注意 不能销毁正在被其他线程持有的锁;销毁后不能再使用该锁,除非重新初始化

    3. pthread_spin_lock() —— 阻塞式加锁

    int pthread_spin_lock(pthread_spinlock_t *lock);

    项说明
    功能 阻塞式获取锁:获取不到则持续自旋等待,直到锁可用
    参数 lock 输入输出参数:指向要获取的自旋锁指针
    返回值 成功返回0;失败返回非 0 错误码(如EDEADLK检测到死锁)
    注意 自旋锁不支持重入,同一线程多次加锁会导致死锁;持有锁的时间必须极短

    4. pthread_spin_trylock() —— 非阻塞式尝试加锁

    int pthread_spin_trylock(pthread_spinlock_t *lock);

    项说明
    功能 非阻塞式尝试加锁:获取不到则立即返回,不会自旋等待
    参数 lock 输入输出参数:指向要尝试获取的自旋锁指针
    返回值 – 成功获取锁:返回0- 锁被占用:返回EBUSY- 其他错误:返回非 0 错误码
    注意 适合 “不想等待,失败则做其他事” 的场景;同样不支持重入

    5. pthread_spin_unlock() —— 解锁

    int pthread_spin_unlock(pthread_spinlock_t *lock);

    项说明
    功能 释放自旋锁,必须由持有锁的线程调用
    参数 lock 输入输出参数:指向要释放的自旋锁指针
    返回值 成功返回0;失败返回非 0 错误码(如EPERM表示调用线程不持有该锁)
    注意 未持有锁的线程调用解锁,会导致未定义行为(如程序崩溃)

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <pthread.h>

    int ticket = 20;
    // 1. 取消自旋锁变量注释
    pthread_spinlock_t lock;

    void *route(void *arg)
    {
    char *id = (char *)arg;
    while (1)
    {
    // 2. 加锁:获取自旋锁,保护临界区
    pthread_spin_lock(&lock);
    if (ticket > 0)
    {
    usleep(1000); // 模拟售票耗时(短时间,符合自旋锁使用原则)
    printf("%s sells ticket:%d\\n", id, ticket);
    ticket–;
    // 3. 解锁:释放自旋锁
    pthread_spin_unlock(&lock);
    }
    else
    {
    // 4. 无票时也要解锁,避免死锁
    pthread_spin_unlock(&lock);
    break;
    }
    }
    // 5. C语言中用NULL而非nullptr
    return NULL;
    }

    int main(void)
    {
    // 6. 初始化自旋锁
    pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);
    pthread_t t1, t2, t3;

    // 创建3个售票线程
    pthread_create(&t1, NULL, route, (void *)"thread 1");
    pthread_create(&t2, NULL, route, (void *)"thread 2");
    pthread_create(&t3, NULL, route, (void *)"thread 3");

    // 7. 等待所有子线程执行完成(核心修复点)
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);

    // 8. 销毁自旋锁,释放资源
    pthread_spin_destroy(&lock);
    return 0;
    }

    读写锁

    在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会

    比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴

    随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效

    率。那么有没有一种方法,可以专门处理这种多读少写的情况呢?

    有,那就是读写锁。

    1.pthread_rwlockattr_setkind_np() —— 设置读写锁优先级

    int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);

    • 项说明
      功能 设置读写锁的优先级策略(读者优先 / 写者优先),属于非标准扩展(_np 表示 non-portable)。
      参数 attr 输入参数:指向 pthread_rwlockattr_t 类型的属性对象指针,用于存储优先级设置。
      参数 pref 输入参数:优先级选项,共 3 种:- PTHREAD_RWLOCK_PREFER_READER_NP:默认,读者优先,可能导致写者饥饿- PTHREAD_RWLOCK_PREFER_WRITER_NP:写者优先(存在 BUG,实际表现与读者优先一致)- PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP:写者优先,且写者不能递归加锁
      返回值 成功返回 0;失败返回非 0 错误码(如 EINVAL 表示参数无效)。
      注意 该接口为非标准扩展,不同系统实现可能存在差异;优先级策略会影响读写锁的竞争行为,需谨慎选择。
    2. pthread_rwlock_init() —— 初始化读写锁

    c

    运行

    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

    项说明
    功能 初始化读写锁,必须在使用前调用。
    参数 rwlock 输入输出参数:指向 pthread_rwlock_t 类型的读写锁指针,用于存储初始化后的锁状态。
    参数 attr 输入参数:指向读写锁属性对象的指针;若为 NULL,则使用默认属性(读者优先)。
    返回值 成功返回 0;失败返回非 0 错误码(如 ENOMEM 表示内存不足)。
    注意 未初始化的读写锁不能使用;同一锁不能重复初始化。

    3. pthread_rwlock_destroy() —— 销毁读写锁

    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

    项说明
    功能 销毁已初始化的读写锁,释放系统资源。
    参数 rwlock 输入输出参数:指向要销毁的 pthread_rwlock_t 类型的读写锁指针。
    返回值 成功返回 0;失败返回非 0 错误码(如 EBUSY 表示锁仍被线程持有)。
    注意 不能销毁正在被其他线程持有的锁;销毁后不能再使用该锁,除非重新初始化。

    4. pthread_rwlock_rdlock() —— 获取读锁(共享锁)

    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

    项说明
    功能 获取读锁(共享锁),多个线程可同时持有读锁,写锁会被阻塞。
    参数 rwlock 输入输出参数:指向要获取读锁的 pthread_rwlock_t 类型的读写锁指针。
    返回值 成功返回 0;失败返回非 0 错误码(如 EDEADLK 检测到死锁)。
    注意 读锁是共享的,适合读多写少的场景;持有读锁时不能再获取写锁,否则会导致死锁。

    5. pthread_rwlock_wrlock() —— 获取写锁(独占锁)

    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

    项说明
    功能 获取写锁(独占锁),写锁持有期间,所有读锁和写锁请求都会被阻塞。
    参数 rwlock 输入输出参数:指向要获取写锁的 pthread_rwlock_t 类型的读写锁指针。
    返回值 成功返回 0;失败返回非 0 错误码(如 EDEADLK 检测到死锁)。
    注意 写锁是独占的,持有写锁时不能再获取读锁或写锁,否则会导致死锁;写锁持有时间应尽量短。

    6. pthread_rwlock_unlock() —— 释放读写锁

    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

    项说明
    功能 释放读锁或写锁,必须由持有锁的线程调用。
    参数 rwlock 输入输出参数:指向要解锁的 pthread_rwlock_t 类型的读写锁指针。
    返回值 成功返回 0;失败返回非 0 错误码(如 EPERM 表示调用线程不持有该锁)。
    注意 未持有锁的线程调用解锁会导致未定义行为(如程序崩溃);解锁后其他线程可竞争获取锁。

    #include <iostream>
    #include <pthread.h>
    #include <unistd.h>
    #include <vector>
    #include <cstdlib>
    #include <ctime>

    // 共享资源
    int shared_data = 0;

    // 读写锁
    pthread_rwlock_t rwlock;

    // 读者线程函数
    void *Reader(void *arg)
    {
    //sleep(1); //读者优先,一旦读者进入&&读者很多,写者基本就很难进入了
    int number = *(int *)arg;
    while (true)
    {
    pthread_rwlock_rdlock(&rwlock); // 读者加锁
    std::cout << "读者-" << number << " 正在读取数据,数据是: " << shared_data << std::endl;
    sleep(1); // 模拟读取操作
    pthread_rwlock_unlock(&rwlock); // 解锁
    }
    delete (int*)arg;
    return NULL;
    }

    // 写者线程函数
    void *Writer(void *arg)
    {
    int number = *(int *)arg;
    while (true)
    {
    pthread_rwlock_wrlock(&rwlock); // 写者加锁
    shared_data = rand() % 100; // 修改共享数据
    std::cout << "写者- " << number << " 正在写入。新的数据是: " << shared_data << std::endl;
    sleep(2); // 模拟写入操作
    pthread_rwlock_unlock(&rwlock); // 解锁
    }
    delete (int*)arg;
    return NULL;
    }

    int main()
    {
    srand(time(nullptr)^getpid());
    pthread_rwlock_init(&rwlock, NULL); // 初始化读写锁

    // 可以更高读写数量配比,观察现象
    const int reader_num = 2;
    const int writer_num = 2;
    const int total = reader_num + writer_num;
    pthread_t threads[total]; // 假设读者和写者数量相等

    // 创建读者线程
    for (int i = 0; i < reader_num; ++i)
    {
    int *id = new int(i);
    pthread_create(&threads[i], NULL, Reader, id);
    }

    // 创建写者线程
    for (int i = reader_num; i < total; ++i)
    {
    int *id = new int(i – reader_num);
    pthread_create(&threads[i], NULL, Writer, id);
    }

    // 等待所有线程完成
    for (int i = 0; i < total; ++i)
    {
    pthread_join(threads[i], NULL);
    }

    pthread_rwlock_destroy(&rwlock); // 销毁读写锁

    return 0;
    }

    读者优先(Reader-Preference

    在这种策略中,系统会尽可能多地允许多个读者同时访问资源(比如共享文件或数

    据),而不会优先考虑写者。这意味着当有读者正在读取时,新到达的读者会立即被

    允许进入读取区,而写者则会被阻塞,直到所有读者都离开读取区。读者优先策略可

    能会导致写者饥饿(即写者长时间无法获得写入权限),特别是当读者频繁到达时。

    写者优先(Writer-Preference

    在这种策略中,系统会优先考虑写者。当写者请求写入权限时,系统会尽快地让写者

    进入写入区,即使此时有读者正在读取。这通常意味着一旦有写者到达,所有后续的

    读者都会被阻塞,直到写者完成写入并离开写入区。写者优先策略可以减少写者等待

    的时间,但可能会导致读者饥饿(即读者长时间无法获得读取权限),特别是当写者

    频繁到达时。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux 网络 (7)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!