linux-----进程及基本操作
进程的基本概念
- 定义:在Linux系统中,进程是正在执行的一个程序实例,它是资源分配和调度的基本单位。每个进程都有自己独立的地址空间、数据段、代码段、栈以及一组系统资源(如文件描述符、内存等)。
- 进程的组成部分:
- 代码段(Text Segment):包含程序的可执行代码,通常是只读的,多个进程可以共享相同程序的代码段。
- 数据段(Data Segment):存储程序中已初始化的全局变量和静态变量。
- BSS段(Block Started by Symbol):存放程序中未初始化的全局变量和静态变量,在程序加载时,系统会将BSS段初始化为全零。
- 栈(Stack):用于存储函数调用的局部变量、函数参数、返回地址等信息。栈的增长方向是从高地址向低地址。
- 堆(Heap):用于动态分配内存,程序可以在运行时通过
malloc等函数在堆上申请内存,堆的增长方向是从低地址向高地址。
- 进程的创建 -
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;
}
- 进程的终止
- 正常终止方式:
return语句(在main函数中):当main函数执行return语句时,进程会正常终止。返回值可以被父进程获取(如果父进程有获取子进程返回值的机制),用于表示进程的执行结果。exit函数:void exit(int status);,其中status是进程的退出状态码,用于告知父进程本进程的退出状态。exit函数会执行一些清理工作,如关闭文件描述符、刷新标准I/O缓冲区等,然后终止进程。
- 异常终止方式:
abort函数:会导致进程异常终止,并产生一个SIGABRT信号,用于在程序出现严重错误(如无法恢复的错误)时强行终止进程。- 收到信号终止:进程可以接收来自操作系统或其他进程发送的信号而终止。例如,当用户在终端中按下
Ctrl + C时,当前正在运行的进程会收到SIGINT信号,如果进程没有对该信号进行处理,就会终止。
- 正常终止方式:
- 进程等待 -
wait和waitpid函数wait函数:- 函数原型:
pid_t wait(int *status);,其中status是一个指向整数的指针,用于存储子进程的退出状态信息。 - 功能:父进程调用
wait函数会阻塞自己,直到它的一个子进程终止。当子进程终止后,wait函数会返回终止子进程的进程ID,并将子进程的退出状态信息存储在status指向的变量中。
- 函数原型:
waitpid函数:- 函数原型:
pid_t waitpid(pid_t pid, int *status, int options);,其中pid指定要等待的子进程的进程ID,status和wait函数中的作用相同,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;
}
- 进程的执行顺序和调度
- 进程调度器:Linux系统中有进程调度器,它决定哪个进程可以在CPU上运行。调度器会根据一定的算法(如时间片轮转、优先级调度等)来分配CPU时间。
- 优先级和Nice值:每个进程都有一个优先级,优先级高的进程会优先获得CPU时间。可以通过调整进程的
Nice值来间接改变进程的优先级。Nice值的范围是 - 20到19,Nice值越小,优先级越高。可以使用nice命令(用于启动一个具有指定Nice值的进程)和renice命令(用于改变一个正在运行进程的Nice值)来操作进程的Nice值。

- 管道(Pipe)
- 基本概念:
- 管道是一种最基本的进程间通信方式,它是一个单向的、先进先出(FIFO)的数据通道。管道用于连接一个写进程和一个读进程,写进程将数据写入管道的一端,读进程从管道的另一端读取数据。管道通常用于具有亲缘关系(如父子进程)的进程之间通信。
- 创建和使用方式:
- 在Linux系统中,可以使用
pipe函数来创建一个管道。pipe函数的原型为int pipe(int pipefd[2]);,其中pipefd是一个包含两个整数的数组,pipefd[0]用于读取管道数据(管道的读端),pipefd[1]用于向管道写入数据(管道的写端)。成功时返回0,失败时返回 - 1。
- 在Linux系统中,可以使用
- 示例代码(父子进程间通过管道通信):
- 基本概念:
#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;
}
- 特点:
- 简单易用,适用于父子进程等有亲缘关系的进程之间的简单数据传输。但管道是半双工通信方式,数据只能单向流动,如果要实现双向通信,需要创建两个管道。并且管道的容量有限,如果写进程写入数据的速度超过读进程读取数据的速度,管道可能会阻塞。
- 命名管道(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;
}
- 特点:
- 可以在没有亲缘关系的进程之间通信,提供了更灵活的通信方式。但同样是半双工通信,若要双向通信可能需要创建两个命名管道。命名管道在打开进行读或写操作时,如果没有对应的进程进行反向操作(如打开写时没有读进程),可能会阻塞。
- 消息队列(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是要接收的消息类型。
- 在Linux系统中,使用消息队列需要包含
- 示例代码(两个进程通过消息队列通信):
- 基本概念:
// 发送消息的进程
#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;
}
- 特点:
- 消息队列克服了管道和命名管道无格式字节流的缺点,消息有类型,可以按照类型进行接收,增强了通信的灵活性。消息队列可以实现多对多的通信,多个进程可以向同一个消息队列发送和接收消息。但是消息队列的容量有限,当消息队列满时,发送进程可能会阻塞;并且消息队列的实现涉及到系统调用,效率相对较低。
- 共享内存(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);,其中cmd为IPC_RMID时表示删除共享内存段。
- 在Linux系统中,使用共享内存需要包含
- 示例代码(两个进程通过共享内存通信):
- 基本概念:
// 创建共享内存并写入数据的进程
#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;
}
-
共享内存(Shared Memory)特点(续)
- 需要使用信号量或互斥锁等同步机制来保证数据的一致性。例如,当一个进程正在向共享内存写入数据时,另一个进程不能同时进行写入操作,否则可能会出现数据混乱。并且共享内存的分配和管理相对复杂,需要考虑内存的大小、地址映射等问题。
-
信号量(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是操作的个数。
- 在Linux系统中,使用信号量需要包含
- 示例代码(使用信号量实现互斥访问共享资源):
- 基本概念:
// 包含必要的头文件
#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操作(释放资源)的含义以及它们之间的关系。如果使用不当,可能会导致死锁等问题。
- 套接字(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地址、端口等知识。并且在网络通信中,还需要考虑网络延迟、丢包、字节序等问题。

相关文章:
linux-----进程及基本操作
进程的基本概念 定义:在Linux系统中,进程是正在执行的一个程序实例,它是资源分配和调度的基本单位。每个进程都有自己独立的地址空间、数据段、代码段、栈以及一组系统资源(如文件描述符、内存等)。进程的组成部分&am…...
[Python学习日记-73] 面向对象实战1——答题系统
[Python学习日记-73] 面向对象实战1——答题系统 简介 需求模型——5w1h8c 领域模型 设计模型 实现模型 案例:年会答题系统 简介 在学习完面向对象之后你会发现,你还是不会自己做软件做系统,这是非常正常的,这是因为计算机软…...
Win10将WindowsTerminal设置默认终端并添加到右键(无法使用微软商店)
由于公司内网限制,无法通过微软商店安装 Windows Terminal,本指南提供手动安装和配置新版 Windows Terminal 的步骤,并添加右键菜单快捷方式。 1. 下载新版终端安装包: 访问 Windows Terminal 的 GitHub 发布页面:https://githu…...
AOI外观缺陷检测机
主要功能: 快速检测产品装配缺陷,包括螺丝、元器件、端子排线、二维码、一维条码、识别读码、产品外观 Logo缺陷以及产品标签、字符缺陷检测等产品的缺陷检测。 设备优势:1.采用轻型可移动支架,可以快速对接产线工艺工序&am…...
精读 84页华为BLM战略规划方法论
这篇文档主要介绍了华为的BLM战略规划方法论,该方法论旨在帮助企业制定战略规划,并确保战略规划的可执行性和有效性。以下是该文档的核心知识点和重点需要关注的内容: 战略规划的定义:战略规划是企业依据企业外部环境和企业自身的…...
工业摄像机基于电荷耦合器件的相机
工业摄像机系列产品及其识别技术的详细介绍: 一、工业摄像机概述 工业摄像机是利用光学成像技术获取视觉信息,并通过图像处理算法分析这些信息的设备。它通常具有高图像稳定性、高传输能力和高抗干扰能力等特性,适用于各种复杂的工业环境。 …...
13.罗意文面试
1、工程化与架构设计(考察项目管理和架构能力) 1.1 你负责的可视化编排项目中,如何设计组件的数据结构来支持"拖拉拽"功能?如何处理组件间的联动关系? // 组件数据结构示例 {components: [{id: comp1,type…...
xxljob window免安装
gitee地址: https://gitee.com/xuxueli0323/xxl-job idea打开 1、配置maven环境 2、修改数据库连接,网页端口 3、修改执行器中连接的网页端口 右侧-xxljob-生命周期-package 生成: D:\xxx\Gitee\xxl-job\xxl-job-admin\target 目录下 x…...
MariaDB 设置 sql_mode=Oracle 和 Oracle 对比验证
功能Oracle语法MariaDB语法Oracle执行结果MariaDB执行结果创建存储过程未使用参数和变量CREATE PROCEDURE p1 ASBEGINNULL;END p1;/ DELIMITER // CREATE PROCEDURE p1()ISBEGINNULL;END // DELIMITER ; 带有参数和变量CREATE PROCEDURE p1(p_input IN NUMBER, p_output OUT NU…...
【AI驱动的数据结构:包装类的艺术与科学】
🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 💫个人格言:“没有罗马,那就自己创造罗马~” 文章目录 包装类装箱和拆箱阿里巴巴面试题 包装类 在Java中基本数据类型不是继承来自Object,为了…...
初学stm32 --- PWM输出
目录 STM32 PWM工作过程编辑 STM32 PWM工作过程(通道1为例) PWM模式1 & PWM模式2 向上计数配置说明编辑 STM32 定时器3输出通道引脚 自动重载的预装载寄存器 编辑 PWM输出相关库函数 输出比较初始化函数: 设置比较值函数&a…...
ES6学习Iterator遍历器(七)
这里写目录标题 一、概念1.1、遍历器1.2、作用1.3、遍历过程 二、代码学习 一、概念 JavaScript 原有的表示“集合”的数据结构,主要是数组( Array )和对象( Object ),ES6 又添加了 Map 和Set 。这样就有了…...
重建大师软件做任务提示引擎错误?
原因1:打开工程用的本地路径,导致访问失败;解决方案:用网络路径打开工程,重新提交空三。 原因2:引擎主机对工程目录没有访问权限;解决方案:找到相应的引擎主机设置访问权限 重建大…...
【图像分类实用脚本】数据可视化以及高数量类别截断
图像分类时,如果某个类别或者某些类别的数量远大于其他类别的话,模型在计算的时候,更倾向于拟合数量更多的类别;因此,观察类别数量以及对数据量多的类别进行截断是很有必要的。 1.准备数据 数据的格式为图像分类数据集…...
python的is和==运算符
在py中,有两个特别的运算符,is和分别用来判断两个变量是不是相同的和两个变量的值是不是相同。 1. is运算符:用来比较两个对象的身份,即判断两个变量是否指向内存中的同一个对象。 应用场景:1)单例模式&a…...
单节点calico性能优化
在单节点上部署calicov3273后,发现资源占用 修改calico以下配置是资源消耗降低 1、因为是单节点,没有跨节点pod网段组网需要,禁用overlay方式网络(ipip,vxlan),使用route方式网络 配置calico-node的环境变量 CALICO_IPV4POOL_I…...
React 19有哪些新特性?
写在前面 2024.12.5,React 团队在 react.dev/blog 上发表了帖子 react.dev/blog/2024/1… React 19 正式进入了 stable 状态 React 团队介绍了一些新的特性和 Breaking Changes,并提供了升级指南, React 19: 新更新、新特性和新Hooks Reac…...
视频生成缩略图
文章目录 视频生成缩略图使用ffmpeg 视频生成缩略图 最近有个需求,视频上传之后在列表和详情页需要展示缩略图 使用ffmpeg 首先引入jar包 <dependency><groupId>org.bytedeco</groupId><artifactId>javacpp</artifactId><vers…...
页面无滚动条,里面div各自有滚动条
一、双滚动条左右布局 实现效果 实现代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Doc…...
DIY-ESP8266移动PM2.5传感器-带屏幕-APP
本教程将指导您制作一台专业级的空气质量检测仪。这个项目使用经济实惠的ESP8266和PMS5003传感器,配合OLED显示屏,不仅能实时显示PM2.5数值,还能通过手机APP随时查看数据。总成本70元,相比几百的用的便宜,用的心理踏实…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
