linux-----进程及基本操作

进程的基本概念

  • 定义:在Linux系统中,进程是正在执行的一个程序实例,它是资源分配和调度的基本单位。每个进程都有自己独立的地址空间、数据段、代码段、栈以及一组系统资源(如文件描述符、内存等)。
  • 进程的组成部分
    • 代码段(Text Segment):包含程序的可执行代码,通常是只读的,多个进程可以共享相同程序的代码段。
    • 数据段(Data Segment):存储程序中已初始化的全局变量和静态变量。
    • BSS段(Block Started by Symbol):存放程序中未初始化的全局变量和静态变量,在程序加载时,系统会将BSS段初始化为全零。
    • 栈(Stack):用于存储函数调用的局部变量、函数参数、返回地址等信息。栈的增长方向是从高地址向低地址。
    • 堆(Heap) :用于动态分配内存,程序可以在运行时通过malloc等函数在堆上申请内存,堆的增长方向是从低地址向高地址。
  1. 进程的创建 - fork函数
    • 函数原型pid_t fork(void);,其中pid_t是一个整数类型,用于表示进程ID。
    • 工作原理 :当一个进程调用fork函数时,系统会创建一个新的进程,这个新进程几乎是原进程的一个副本。原进程称为父进程,新创建的进程称为子进程。子进程会复制父进程的代码段、数据段、堆和栈等资源。
    • 返回值
      • 在父进程中,fork函数返回新创建的子进程的进程ID。这个ID是一个大于0的值,用于在父进程中区分不同的子进程。
      • 在子进程中,fork函数返回0。
      • 如果fork函数调用失败,会返回 - 1,并设置errno来指示错误原因,例如内存不足等。
    • 示例代码
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
    pid_t pid;
    pid = fork();
    if (pid == -1) {
       perror("fork失败");
       return 1;
    } else if (pid == 0) {
       // 子进程代码
       printf("这是子进程,进程ID为 %d,父进程ID为 %d\n", getpid(), getppid());
    } else {
       // 父进程代码
       printf("这是父进程,进程ID为 %d,子进程ID为 %d\n", getpid(), pid);
    }
    return 0;
}
  1. 进程的终止
    • 正常终止方式
      • return语句(在main函数中) :当main函数执行return语句时,进程会正常终止。返回值可以被父进程获取(如果父进程有获取子进程返回值的机制),用于表示进程的执行结果。
      • exit函数void exit(int status);,其中status是进程的退出状态码,用于告知父进程本进程的退出状态。exit函数会执行一些清理工作,如关闭文件描述符、刷新标准I/O缓冲区等,然后终止进程。
    • 异常终止方式
      • abort函数 :会导致进程异常终止,并产生一个SIGABRT信号,用于在程序出现严重错误(如无法恢复的错误)时强行终止进程。
      • 收到信号终止 :进程可以接收来自操作系统或其他进程发送的信号而终止。例如,当用户在终端中按下Ctrl + C时,当前正在运行的进程会收到SIGINT信号,如果进程没有对该信号进行处理,就会终止。
  2. 进程等待 - waitwaitpid函数
    • wait函数
      • 函数原型pid_t wait(int *status);,其中status是一个指向整数的指针,用于存储子进程的退出状态信息。
      • 功能 :父进程调用wait函数会阻塞自己,直到它的一个子进程终止。当子进程终止后,wait函数会返回终止子进程的进程ID,并将子进程的退出状态信息存储在status指向的变量中。
    • waitpid函数
      • 函数原型pid_t waitpid(pid_t pid, int *status, int options);,其中pid指定要等待的子进程的进程ID,statuswait函数中的作用相同,options用于设置等待选项,如WNOHANG表示如果没有子进程终止就立即返回,不阻塞父进程。
      • 功能 :相比wait函数更加灵活,可以等待指定的子进程,并且可以通过options参数控制等待行为。
    • 示例代码(使用wait函数)
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
    pid_t pid;
    int status;
    pid = fork();
    if (pid == -1) {
       perror("fork失败");
       return 1;
    } else if (pid == 0) {
       // 子进程代码
       printf("子进程开始执行,进程ID为 %d\n", getpid());
       // 模拟子进程执行一些任务后退出
       sleep(2);
       return 42;
    } else {
       // 父进程代码
       printf("父进程等待子进程...\n");
       pid_t terminated_pid = wait(&status);
       if (terminated_pid == -1) {
          perror("wait失败");
          return 1;
       }
       if (WIFEXITED(status)) {
          int exit_status = WEXITSTATUS(status);
          printf("子进程 %d 正常退出,退出状态为 %d\n", terminated_pid, exit_status);
       }
    }
    return 0;
}
  1. 进程的执行顺序和调度
    • 进程调度器:Linux系统中有进程调度器,它决定哪个进程可以在CPU上运行。调度器会根据一定的算法(如时间片轮转、优先级调度等)来分配CPU时间。
    • 优先级和Nice值 :每个进程都有一个优先级,优先级高的进程会优先获得CPU时间。可以通过调整进程的Nice值来间接改变进程的优先级。Nice值的范围是 - 20到19,Nice值越小,优先级越高。可以使用nice命令(用于启动一个具有指定Nice值的进程)和renice命令(用于改变一个正在运行进程的Nice值)来操作进程的Nice值。
  1. 管道(Pipe)
    • 基本概念
      • 管道是一种最基本的进程间通信方式,它是一个单向的、先进先出(FIFO)的数据通道。管道用于连接一个写进程和一个读进程,写进程将数据写入管道的一端,读进程从管道的另一端读取数据。管道通常用于具有亲缘关系(如父子进程)的进程之间通信。
    • 创建和使用方式
      • 在Linux系统中,可以使用pipe函数来创建一个管道。pipe函数的原型为int pipe(int pipefd[2]);,其中pipefd是一个包含两个整数的数组,pipefd[0]用于读取管道数据(管道的读端),pipefd[1]用于向管道写入数据(管道的写端)。成功时返回0,失败时返回 - 1。
    • 示例代码(父子进程间通过管道通信)
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#define BUFFER_SIZE 1024
int main() {
    int pipefd[2];
    pid_t pid;
    char buffer[BUFFER_SIZE];
    // 创建管道
    if (pipe(pipefd) == -1) {
       perror("创建管道失败");
       return 1;
    }
    // 创建子进程
    pid = fork();
    if (pid == -1) {
       perror("创建子进程失败");
       return 1;
    } else if (pid == 0) {
       // 子进程关闭管道写端,从管道读端读取数据
       close(pipefd[1]);
       ssize_t num_read = read(pipefd[0], buffer, sizeof(buffer));
       if (num_read == -1) {
          perror("子进程读取管道数据失败");
          return 1;
       } else if (num_read == 0) {
          printf("管道已关闭,没有数据可读。\n");
       } else {
          buffer[num_read] = '\0';
          printf("子进程读取到的数据:%s\n", buffer);
       }
       close(pipefd[0]);
    } else {
       // 父进程关闭管道读端,向管道写端写入数据
       close(pipefd[0]);
       char data[] = "这是通过管道发送的数据。";
       ssize_t num_written = write(pipefd[1], data, strlen(data));
       if (num_written == -1) {
          perror("父进程向管道写入数据失败");
          return 1;
       }
       printf("父进程向管道写入了 %zd 个字节的数据。\n", num_written);
       close(pipefd[1]);
    }
    return 0;
}
  • 特点
    • 简单易用,适用于父子进程等有亲缘关系的进程之间的简单数据传输。但管道是半双工通信方式,数据只能单向流动,如果要实现双向通信,需要创建两个管道。并且管道的容量有限,如果写进程写入数据的速度超过读进程读取数据的速度,管道可能会阻塞。
  1. 命名管道(Named Pipe或FIFO)
    • 基本概念
      • 命名管道是一种特殊类型的文件,它也提供了一个单向或双向的通信通道。与普通管道不同的是,命名管道有一个文件名,可以被多个没有亲缘关系的进程访问,用于实现这些进程之间的通信。
    • 创建和使用方式
      • 可以使用mkfifo命令(在命令行中)或mkfifo函数(在程序中)来创建一个命名管道。mkfifo函数的原型为int mkfifo(const char *pathname, mode_t mode);,其中pathname是命名管道的文件名,mode是文件的权限模式。成功时返回0,失败时返回 - 1。
      • 一个进程以写方式打开命名管道(使用open函数,打开模式为O_WRONLY),另一个进程以读方式打开命名管道(打开模式为O_RDONLY),就可以进行通信。
    • 示例代码(两个无关进程通过命名管道通信)
c 复制代码
// 写进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 1024
int main() {
    int fd;
    char data[] = "这是通过命名管道发送的数据。";
    // 创建命名管道(如果不存在)
    if (mkfifo("myfifo", 0666) == -1 && errno!= EEXIST) {
       perror("创建命名管道失败");
       return 1;
    }
    // 以写方式打开命名管道
    fd = open("myfifo", O_WRONLY);
    if (fd == -1) {
       perror("打开命名管道(写)失败");
       return 1;
    }
    ssize_t num_written = write(fd, data, strlen(data));
    if (num_written == -1) {
       perror("向命名管道写入数据失败");
       close(fd);
       return 1;
    }
    printf("向命名管道写入了 %zd 个字节的数据。\n", num_written);
    close(fd);
    return 0;
}
c 复制代码
// 读进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 1024
int main() {
    int fd;
    char buffer[BUFFER_SIZE];
    // 以读方式打开命名管道
    fd = open("myfifo", O_RDONLY);
    if (fd == -1) {
       perror("打开命名管道(读)失败");
       return 1;
    }
    ssize_t num_read = read(fd, buffer, sizeof(buffer));
    if (num_read == -1) {
       perror("从命名管道读取数据失败");
       close(fd);
       return 1;
    } else if (num_read == 0) {
       printf("命名管道已关闭,没有数据可读。\n");
    } else {
       buffer[num_read] = '\0';
       printf("从命名管道读取到的数据:%s\n", buffer);
    }
    close(fd);
    return 0;
}
  • 特点
    • 可以在没有亲缘关系的进程之间通信,提供了更灵活的通信方式。但同样是半双工通信,若要双向通信可能需要创建两个命名管道。命名管道在打开进行读或写操作时,如果没有对应的进程进行反向操作(如打开写时没有读进程),可能会阻塞。
  1. 消息队列(Message Queue)
    • 基本概念
      • 消息队列是一个由内核维护的消息链表,它允许不同进程通过发送和接收消息来进行通信。消息队列中的每个消息都有一个特定的类型,可以根据类型来接收消息,使得进程可以选择性地获取自己感兴趣的消息。
    • 创建和使用方式
      • 在Linux系统中,使用消息队列需要包含<sys/types.h><sys/ipc.h><sys/msg.h>头文件。首先,使用msgget函数来创建或获取一个消息队列标识符。msgget函数的原型为int msgget(key_t key, int msgflg);,其中key是一个键值,用于唯一标识消息队列,msgflg用于指定创建或访问消息队列的标志。
      • 进程可以使用msgsnd函数向消息队列发送消息,msgsnd函数的原型为int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);,其中msqid是消息队列标识符,msgp是指向消息结构体的指针,msgsz是消息的长度,msgflg是发送标志。
      • 接收消息可以使用msgrcv函数,其原型为int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);,其中msgtyp是要接收的消息类型。
    • 示例代码(两个进程通过消息队列通信)
c 复制代码
// 发送消息的进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#define MAX_TEXT 512
struct my_msg {
    long int my_msg_type;
    char some_text[MAX_TEXT];
};
int main() {
    int msqid;
    key_t key;
    struct my_msg some_data;
    // 生成一个唯一的键值
    if ((key = ftok(".", 'a')) == -1) {
       perror("生成键值失败");
       return 1;
    }
    // 创建或获取消息队列
    if ((msqid = msgget(key, 0666 | IPC_CREAT)) == -1) {
       perror("获取消息队列失败");
       return 1;
    }
    // 设置要发送的消息类型和内容
    some_data.my_msg_type = 1;
    strcpy(some_data.some_text, "这是通过消息队列发送的消息。");
    // 发送消息
    if (msgsnd(msqid, &some_data, sizeof(some_data.some_text), 0) == -1) {
       perror("发送消息失败");
       return 1;
    }
    printf("消息已发送。\n");
    return 0;
}
c 复制代码
// 接收消息的进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#define MAX_TEXT 512
struct my_msg {
    long int my_msg_type;
    char some_text[MAX_TEXT];
};
int main() {
    int msqid;
    key_t key;
    struct my_msg some_data;
    // 生成一个唯一的键值
    if ((key = ftok(".", 'a')) == -1) {
       perror("生成键值失败");
       return 1;
    }
    // 创建或获取消息队列
    if ((msqid = msgget(key, 0666 | IPC_CREAT)) == -1) {
       perror("获取消息队列失败");
       return 1;
    }
    // 接收消息类型为1的消息
    if (msgrcv(msqid, &some_data, sizeof(some_data.some_text), 1, 0) == -1) {
       perror("接收消息失败");
       return 1;
    }
    printf("接收到的消息:%s\n", some_data.some_text);
    // 标记消息队列可以被删除(当所有进程都不再使用时)
    if (msgctl(msqid, IPC_RMID, NULL) == -1) {
       perror("删除消息队列标记失败");
       return 1;
    }
    return 0;
}
  • 特点
    • 消息队列克服了管道和命名管道无格式字节流的缺点,消息有类型,可以按照类型进行接收,增强了通信的灵活性。消息队列可以实现多对多的通信,多个进程可以向同一个消息队列发送和接收消息。但是消息队列的容量有限,当消息队列满时,发送进程可能会阻塞;并且消息队列的实现涉及到系统调用,效率相对较低。
  1. 共享内存(Shared Memory)
    • 基本概念
      • 共享内存是一种高效的进程间通信方式,它允许两个或多个进程共享一段物理内存区域。进程可以像访问自己的内存一样访问共享内存区域,这使得数据的传输速度非常快,因为不需要进行数据的复制操作(如管道和消息队列需要在用户空间和内核空间之间复制数据)。
    • 创建和使用方式
      • 在Linux系统中,使用共享内存需要包含<sys/types.h><sys/ipc.h><sys/shm.h>头文件。首先,使用shmget函数来创建或获取一个共享内存段标识符。shmget函数的原型为int shmget(key_t key, size_t size, int shmflg);,其中key是一个键值,size是共享内存段的大小,shmflg是创建或访问标志。
      • 然后,使用shmat函数将共享内存段连接到进程的地址空间。shmat函数的原型为void *shmat(int shmid, const void *shmaddr, int shmflg);,其中shmid是共享内存段标识符,shmaddr是指定连接的地址(通常为NULL,让系统自动选择地址),shmflg是连接标志。
      • 进程使用完共享内存后,需要使用shmdt函数将共享内存段从进程的地址空间分离,shmdt函数的原型为int shmdt(const void *shmaddr);,其中shmaddr是之前shmat函数返回的地址。最后,使用shmctl函数来标记共享内存段可以被删除(当所有进程都不再使用时),shmctl函数的原型为int shmctl(int shmid, int cmd, struct shmid_ds *buf);,其中cmdIPC_RMID时表示删除共享内存段。
    • 示例代码(两个进程通过共享内存通信)
c 复制代码
// 创建共享内存并写入数据的进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#define BUFFER_SIZE 1024
int main() {
    int shmid;
    key_t key;
    char *shared_memory;
    // 生成一个唯一的键值
    if ((key = ftok(".", 'a')) == -1) {
       perror("生成键值失败");
       return 1;
    }
    // 创建共享内存段
    if ((shmid = shmget(key, BUFFER_SIZE, 0666 | IPC_CREAT)) == -1) {
       perror("获取共享内存段失败");
       return 1;
    }
    // 将共享内存段连接到进程的地址空间
    if ((shared_memory = (char *)shmat(shmid, NULL, 0)) == NULL) {
       perror("连接共享内存段失败");
       return 1;
    }
    // 写入数据到共享内存
    strcpy(shared_memory, "这是通过共享内存写入的数据。");
    // 将共享内存段从进程的地址空间分离
    if (shmdt(shared_memory) == -1) {
       perror("分离共享内存段失败");
       return 1;
    }
    return 0;
}
c 复制代码
// 从共享内存读取数据的进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#define BUFFER_SIZE 1024
int main() {
    int shmid;
    key_t key;
    char *shared_memory;
    // 生成一个唯一的键值
    if ((key = ftok(".", 'a')) == -1) {
       perror("生成键值失败");
       return 1;
    }
    // 获取共享内存段
    if ((shmid = shmget(key, BUFFER_SIZE, 0666 | IPC_CREAT)) == -1) {
       perror("获取共享内存段失败");
       return 1;
    }
    // 将共享内存段连接到进程的地址空间
    if ((shared_memory = (char *)shmat(shmid, NULL, 0)) == NULL) {
       perror("连接共享内存段失败");
       return 1;
    }
    // 读取共享内存中的数据
    printf("从共享内存读取到的数据:%s\n", shared_memory);
    // 将共享内存段从进程的地址空间分离
    if (shmdt(shared_memory) == -1) {
       perror("分离共享内存段失败");
       return 1;
    }
    // 标记共享内存段可以被删除
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
       perror("删除共享内存段标记失败");
       return 1;
    }
    return 0;
}
  1. 共享内存(Shared Memory)特点(续)

    • 需要使用信号量或互斥锁等同步机制来保证数据的一致性。例如,当一个进程正在向共享内存写入数据时,另一个进程不能同时进行写入操作,否则可能会出现数据混乱。并且共享内存的分配和管理相对复杂,需要考虑内存的大小、地址映射等问题。
  2. 信号量(Semaphore)

    • 基本概念
      • 信号量是一个计数器,用于控制多个进程对共享资源的访问。它可以实现进程间的同步和互斥。信号量的值表示可用资源的数量,当信号量的值大于0时,表示有可用资源;当信号量的值等于0时,表示没有可用资源,此时请求资源的进程会被阻塞。
    • 创建和使用方式
      • 在Linux系统中,使用信号量需要包含<sys/types.h><sys/ipc.h><sys/sem.h>头文件。首先,使用semget函数创建或获取一个信号量集标识符。semget函数的原型为int semget(key_t key, int nsems, int semflg);,其中key是一个键值,nsems是信号量集中信号量的个数(通常为1),semflg是创建或访问标志。
      • 然后,使用semctl函数对信号量进行初始化等操作。semctl函数的原型为int semctl(int semid, int semnum, int cmd,...);,其中semid是信号量集标识符,semnum是信号量在集合中的编号(对于只有一个信号量的集合,通常为0),cmd是操作命令,如SETVAL用于设置信号量的值。
      • 进程可以使用semop函数来操作信号量(如对信号量进行P操作和V操作)。semop函数的原型为int semop(int semid, struct sembuf *sops, unsigned nsops);,其中sops是一个指向struct sembuf结构体数组的指针,struct sembuf结构体包含信号量操作的相关信息,如操作类型(-1表示P操作,+1表示V操作)、信号量编号等,nsops是操作的个数。
    • 示例代码(使用信号量实现互斥访问共享资源)
c 复制代码
// 包含必要的头文件
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <unistd.h>
#include <string.h>

#define BUFFER_SIZE 1024

// 定义联合体,用于semctl函数的参数传递
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *__buf;
};

// 信号量P操作函数
void semaphore_p(int semid) {
    struct sembuf sop;
    sop.sem_num = 0;
    sop.sem_op = -1;
    sop.sem_flg = 0;
    if (semop(semid, &sop, 1) == -1) {
        perror("P操作失败");
        exit(1);
    }
}

// 信号量V操作函数
void semaphore_v(int semid) {
    struct sembuf sop;
    sop.sem_num = 0;
    sop.sem_op = 1;
    sop.sem_flg = 0;
    if (semop(semid, &sop, 1) == -1) {
        perror("V操作失败");
        exit(1);
    }
}

// 主函数
int main() {
    int shmid, semid;
    key_t key;
    char *shared_memory;
    union semun arg;
    // 生成一个唯一的键值
    if ((key = ftok(".", 'a')) == -1) {
        perror("生成键值失败");
        return 1;
    }
    // 创建共享内存段
    if ((shmid = shmget(key, BUFFER_SIZE, 0666 | IPC_CREAT)) == -1) {
        perror("获取共享内存段失败");
        return 1;
    }
    // 将共享内存段连接到进程的地址空间
    if ((shared_memory = (char *)shmat(shmid, NULL, 0)) == NULL) {
        perror("连接共享内存段失败");
        return 1;
    }
    // 创建信号量
    if ((semid = semget(key, 1, 0666 | IPC_CREAT)) == -1) {
        perror("获取信号量失败");
        return 1;
    }
    // 初始化信号量的值为1
    arg.val = 1;
    if (semctl(semid, 0, SETVAL, arg) == -1) {
        perror("初始化信号量失败");
        return 1;
    }
    pid_t pid = fork();
    if (pid == -1) {
        perror("创建子进程失败");
        return 1;
    } else if (pid == 0) {
        // 子进程
        semaphore_p(semid);
        strcpy(shared_memory, "这是子进程写入共享内存的数据。");
        semaphore_v(semid);
        // 将共享内存段从进程的地址空间分离
        if (shmdt(shared_memory) == -1) {
            perror("子进程分离共享内存段失败");
            return 1;
        }
    } else {
        // 父进程
        semaphore_p(semid);
        printf("父进程从共享内存读取到的数据:%s\n", shared_memory);
        semaphore_v(semid);
        // 将共享内存段从进程的地址空间分离
        if (shmdt(shared_memory) == -1) {
            perror("父进程分离共享内存段失败");
            return 1;
        }
        // 标记共享内存段可以被删除
        if (shmctl(shmid, IPC_RMID, NULL) == -1) {
            perror("删除共享内存段标记失败");
            return 1;
        }
        // 标记信号量可以被删除
        if (semctl(semid, 0, IPC_RMID, NULL) == -1) {
            perror("删除信号量标记失败");
            return 1;
        }
    }
    return 0;
}
- **特点**:
    - 信号量主要用于进程间的同步和互斥,能够有效地控制对共享资源的访问。通过合理设置信号量的初始值和操作方式,可以实现复杂的进程同步场景。但是信号量的使用比较复杂,需要正确理解P操作(等待资源)和V操作(释放资源)的含义以及它们之间的关系。如果使用不当,可能会导致死锁等问题。
  1. 套接字(Socket)
    • 基本概念
      • 套接字是一种更为通用的进程间通信方式,它不仅可以用于同一台计算机上的进程通信,还可以用于不同计算机之间(通过网络)的进程通信。套接字提供了一种基于网络协议(如TCP/IP)的通信接口,使得进程可以像读写文件一样进行网络通信。
- **创建和使用方式**:
    - 在Linux系统中,使用套接字需要包含`<sys/types.h>`、`<sys/socket.h>`头文件。首先,使用`socket`函数创建一个套接字。`socket`函数的原型为`int socket(int domain, int type, int protocol);`,其中`domain`表示套接字使用的协议族(如`AF_INET`表示IPv4协议族),`type`表示套接字类型(如`SOCK_STREAM`表示面向连接的TCP套接字,`SOCK_DGRAM`表示无连接的UDP套接字),`protocol`表示协议(通常为0,表示使用默认协议)。
    - 对于基于TCP的套接字通信,服务器端需要使用`bind`函数将套接字绑定到一个本地地址和端口,`bind`函数的原型为`int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);`,其中`sockfd`是套接字描述符,`addr`是指向`sockaddr`结构体(或其变体,如`sockaddr_in`用于IPv4地址)的指针,`addrlen`是地址结构体的长度。
    - 然后,服务器端使用`listen`函数监听端口,等待客户端连接。`listen`函数的原型为`int listen(int sockfd, int backlog);`,其中`backlog`表示等待连接队列的最大长度。
    - 客户端使用`connect`函数连接到服务器。`connect`函数的原型为`int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);`,其中参数含义与`bind`函数类似。
    - 服务器端接受客户端连接使用`accept`函数,`accept`函数的原型为`int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);`,它会返回一个新的套接字描述符,用于与客户端进行通信。
    - 通信过程中,使用`send`(用于TCP套接字)或`sendto`(用于UDP套接字)函数发送数据,使用`recv`(用于TCP套接字)或`recvfrom`(用于UDP套接字)函数接收数据。
- **示例代码(简单的TCP套接字通信示例,服务器端和客户端)**:
- **服务器端代码**:
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>

#define PORT 8888
#define BUFFER_SIZE 1024

int main() {
    int server_socket, client_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len;
    char buffer[BUFFER_SIZE];
    // 创建套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("创建服务器套接字失败");
        return 1;
    }
    // 初始化服务器地址结构体
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    // 绑定套接字到本地地址和端口
    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定服务器套接字失败");
        close(server_socket);
        return 1;
    }
    // 监听端口
    if (listen(server_socket, 5) == -1) {
        perror("监听端口失败");
        close(server_socket);
        return 1;
    }
    printf("服务器正在等待客户端连接...\n");
    // 接受客户端连接
    client_addr_len = sizeof(client_addr);
    client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);
    if (client_socket == -1) {
        perror("接受客户端连接失败");
        close(server_socket);
        return 1;
    }
    printf("客户端已连接。\n");
    // 接收客户端发送的数据
    ssize_t num_read = recv(client_socket, buffer, sizeof(buffer), 0);
    if (num_read == -1) {
        perror("接收客户端数据失败");
        close(client_socket);
        close(server_socket);
        return 1;
    } else if (num_read == 0) {
        printf("客户端已断开连接。\n");
    } else {
        buffer[num_read] = '\0';
        printf("接收到客户端发送的数据:%s\n", buffer);
    }
    // 发送响应数据给客户端
    char response[] = "这是服务器的响应数据。";
    ssize_t num_written = send(client_socket, response, strlen(response), 0);
    if (num_written == -1) {
        perror("发送响应数据给客户端失败");
        close(client_socket);
        close(server_socket);
        return 1;
    }
    // 关闭套接字
    close(client_socket);
    close(server_socket);
    return 0;
}
- **客户端代码**:
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>

#define PORT 8888
#define BUFFER_SIZE 1024

int main() {
    int client_socket;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];
    // 创建套接字
    client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (client_socket == -1) {
        perror("创建客户端套接字失败");
        return 1;
    }
    // 初始化服务器地址结构体
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    // 连接服务器
    if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("连接服务器失败");
        close(client_socket);
        return 1;
    }
    // 发送数据给服务器
    char data[] = "这是客户端发送的数据。";
    ssize_t num_written = send(client_socket, data, strlen(data), 0);
    if (num_written == -1) {
        perror("发送数据给服务器失败");
        close(client_socket);
        return 1;
    }
    // 接收服务器响应的数据
    ssize_t num_read = recv(client_socket, buffer, sizeof(buffer), 0);
    if (num_read == -1) {
        perror("接收服务器响应数据失败");
        close(client_socket);
        return 1;
    } else if (num_read == 0) {
        printf("服务器已断开连接。\n");
    } else {
        buffer[num_read] = '\0';
        printf("接收到服务器响应的数据:%s\n", buffer);
    }
    // 关闭套接字
    close(client_socket);
    return 0;
}
- **特点**:
    - 套接字通信功能强大,应用范围广泛,可以实现本地进程间通信和网络进程间通信。但套接字编程相对复杂,需要了解网络协议、IP地址、端口等知识。并且在网络通信中,还需要考虑网络延迟、丢包、字节序等问题。
相关推荐
这题怎么做?!?27 分钟前
ARP协议及其具体过程
运维·服务器·网络
无线认证x英利检测31 分钟前
进网许可认证、交换路由设备检测项目更新25年1月起
网络·智能路由器
卡卡大怪兽33 分钟前
fastAPI接口的请求与响应——基础
服务器·网络·fastapi
王三三34 分钟前
群晖利用acme.sh自动申请证书并且自动重载证书的问题解决
linux·自动化·证书·群晖·acme·acme.sh·lets encrypt
路飞雪吖~37 分钟前
【Linux】进程控制
linux·运维·服务器
wy02_40 分钟前
Linux基本命令
linux·运维
qq_4592384944 分钟前
Linux之压缩解压相关命令
linux
昌sit!1 小时前
监控IP频繁登录服务器脚本
服务器·网络·tcp/ip
北京华人开创公司1 小时前
京准电钟:电厂自控NTP时间同步服务器技术方案
运维·服务器·卫星时钟服务器·ntp时间服务器·时间同步服务器·网络时间服务器·北斗授时服务器
慕羽★1 小时前
详细介绍如何使用rapidjson读取json文件
linux·c++·windows·json·file·param·rapidjson