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.rhkb.cn/news/493109.html

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

相关文章

胡九道:经典传承(贵宾酒)

胡九道的由来 在辽阔的科尔沁草原上&#xff0c;有一个美丽的女子&#xff0c;她才貌双全&#xff0c;知书达礼&#xff0c;她就是历史上著名的孝庄皇后。大玉儿不仅聪慧过人&#xff0c;而且深具母仪天下的气质&#xff0c;深受百姓和皇室的敬爱。当她跟随丈夫皇太极入关来到…

【Mongo工具】Mongo迁移工具之Mongo-shake

Mongo-Shake 简介 Mongo-Shake 是一个基于 MongoDB 操作日志&#xff08;oplog&#xff09;的通用服务平台。它从源 MongoDB 数据库中获取操作日志&#xff0c;并在目标 MongoDB 数据库中重放&#xff0c;或者通过不同的隧道发送到其他终端。如果目标端是 MongoDB 数据库&…

EGO Swarm翻译

目录 摘要 Ⅰ 介绍 Ⅱ 相关工作 A . 单四旋翼局部规划 B . 拓扑规划 C. 分布式无人机集群 Ⅲ 基于梯度的局部规划隐式拓扑轨迹生成 A.无需ESDF梯度的局部路径规划 B.隐式拓扑轨迹生成 Ⅳ 无人机集群导航 A 机间避碰 B. 定位漂移补偿 C. 从深度图像中去除agent Ⅴ …

虚拟机断网没有网络,需清理内存,删除后再重启

进入NetworkManager可能没权限&#xff0c;设置权限777 to

整合 Knife4j 于 Spring Cloud 网关:实现跨服务的 API 文档统一展示

&#x1f3af;导读&#xff1a;本文档概述了构建和配置基于JDK 17、Spring Boot 3.0.7及Spring Cloud 2022.0.3的微服务系统&#xff0c;特别聚焦于集成Knife4j以增强API文档管理和接口测试功能。文中详细介绍了如何在Spring Boot应用中添加Knife4j依赖、配置Swagger UI路径和A…

使用光耦合器测量电压:实用指南

光耦合器&#xff0c;也称为光隔离器&#xff0c;是用于电气隔离和信号传输的多功能组件。其应用之一是测量电路中的电压。本文介绍了如何利用光耦合器进行电压测量&#xff0c;阐明了其操作和实际用途。 使用光耦合器进行电压测量的工作原理 使用光耦合器进行电压测量依赖于其…

LeetCode刷题day29——动态规划(完全背包)

LeetCode刷题day29——动态规划&#xff08;完全背包&#xff09; 377. 组合总和 Ⅳ分析&#xff1a; 57. 爬楼梯&#xff08;第八期模拟笔试&#xff09;题目描述输入描述输出描述输入示例输出示例提示信息 分析&#xff1a; 322. 零钱兑换分析&#xff1a; 279. 完全平方数分…

【STM32 Modbus编程】-作为主设备写入多个线圈和寄存器

作为主设备写入多个线圈和寄存器 文章目录 作为主设备写入多个线圈和寄存器1、硬件准备与连接1.1 RS485模块介绍1.2 硬件配置与接线1.3 软件准备2、写入多个线圈2.1 数据格式2.2 发送数据2.3 结果3、写入多个寄存器3.1 数据格式3.2 发送数据3.3 结果本文将实现STM32作为ModBus主…

Unity 圆形循环复用滚动列表

一.在上一篇垂直循环复用滚动列表的基础上&#xff0c;扩展延申了圆形循环复用滚动列表。实现此效果需要导入垂直循环复用滚动列表里面的类。 1.基础类 using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using …

【前后端】HTTP网络传输协议

近期更新完毕&#xff0c;建议关注、收藏&#xff01; http请求 URL 严格意义上应该是URI http or https http不加密不安全&#xff1b;https加密协议&#xff08;公网使用&#xff09; http端口号80 https端口号443GET or POST GET和POST是HTTP请求的两种基本方法. 因为POST需…

基于LSB最低有效位的音频水印嵌入提取算法FPGA实现,包含testbench和MATLAB对比

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 vivado2019.2 matlab2022a 3.部分核心程序 &#xff08;完整版代码包含详细中文注释和操作步骤视…

疾风大模型气象系统:精准预报,引领未来

精准预报,引领未来 在当今快速变化的世界中,天气预报已成为日常生活和社会运行中不可或缺的一部分。从规划日常出行到防范极端天气影响,高精准的气象服务正在重新定义我们的生活方式。而在这一领域,疾风大模型气象系统以其卓越的技术实力和领先的预测能力,正引领气象服务…

中间件 redis安装

redis官网地址&#xff1a;Redis - The Real-time Data Platform 环境 CentOS Linux release 7.9.2009 (Core) java version "17.0.12" 2024-07-16 LTS 1、通过压缩包安装redis 1&#xff0c;远程下载redis压缩包&#xff0c;或去官网下载&#xff1a;Downloads …

rfid标签打印开发指导

使用java连接斑马打印机&#xff0c;开发rfid标签打印功能 1.引用斑马打印机的SDKjar包 ZSDK_API.jar 将这个jar文件放到项目的lib目录下&#xff0c;没有就新建一个。 然后点击 File–Project Sreucture–Modules 点击加号 选择对应jar包即可 2.代码开发 1.打印机连接地址…

【笔记】深度学习模型评估指标

推荐链接&#xff1a; &#xff08;0&#xff09;多分类器的评价指标 &#xff08;1&#xff09;泛化误差的评价方法&#xff1a;【机器学习】模型评估与选择&#xff08;留出法、交叉验证法、查全率、查准率、偏差、方差&#xff09; &#xff08;2&#xff09;机器学习&…

【MAC】深入浅出 Homebrew 下 Nginx 的安装与配置指南

硬件&#xff1a;Apple M4 Pro 16寸 系统&#xff1a; macos Sonoma 15.1.1 Nginx 是一款高性能的 Web 服务器和反向代理服务器&#xff0c;广泛应用于全球各地的网站和企业应用中。本文将详细介绍如何在 macOS 环境下使用 Homebrew 安装、启动、管理以及优化配置 Nginx&#x…

OpenCV 学习记录:首篇

最近在学习机器视觉&#xff0c;希望能通过记录博客的形式来鞭策自己坚持学完&#xff0c;同时也把重要的知识点记录下来供参考学习。 1. OpenCV 介绍与模块组成 什么是 OpenCV&#xff1f; OpenCV (Open Source Computer Vision Library) 是一个开源的计算机视觉和机器学习软…

git使用和gitlab部署

1.ci,cd,DevOps ci&#xff1a;持续集成&#xff1a;开发的代码集成到代码仓库 cd&#xff1a;持续交互&#xff1a;从代码仓库拉取代码到部署到测试环境 cd&#xff1a;持续部署&#xff1a;从代码仓库拉取代码到部署到生产环境 DevOps:开发写完的代码自动集成&#xff0c…

数据结构:B树与B+树

工具 数据结构与算法可视化在线演示 m阶 B树有以下特点&#xff1a; B-树&#xff0c;有时又写为B_树&#xff08;其中的-或者_只是连字符&#xff0c;并不读作 B减树&#xff09;&#xff0c;一颗 m 阶(或度)的 B-树&#xff0c;或者本身是空树&#xff0c;否则必须满足以下…

CSDN数据大屏可视化【开源】

项目简介 本次基于版本3 开源 版本3开源地址&#xff1a;https://github.com/nangongchengfeng/CsdnBlogBoard.git 版本1开源地址&#xff1a;https://github.com/nangongchengfeng/CSDash.git 这是一个基于 Python 的 CSDN 博客数据可视化看板项目&#xff0c;通过爬虫采…