Linux操作系统

线程竞争

那么初始化一个整型为 0,使用一万个线程,每个线程都对该整型加 1,最后结果不一定会是 10000。这是因为整型变量的赋值操作不是原子操作,也就是说它不是一个不可分割的操作,而是由多条指令组成的。例如,对一个整型变量执行 a++ 操作,实际上包含了三个步骤:

  1. 将变量 a 的值从内存中读取到寄存器中。
  2. 将寄存器中的值加 1。
  3. 将寄存器中的值写回到内存中。
  • 在多线程环境中,如果没有同步机制,那么这三个步骤可能会被打断或重排,导致数据不一致或丢失。例如,假设有两个线程 A 和 B 同时对变量 a 执行 a++ 操作,初始时 a 的值为 0,那么可能出现以下情况:

    • 线程 A 执行第一步,将 a 的值读取到寄存器中,此时寄存器中的值为 0。
    • 线程 B 执行第一步,将 a 的值读取到寄存器中,此时寄存器中的值也为 0。
    • 线程 A 执行第二步,将寄存器中的值加 1,此时寄存器中的值为 1。
    • 线程 B 执行第二步,将寄存器中的值加 1,此时寄存器中的值也为 1。
    • 线程 A 执行第三步,将寄存器中的值写回到内存中,此时内存中 a 的值为 1。
    • 线程 B 执行第三步,将寄存器中的值写回到内存中,此时内存中 a 的值也为 1。
  • 这样就出现了一个问题,虽然两个线程都对 a 加了 1,但是最后 a 的值只增加了 1。这就是多线程操作共享变量时可能出现的竞态条件(race condition),也就是多个线程同时访问和修改同一个数据时导致结果不正确或不可预测的情况。

  • 要避免这个问题,有两种常见的方法:

    • 使用原子操作(atomic operation),也就是一种不可分割的操作,它可以保证在执行过程中不会被其他线程打断或干扰。C++ 提供了 <atomic> 头文件来支持原子操作。例如,可以使用 std::atomic<int> 类型来声明一个原子整型变量,并使用 fetch_add 方法来对其进行原子加法操作。这样就可以保证每个线程都能正确地对该变量加 1,并返回旧值或新值。
    #include <atomic>
    #include <thread>
    #include <iostream>std::atomic<int> a(0); // 声明一个原子整型变量,并初始化为 0void add_one() {// 对原子整型变量执行原子加法操作,并返回旧值int old = a.fetch_add(1);// 输出旧值和新值std::cout << "Old value: " << old << ", new value: " << old + 1 << std::endl;
    }int main() {// 创建一万个线程std::thread threads[10000];for (int i = 0; i < 10000; i++) {threads[i] = std::thread(add_one);}// 等待所有线程结束for (int i = 0; i < 10000; i++) {threads[i].join();}// 输出最终结果std::cout << "Final value: " << a << std::endl;return 0;
    }
    
    • 使用互斥锁(mutex),也就是一种同步机制,它可以保证在某一时刻只有一个线程可以访问和修改共享数据。C++ 提供了 <mutex> 头文件来支持互斥锁。例如,可以使用 std::mutex 类型来声明一个互斥锁,并使用 lockunlock 方法来对其进行加锁和解锁操作。这样就可以保证每个线程在对共享变量加 1 时,不会被其他线程干扰。
    #include <mutex>
    #include <thread>
    #include <iostream>int a = 0; // 声明一个普通整型变量,并初始化为 0
    std::mutex m; // 声明一个互斥锁void add_one() {// 对互斥锁进行加锁m.lock();// 对普通整型变量执行加法操作a++;// 输出当前值std::cout << "Current value: " << a << std::endl;// 对互斥锁进行解锁m.unlock();
    }int main() {// 创建一万个线程std::thread threads[10000];for (int i = 0; i < 10000; i++) {threads[i] = std::thread(add_one);}// 等待所有线程结束for (int i = 0; i < 10000; i++) {threads[i].join();}// 输出最终结果std::cout << "Final value: " << a << std::endl;return 0;
    }
    
  • 原子操作的优点是效率高,不需要额外的同步开销,但是它只能对简单的数据类型进行操作,而且不能保证内存顺序性(memory order),也就是说多个原子操作之间的执行顺序可能会被编译器或处理器重排,导致结果不符合预期。互斥锁的优点是灵活性高,可以对任意复杂的数据类型进行操作,而且可以保证内存顺序性,但是它需要额外的同步开销,而且可能导致死锁(deadlock),也就是说多个线程相互等待对方释放锁,导致程序无法继续执行。

除了原子操作和互斥锁之外,还有一些其他的方法可以避免多线程操作共享变量时出现的问题,例如:

  • 使用条件变量(condition variable),也就是一种同步机制,它可以让一个线程等待另一个线程的通知,从而避免不必要的轮询或竞争。条件变量通常和互斥锁一起使用,以保证数据的一致性。C++ 提供了 <condition_variable> 头文件来支持条件变量。例如,可以使用 std::condition_variable 类型来声明一个条件变量,并使用 waitnotify_onenotify_all 方法来进行等待和通知操作。这样就可以实现生产者-消费者模式,也就是一个线程负责生产数据,另一个线程负责消费数据,两者之间通过条件变量进行协调。

    #include <mutex>
    #include <condition_variable>
    #include <queue>
    #include <thread>
    #include <iostream>std::mutex m; // 声明一个互斥锁
    std::condition_variable cv; // 声明一个条件变量
    std::queue<int> q; // 声明一个队列
    bool done = false; // 声明一个标志位void producer() {// 生产 10 个数据for (int i = 0; i < 10; i++) {// 对互斥锁进行加锁std::unique_lock<std::mutex> lock(m);// 向队列中插入数据q.push(i);// 输出生产的数据std::cout << "Produced " << i << std::endl;// 对互斥锁进行解锁lock.unlock();// 通知消费者cv.notify_one();}// 设置标志位为 truedone = true;// 通知消费者cv.notify_one();
    }void consumer() {while (true) {// 对互斥锁进行加锁std::unique_lock<std::mutex> lock(m);// 等待生产者的通知或标志位为 truecv.wait(lock, []{return !q.empty() || done;});// 如果队列不为空,则取出数据并消费if (!q.empty()) {int x = q.front();q.pop();std::cout << "Consumed " << x << std::endl;lock.unlock();} else {// 如果队列为空且标志位为 true,则退出循环if (done) {break;}lock.unlock();}}
    }int main() {// 创建两个线程std::thread t1(producer);std::thread t2(consumer);// 等待两个线程结束t1.join();t2.join();return 0;
    }
    
  • 使用信号量(semaphore),也就是一种同步机制,它可以限制对共享资源的访问数量,从而避免过载或冲突。信号量有一个整数值,表示可用的资源数量,当一个线程想要访问资源时,它必须先获取信号量,如果信号量大于零,则信号量减一,并允许访问资源;如果信号量等于零,则线程必须等待其他线程释放信号量。当一个线程访问完资源后,它必须释放信号量,使信号量加一,并唤醒等待的线程。C++ 提供了 <semaphore> 头文件来支持信号量。例如,可以使用 std::counting_semaphore 类型来声明一个计数信号量,并使用 acquirerelease 方法来进行获取和释放操作。这样就可以实现多个线程同时访问有限数量的资源。

    #include <semaphore>
    #include <thread>
    #include <iostream>std::counting_semaphore<3> sem(3); // 声明一个计数信号量,初始值为 3void access_resource(int id) {// 获取信号量sem.acquire();// 输出访问资源的线程 idstd::cout << "Thread " << id << " is accessing resource" << std::endl;// 模拟访问资源的时间std::this_thread::sleep_for(std::chrono::seconds(1));// 输出释放资源的线程 idstd::cout << "Thread " << id << " is releasing resource" << std::endl;// 释放信号量sem.release();
    }int main() {// 创建 10 个线程std::thread threads[10];for (int i = 0; i < 10; i++) {threads[i] = std::thread(access_resource, i);}// 等待 10 个线程结束for (int i = 0; i < 10; i++) {threads[i].join();}return 0;
    }
    

IO多路复用

  • IO 多路复用:IO 多路复用是一种技术,它可以让一个进程或线程同时监视多个 IO 事件(如文件描述符、套接字等),并在其中一个或多个 IO 事件发生时,通知该进程或线程进行相应的处理。IO 多路复用可以提高 IO 效率,避免不必要的阻塞和轮询,适用于高并发的网络编程场景。

  • IO 多路复用的原理:IO 多路复用的原理是利用操作系统提供的一些系统调用(如 select, poll, epoll 等),将多个 IO 事件注册到一个事件集合中,然后让操作系统负责监视这些事件的状态变化,并在有事件发生时返回给用户程序。用户程序只需要调用一次系统调用,就可以处理多个 IO 事件,而不需要自己去轮询每个 IO 事件的状态,从而节省了 CPU 资源和时间。

  • IO 多路复用的优缺点:IO 多路复用的优点是它可以实现高效的 IO 处理,减少了进程或线程的切换开销,提高了并发性能。IO 多路复用的缺点是它需要额外的系统调用开销,而且不同的系统调用有各自的局限性和兼容性问题。

  • Linux 中常见的 IO 多路复用系统调用:Linux 中常见的 IO 多路复用系统调用有以下几种:

    • select:select 是最早出现的 IO 多路复用系统调用,它可以监视一组文件描述符(file descriptor),并在其中一个或多个文件描述符就绪(可读、可写或有异常)时返回。select 的原型如下:
    #include <sys/select.h>
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    

    其中,nfds 是要监视的文件描述符的数量,一般为最大文件描述符值加一;readfds, writefds, exceptfds 分别是指向读、写、异常文件描述符集合的指针,如果对某种类型不感兴趣,可以传入 NULL;timeout 是指定等待时间的指针,如果为 NULL,则表示无限等待;如果为零,则表示立即返回;否则表示等待指定的秒数和微秒数。select 的返回值是就绪文件描述符的数量,如果超时则返回零,如果出错则返回 -1,并设置 errno。

    select 的优点是它可以跨平台使用,兼容性好;缺点是它只能监视 1024 个文件描述符(受 FD_SETSIZE 的限制),而且每次调用都需要将文件描述符集合从用户空间拷贝到内核空间,效率低。

    • poll:poll 是对 select 的改进,它也可以监视一组文件描述符,并在其中一个或多个文件描述符就绪时返回。poll 的原型如下:
    #include <poll.h>
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    

    其中,fds 是指向一个 pollfd 结构体数组的指针,每个 pollfd 结构体包含了一个文件描述符和一个事件掩码(表示要监视哪些事件);nfds 是要监视的文件描述符的数量;timeout 是指定等待时间的毫秒数,如果为 -1,则表示无限等待;如果为零,则表示立即返回;否则表示等待指定的毫秒数。poll 的返回值是就绪文件描述符的数量,如果超时则返回零,如果出错则返回 -1,并设置 errno。

    poll 的优点是它没有监视文件描述符的数量限制,而且不需要每次调用都重新设置文件描述符集合;缺点是它仍然需要将整个文件描述符数组从用户空间拷贝到内核空间,而且返回时需要遍历整个数组来找出就绪的文件描述符,效率低。

    • epoll:epoll 是 Linux 特有的 IO 多路复用系统调用,它可以监视一组文件描述符,并在其中一个或多个文件描述符就绪时返回。epoll 的原型如下:
    #include <sys/epoll.h>
    int epoll_create(int size);
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    

    其中,epoll_create 用于创建一个 epoll 实例,并返回一个文件描述符 epfd,size 参数已经被忽略,只是为了兼容旧版本的接口;epoll_ctl 用于向 epoll 实例中添加、修改或删除一个文件描述符,op 参数表示操作类型(EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL),fd 参数表示要操作的文件描述符,event 参数是指向一个 epoll_event 结构体的指针,该结构体包含了一个事件掩码和一个用户数据(可以是一个指针或一个整数);epoll_wait 用于等待 epoll 实例中的文件描述符就绪,并将就绪的文件描述符填充到 events 数组中,maxevents 参数表示 events 数组的大小,timeout 参数表示等待时间的毫秒数,如果为 -1,则表示无限等待;如果为零,则表示立即返回;否则表示等待指定的毫秒数。epoll_wait 的返回值是就绪文件描述符的数量,如果超时则返回零,如果出错则返回 -1,并设置 errno。

    epoll 的优点是它使用了内核与用户空间共享内存的机制,避免了不必要的拷贝开销,而且它只返回就绪的文件描述符,不需要遍历整个集合,效率高;缺点是它只能在 Linux 上使用,而且对于某些特殊的文件描述符(如 pipe 的写端),它可能产生惊群效应(thundering herd),也就是说多个线程都被唤醒,但只有一个线程能够处理事件,其他线程又重新进入等待状态。

进程间通通信(IPC)的方法。

  • 进程间通信:进程间通讯是指不同的进程之间如何传递和共享数据的技术,它可以实现进程之间的协作和同步,提高系统的并发性能和可靠性。Linux 操作系统中提供了多种进程间通讯的方法,主要有以下几种:

    • 管道(pipe):管道是一种最简单的进程间通讯的方法,它可以在有亲缘关系的进程(如父子进程)之间建立一个单向的数据流,一个进程向管道的写端写入数据,另一个进程从管道的读端读取数据。管道的优点是使用方便,不需要额外的文件或内存空间;缺点是只能在有亲缘关系的进程之间使用,而且只能实现单向的通讯。Linux 提供了 pipe 系统调用来创建一个管道,并返回两个文件描述符,分别表示管道的读端和写端。
    #include <unistd.h>
    int pipe(int pipefd[2]);
    
    • 命名管道(named pipe):命名管道是对管道的改进,它可以在没有亲缘关系的进程之间建立一个双向的数据流,它通过在文件系统中创建一个特殊的文件来实现,任何知道该文件名的进程都可以打开该文件,并进行读写操作。命名管道的优点是可以在任意的进程之间使用,而且可以实现双向的通讯;缺点是需要额外的文件空间,而且数据传输速度较慢。Linux 提供了 mkfifo 系统调用来创建一个命名管道,并返回一个文件描述符。
    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo(const char *pathname, mode_t mode);
    
    • 消息队列(message queue):消息队列是一种更高级的进程间通讯的方法,它可以在没有亲缘关系的进程之间传递一组消息,每个消息都有一个类型和一个长度,消息队列按照先进先出(FIFO)或者优先级的顺序存储和发送消息。消息队列的优点是可以避免数据同步和阻塞问题,而且可以根据消息类型进行选择性地接收;缺点是需要额外的内核空间,而且消息格式和长度有限制。Linux 提供了 msggetmsgsndmsgrcv 等系统调用来创建、发送和接收消息队列。
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    int msgget(key_t key, int msgflg);
    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
    

进程崩溃(crash)的原因和处理方法

  • 进程崩溃:进程崩溃是指一个进程在运行过程中由于某些原因意外地终止,无法正常地完成其任务。进程崩溃通常会导致系统资源的浪费,用户数据的丢失,甚至系统的不稳定或死机。

  • 进程崩溃的原因:进程崩溃的原因有很多,主要有以下几种:

    • 非法操作:如果一个进程试图执行一些非法的操作,例如访问非法的内存地址,除以零,执行无效的指令等,那么操作系统会检测到这些错误,并向该进程发送一个信号(signal),通常是 SIGSEGV(段错误),SIGFPE(浮点异常),SIGILL(非法指令)等。如果该进程没有注册相应的信号处理函数(signal handler),那么该进程就会被终止,并生成一个核心转储文件(core dump file),用于记录进程崩溃时的内存和寄存器状态。
    • 资源不足:如果一个进程在运行过程中需要申请更多的资源,例如内存,文件描述符,线程等,而操作系统无法满足其需求,那么操作系统也会向该进程发送一个信号,通常是 SIGXFSZ(文件大小超限),SIGXCPU(CPU 时间超限),ENOMEM(内存不足)等。如果该进程没有注册相应的信号处理函数,那么该进程也会被终止,并可能生成一个核心转储文件。
    • 程序逻辑错误:如果一个进程在运行过程中出现了程序逻辑错误,例如死循环,死锁,内存泄漏等,那么该进程可能会陷入无法正常退出的状态,或者消耗过多的系统资源,影响其他进程的运行。这种情况下,操作系统不一定会主动终止该进程,而是需要用户或管理员手动地杀死(kill)该进程,并分析其日志文件(log file)或调试信息(debug info)来找出错误原因。
  • 进程崩溃的处理方法:进程崩溃的处理方法有以下几种:

    • 预防:预防是最好的处理方法,它要求程序员在编写程序时遵循良好的编码规范和风格,避免使用不安全的函数或语句,检查各种可能的边界条件和异常情况,并使用合适的工具和方法进行调试和测试,以消除程序中潜在的错误和漏洞。
    • 恢复:恢复是在进程崩溃后尽可能地恢复其正常状态或最小化其损失的方法,它要求程序员在编写程序时注册合适的信号处理函数,并在信号处理函数中执行一些必要的操作,例如释放资源,保存数据,记录日志等。此外,还可以使用一些工具和方法来分析核心转储文件或日志文件,以确定进程崩溃时的上下文信息和错误原因,并根据这些信息进行修复或优化。
    • 重启:重启是在进程崩溃后重新启动该进程或整个系统的方法,它是一种简单而暴力的处理方法,它可以快速地恢复服务或功能,但是它不能解决根本的问题,而且可能会导致数据丢失或不一致。重启可以通过手动或自动的方式来实现,例如使用 shell 命令,编写守护进程(daemon process),使用 systemd 服务等。

套接字

  • 套接字:套接字是一种通信机制,它可以在不同的进程或不同的主机之间进行数据交换,类似于文件描述符(file descriptor),它也是一个整数,表示一个打开的通信端点。套接字的头文件是 <sys/socket.h>,它提供了一些函数和数据结构来创建和操作套接字。

  • 套接字的类型:Linux 操作系统中支持以下几种类型的套接字:

    • 流式套接字(stream socket):流式套接字使用 TCP 协议来提供可靠的、有序的、双向的字节流服务,它可以保证数据不会丢失或重复,也不会出现边界问题。流式套接字适用于需要高可靠性的应用,例如文件传输,远程登录等。流式套接字的类型常量是 SOCK_STREAM
    • 数据报套接字(datagram socket):数据报套接字使用 UDP 协议来提供无连接的、不可靠的、无序的数据报服务,它不保证数据的到达或顺序,也不进行重传或确认,但是它可以避免连接建立和维护的开销,提高传输效率。数据报套接字适用于需要高效率或实时性的应用,例如视频流,语音通话等。数据报套接字的类型常量是 SOCK_DGRAM
    • 原始套接字(raw socket):原始套接字使用 IP 协议来提供直接访问网络层的服务,它可以自定义或修改 IP 数据包的内容和格式,也可以处理一些特殊的协议,例如 ICMP,IGMP 等。原始套接字适用于需要高灵活性或定制性的应用,例如网络诊断,网络安全等。原始套接字的类型常量是 SOCK_RAW
  • 套接字的地址:每个套接字都有一个地址(address),用于标识通信的源和目的地。不同类型的套接字有不同格式的地址,但是它们都使用一个通用的数据结构来表示,即 sockaddr 结构体,它定义如下:

struct sockaddr {sa_family_t sa_family; // 地址族char        sa_data[14]; // 地址信息
};

其中,sa_family 表示地址族(address family),也就是通信协议的类型,常见的地址族有 AF_INET(IPv4 协议),AF_INET6(IPv6 协议),AF_UNIX(Unix 域协议)等;sa_data 表示地址信息,也就是具体的通信地址,它根据不同的地址族有不同的含义和格式。

为了方便使用不同地址族的地址信息,Linux 操作系统还提供了一些专门针对某种地址族的数据结构来表示地址,例如:

  • 对于 IPv4 地址族(AF_INET),使用 sockaddr_in 结构体来表示地址,它定义如下:
struct sockaddr_in {sa_family_t    sin_family; // 地址族in_port_t      sin_port; // 端口号struct in_addr sin_addr; // IP 地址
};

其中,sin_family 表示地址族,必须为 AF_INET;sin_port 表示端口号,使用网络字节序(big-endian)表示;sin_addr 表示 IP 地址,使用一个 in_addr 结构体表示,它定义如下:

struct in_addr {uint32_t s_addr; // IP 地址
};

其中,s_addr 表示 IP 地址,使用网络字节序表示。为了方便地将 IP 地址和端口号转换为字符串或数字,Linux 操作系统提供了一些函数,例如 inet_ntopinet_ptonhtonsntohs 等。

  • 对于 IPv6 地址族(AF_INET6),使用 sockaddr_in6 结构体来表示地址,它定义如下:
struct sockaddr_in6 {sa_family_t     sin6_family; // 地址族in_port_t       sin6_port; // 端口号uint32_t        sin6_flowinfo; // 流信息struct in6_addr sin6_addr; // IP 地址uint32_t        sin6_scope_id; // 作用域
};

其中,sin6_family 表示地址族,必须为 AF_INET6;sin6_port 表示端口号,使用网络字节序表示;sin6_flowinfo 表示流信息,用于区分同一主机上的不同流;sin6_addr 表示 IP 地址,使用一个 in6_addr 结构体表示,它定义如下:

struct in6_addr {unsigned char s6_addr[16]; // IP 地址
};

其中,s6_addr 表示 IP 地址,使用网络字节序表示。为了方便地将 IP 地址和端口号转换为字符串或数字,Linux 操作系统提供了一些函数,例如 inet_ntopinet_ptonhtonsntohs 等。

  • 对于 Unix 域地址族(AF_UNIX),使用 sockaddr_un 结构体来表示地址,它定义如下:
#define UNIX_PATH_MAX 108
struct sockaddr_un {sa_family_t sun_family; // 地址族char        sun_path[UNIX_PATH_MAX]; // 路径名
};

其中,sun_family 表示地址族,必须为 AF_UNIX;sun_path 表示路径名,也就是 Unix 域套接字所对应的文件名。

  • 套接字的操作:Linux 操作系统中提供了一些函数来创建和操作套接字,主要有以下几种:

    • socket:socket 函数用于创建一个套接字,并返回一个文件描述符,它的原型如下:
    #include <sys/types.h>
    #include <sys/socket.h>
    int socket(int domain, int type, int protocol);
    

    其中,domain 参数表示地址族的类型,可以是 AF_INET, AF_INET6, AF_UNIX 等;type 参数表示套接字的类型,可以是 SOCK_STREAM, SOCK_DGRAM, SOCK_RAW 等;protocol 参数表示具体的协议类型,通常为 0,表示使用默认的协议。socket 函数的返回值是一个文件描述符,如果出错则返回 -1,并设置 errno。

    • bind:bind 函数用于将一个套接字绑定到一个地址上,它的原型如下:
    #include <sys/types.h>
    #include <sys/socket.h>
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    其中,sockfd 参数是由 socket 函数返回的文件描述符;addr 参数是指向一个 sockaddr 结构体或其子结构体(如 sockaddr_in, sockaddr_in6, sockaddr_un 等)的指针;addrlen 参数是该结构体的大小。bind 函数的返回值是 0,表示成功;如果出错则返回 -1,并设置 errno。

    • listen:listen 函数用于将一个流式套接字转换为被动模式(passive mode),也就是说该套接字可以接受其他进程的连接请求,它的原型如下:
    #include <sys/types.h>
    #include <sys/socket.h>
    int listen(int sockfd, int backlog);
    

    其中,sockfd 参数是由 socket 函数返回的文件描述符;backlog 参数表示等待连接队列(pending connection queue)的最大长度,也就是说该套接字可以等待的最大连接数。listen 函数的返回值是 0,表示成功;如果出错则返回 -1,并设置 errno。

  • accept:accept 函数用于接受一个连接请求,并返回一个新的文件描述符,该文件描述符用于与客户端进行通信,它的原型如下:

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

其中,sockfd 参数是由 listen 函数创建的套接字的文件描述符;addr 参数是一个指向 sockaddr 结构体的指针,用于存储客户端的地址信息;addrlen 参数是一个指向整数的指针,用于表示 addr 结构体的大小。accept 函数的返回值是一个新的文件描述符,用于与客户端进行通信;如果出错则返回 -1,并设置 errno。

  • connect:connect 函数用于发起一个连接请求,将套接字连接到指定的地址,它的原型如下:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

其中,sockfd 参数是由 socket 函数创建的套接字的文件描述符;addr 参数是一个指向 sockaddr 结构体的指针,用于指定目标地址;addrlen 参数是 addr 结构体的大小。connect 函数的返回值是 0,表示成功;如果出错则返回 -1,并设置 errno。

  • sendrecv:send 函数用于向已连接的套接字发送数据,recv 函数用于从已连接的套接字接收数据,它们的原型分别如下:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

其中,sockfd 参数是已连接的套接字的文件描述符;buf 参数是一个指向数据缓冲区的指针;len 参数表示数据的长度;flags 参数用于指定发送或接收数据的选项。send 和 recv 函数的返回值是实际发送或接收的数据长度,如果出错则返回 -1,并设置 errno。

  • close:close 函数用于关闭一个套接字,释放相关的资源,它的原型如下:
#include <unistd.h>
int close(int sockfd);

其中,sockfd 参数是要关闭的套接字的文件描述符。close 函数的返回值是 0,表示成功;如果出错则返回 -1,并设置 errno。

这些是套接字在 Linux 操作系统中的基本操作和数据结构,可以用于实现各种网络通信应用。根据不同的需求和场景,可以选择合适的套接字类型、地址族和操作来构建应用程序。同时,还需要考虑网络安全性、性能优化等方面的问题,以确保应用程序的稳定性和可靠性。

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

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

相关文章

如何在 Linux 中管理用户

Linux是一种开源操作系统,由于其灵活性、稳定性和安全性而被广泛应用于各种应用中。Linux 的基本方面之一是用户管理,它使管理员能够控制对资源的访问并维护系统的安全。 在快节奏的技术世界中,高效的用户管理对于维护安全且组织良好的 Linux 环境至关重要。本文作为 Linux…

Css 将div设置透明度,并向上移50px,盖住上面的元素一部分

可以使用CSS中的opacity和position属性来实现。 首先&#xff0c;将div的opacity属性设置为小于1的值&#xff0c;比如0.5&#xff0c;这样就可以设置透明度了。其次&#xff0c;将div的position设置为relative&#xff0c;然后再将它向上移动50px&#xff0c;即可盖住上面的元…

Kafka多语言版本

Installation curl -sSL https://raw.githubusercontent.com/bitnami/containers/main/bitnami/kafka/docker-compose.yml > docker-compose.yml docker-compose up -dRust 这里使用的是rdkafka, producer.rs: use std::time::Duration;use clap::{App, Arg}; use log::…

力扣(LeetCode)算法_C++—— 只出现一次的数字

给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 示例 1 &#xff1a; 输入&#xff1…

uni-app之android项目云打包

1&#xff0c;项目根目录&#xff0c;找到mainfest.json&#xff0c;如果appid是空的&#xff0c;需要生成一个appid 2&#xff0c;点击重新获取appid&#xff0c;这个时候需要登录&#xff0c;那就输入账号密码登录下 3&#xff0c;登陆后可以看到获取appid成功 4&#xff0c;…

gitHub添加ssh

gitHub添加ssh 首先你需要有一个github的账户 第一步&#xff1a; 打开终端&#xff0c;输入以下命令&#xff0c;注意“your email”处你自己的邮箱&#xff0c;创建新的ssh ssh-keygen -t ed25519 -C “your email” 第二步&#xff1a;使用ssh登录ssh-agent&#xff0c;终端…

【ES6】require、export和import的用法

在JavaScript中&#xff0c;require、export和import是Node.js的模块系统中的关键字&#xff0c;用于处理模块间的依赖关系。 1、require&#xff1a;这是Node.js中引入模块的方法。当你需要使用其他模块提供的功能时&#xff0c;可以使用require关键字来引入该模块。例如&…

恒运资本:股市板块轮动顺口溜?

股市是一个变化多端的场所&#xff0c;不同的板块会因为不同的方针、商场影响、经济形势等多种原因而有不同的体现。因而&#xff0c;不同时期不同板块的轮动也成为了研究的热门。下面咱们就通过一个顺口溜&#xff0c;来深化了解股市板块轮动&#xff1a; “钢铁、水泥、煤炭…

如何在Ubuntu 20.04|18.04上安装 FreeSwitch

如何在Ubuntu 20.04|18.04上安装FreeSwitch 什么是 FreeSwitch PBX&#xff1f;FreeSwitch PBX 系统有哪些功能&#xff1f;开始部署部署前准备开始安装freeswitch 安装完成错误及问题FAQ常见配置文件及说明修改默认端口&#xff1a;防火墙配置账号密码配置/添加新用户freeswit…

Nginx从安装到使用,反向代理,负载均衡

什么是Nginx&#xff1f; 文章目录 什么是Nginx&#xff1f;1、Nginx概述1.1、Nginx介绍1.2、Nginx下载和安装1.3、Nginx目录结构 2、Nginx命令2.1、查看版本2.2、检查配置文件正确性2.3、启动和停止2.4、重新加载配置文件2.5、环境变量的配置 3、Nginx配置文件结构4、Nginx具体…

【ES】笔记-Class类剖析

Class Class介绍与初体验ES5 通过构造函数实例化对象ES6 通过Class中的constructor实列化对象 Class 静态成员实例对象与函数对象的属性不相通实例对象与函数对象原型上的属性是相通的Class中对于static 标注的对象和方法不属于实列对象&#xff0c;属于类。 ES5构造函数继承Cl…

HTML emoji整理 表情符号

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><title>测试</title></head><body><div style"font-size: 50px;">&#128276</div><script>let count 0d…

将Vue项目迁移到微信小程序中

文章目录 一、创建一个Vue.js的应用程序二、构建微信小程序1. 安装微信小程序构建工具2. 在vuejs项目的根目录中创建一个wepy.confgjs文件3. 在vuejs项目的根目录中运行构建 三、错误记录1. 找不到编译器&#xff1a;wepy-compiler-sass 一、创建一个Vue.js的应用程序 使用 Vu…

Linux 系统服务日志查询 journalctl:查询 systemd 日记

journalctl&#xff1a;查询 systemd 日记 systemd 在取代 SUSE Linux Enterprise 12 中的传统 init 脚本时&#xff08;参见第 13 章 “systemd 守护程序”&#xff09;&#xff0c;引入了自身的称为日记的日志记录系统。由于所有系统事件都将写入到日记中&#xff0c;因此&a…

什么是反向代理(Reverse Proxy)?解释反向代理的作用和常见应用。

1、什么是反向代理&#xff08;Reverse Proxy&#xff09;&#xff1f;解释反向代理的作用和常见应用。 反向代理是一种代理服务器模型&#xff0c;它位于客户端和后端服务器之间。它允许将请求转发到后端服务器&#xff0c;并将响应返回给客户端。反向代理的主要作用如下&…

ElementUI浅尝辄止31:Tabs 标签页

选项卡组件&#xff1a;分隔内容上有关联但属于不同类别的数据集合。 常见于网站内容信息分类或app内容信息tab分类 1.如何使用&#xff1f; Tabs 组件提供了选项卡功能&#xff0c;默认选中第一个标签页&#xff0c;你也可以通过 value 属性来指定当前选中的标签页。 <temp…

Geotools对geojson的解析

在 GeoTools 中&#xff0c;对 GeoJSON 的支持是通过一个插件来完成的&#xff0c;用户同样可以在 Maven 的 pom.xml 配置文件中添加下述的依赖。 <dependency><groupId>org.geotools</groupId><artifactId>gt-geojson</artifactId><version&…

3.运行项目

克隆项目 使用安装的git克隆vue2版本的若依项目&#xff0c;博主使用的版本是3.8.6. git clone https://gitee.com/y_project/RuoYi-Vue.git目录结构如下图所示&#xff0c;其中ruoyi-ui是前端的内容&#xff0c;其它均为后端的内容。 配置mysql数据库 在数据库里新建一个…

Android知识点整理

关键点 Activity Fragment 调试应用 处理应用程序配置 Intent 和 Intent 过滤器 会使用Context 后台处理指南 Android 的数据隐私 Android 网络数据安全教程 Android 中的依赖项注入 内容提供程序 Android 内存管理概览 一些重要的库 1.Glide 是一个 Android 上的…

【ARM CoreLink 系列 1 -- CoreLink 系列 产品介绍】

文章目录 ARM CoreLink 介绍ARM CoreLink InterconnectARM CoreLink 处理器外设ARM CoreLink Memory Controllers ARM CoreLink 介绍 ARM的CoreLink系列产品是一套能够进行高效互联的组件和工具&#xff0c;它们用于构建高性能、低功耗的嵌入式和消费电子设备。CoreLink产品系…