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来指示错误原因,例如内存不足等。
    • 示例代码
#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函数)
#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。
    • 示例代码(父子进程间通过管道通信)
#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),就可以进行通信。
    • 示例代码(两个无关进程通过命名管道通信)
// 写进程
#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;
}
// 读进程
#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是要接收的消息类型。
    • 示例代码(两个进程通过消息队列通信)
// 发送消息的进程
#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;
}
// 接收消息的进程
#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时表示删除共享内存段。
    • 示例代码(两个进程通过共享内存通信)
// 创建共享内存并写入数据的进程
#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;
}
// 从共享内存读取数据的进程
#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是操作的个数。
    • 示例代码(使用信号量实现互斥访问共享资源)
// 包含必要的头文件
#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;}// 初始化信号量的值为1arg.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套接字通信示例,服务器端和客户端)**:
- **服务器端代码**:
#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 1024int 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;
}
- **客户端代码**:
#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 1024int 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地址、端口等知识。并且在网络通信中,还需要考虑网络延迟、丢包、字节序等问题。

在这里插入图片描述

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

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

相关文章

ArkTs组件的学习

一. AlphabetIndexer 可以与容器组件联动用于按逻辑结构快速定位容器显示区域的组件 参数名类型必填说明arrayValueArray<string>是字母索引字符串数组&#xff0c;不可设置为空selectednumber是初始选中项索引值若超出索引值范围则取默认值0 class Lxr{tImg:Resource…

51c自动驾驶~合集42

我自己的原文哦~ https://blog.51cto.com/whaosoft/12888355 #DriveMM 六大数据集全部SOTA&#xff01;最新DriveMM&#xff1a;自动驾驶一体化多模态大模型&#xff08;美团&中山大学&#xff09; 近年来&#xff0c;视觉-语言数据和模型在自动驾驶领域引起了广泛关注…

Linux限制root 用户的远程登录(安全要求)

前言&#xff1a;现在基本用户主机都不允许使用root来操作&#xff0c;所以本文通过创建新用户&#xff0c;并限制root用户的ssh来解决这个问题 1. 创建新账户 aingo 首先&#xff0c;使用 root 账户登录系统。 sudo useradd aingo设置 aingo 账户密码&#xff1a; sudo pa…

前端学习笔记-Vue篇-04

4 Vue中的ajax 4.1 解决开发环境Ajax跨域问题 vue脚手架配置代理 配置参考 | Vue CLI方法一&#xff1a;在vue.config.js中添加如下配置: module.exports {devServer: {proxy: http://localhost:4000} } 说明: 1.优点:配置简单&#xff0c;请求资源时直接发给前端(8080)即…

【优选算法篇】位运算小课堂:从入门到精通的奇妙之旅(上篇)

文章目录 须知 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&#xff1…

【Linux】重启系统后开不开机(内核模块丢失问题)

问题 重启后开不开机报错如下&#xff1a; FAILED failed to start load kernel moduiles 可以看到提示module dm_mod not found 缺少了dm_mod 在内核module目录中 reboot重启可以看到这个现象&#xff1a; 可以看到重启启动磁盘&#xff0c;加载不到root 原因 dm_mod模块…

【现代服务端架构】传统服务器 对比 Serverless

在现代开发中&#xff0c;选择合适的架构是至关重要的。两种非常常见的架构模式分别是 传统服务器架构 和 Serverless。它们各有优缺点&#xff0c;适合不同的应用场景。今天&#xff0c;我就带大家一起对比这两种架构&#xff0c;看看它们的差异&#xff0c;并且帮助你选择最适…

搭建Tomcat(四)---Servlet容器

目录 引入 Servlet容器 一、优化MyTomcat ①先将MyTomcat的main函数搬过来&#xff1a; ②将getClass()函数搬过来 ③创建容器 ④连接ServletConfigMapping和MyTomcat 连接&#xff1a; ⑤完整的ServletConfigMapping和MyTomcat方法&#xff1a; a.ServletConfigMappin…

WPF DataTemplate 数据模板

DataTemplate 顾名思义&#xff0c;数据模板&#xff0c;在 wpf 中使用非常频繁。 它一般用在带有 DataTemplate 依赖属性的控件中&#xff0c;如 ContentControl、集合控件 ListBox、ItemsControl 、TabControls 等。 1. 非集合控件中使用 <UserControl.Resources>&l…

Python中exifread库使用

目录 简要介绍 库的安装 使用案例 常见问题 简要介绍 exifread 是一个用于读取图像文件 EXIF 元数据的 Python 库&#xff0c;能够提取图片的隐藏信息&#xff0c;包括经纬度、拍摄时间等信息。 库的安装 使用exifread库首先要确保已经安装 pip install exifread 使用…

clickhouse-数据库引擎

1、数据库引擎和表引擎 数据库引擎默认是Ordinary&#xff0c;在这种数据库下面的表可以是任意类型引擎。 生产环境中常用的表引擎是MergeTree系列&#xff0c;也是官方主推的引擎。 MergeTree是基础引擎&#xff0c;有主键索引、数据分区、数据副本、数据采样、删除和修改等功…

网络安全(3)_安全套接字层SSL

4. 安全套接字层 4.1 安全套接字层&#xff08;SSL&#xff09;和传输层安全&#xff08;TLS&#xff09; &#xff08;1&#xff09;SSL/TLS提供的安全服务 ①SSL服务器鉴别&#xff0c;允许用户证实服务器的身份。支持SSL的客户端通过验证来自服务器的证书&#xff0c;来鉴别…

Starfish 因子开发管理平台快速上手:如何完成策略编写与回测

DolphinDB 开发的因子开发管理平台 Starfish 围绕量化投研的因子、策略开发阶段设计&#xff0c;为用户提供了一个从数据管理、因子研究到策略回测的完整解决方案。 因子平台的回测引擎提供了多个关键的事件函数&#xff0c;涵盖策略初始化、每日盘前和盘后回调、逐笔、快照和…

排序算法(3)——归并排序、计数排序

目录 1. 归并排序 1.1 递归实现 1.2 非递归实现 1.3 归并排序特性总结 2. 计数排序 代码实现 3. 总结 1. 归并排序 基本思想&#xff1a; 归并排序&#xff08;merge sort&#xff09;是建立在归并操作上的一种有效的排序算法&#xff0c;该算法是采用分治法&#xff0…

GIN

gin是什么 Gin 是一个用 Go (Golang) 编写的 HTTP Web 框架。 它具有类似 Martini 的 API&#xff0c;但性能比 Martini 快 40 倍。如果你需要极好的性能&#xff0c;使用 Gin 吧。 特点&#xff1a;gin是golang的net/http库封装的web框架&#xff0c;api友好&#xff0c;注…

基于asp.net游乐园管理系统设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php python(flask Django) 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找…

PH热榜 | 2024-12-16

1. Animate AI 标语&#xff1a;动画系列剧集AI视频生成器 介绍&#xff1a;Animate AI 是一体化AI动画视频生成器&#xff0c;可以快速、轻松制作出动画系列视频。今天就免费开启你的创意之旅吧&#xff01;轻松搞定&#xff01; 产品网站&#xff1a; 立即访问 Product H…

VScode使用教程(菜鸟版)

目录 1.VScode是什么&#xff1f; 2.VScode的下载和安装&#xff1f; 2.1下载和安装 下载路径&#xff1a; 安装流程&#xff1a; 一、点击【Download for Windows】 二、等一小会儿的下载&#xff0c;找到并双击你下载好的.exe文件&#xff0c;开始进入安装进程 三、点…

【前端基础篇】JavaScript之DOM介绍

文章目录 前言WebAPI背景知识什么是WebAPI什么是APIAPI参考文档 DOM基本概念什么是DOMDOM树查找HTML元素方法概览1. document.getElementById(id)2.document.getElementsByTagName(name)3. document.getElementsByClassName(name)4. document.querySelector(CSS选择器)5. docum…

LabVIEW起落架震台检测

在现代飞机制造与维护过程中&#xff0c;起落架的性能测试是保障飞机安全的重要环节。通过LabVIEW开发的起落架小落震台检测系统&#xff0c;通过模拟飞机着陆过程&#xff0c;准确捕捉起落架在着陆时承受的各种动力学特性和应力响应&#xff0c;有效提升起落架设计的精度与可靠…