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元,相比几百的用的便宜,用的心理踏实…...

【Canvas与技法】椭圆画法
【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>椭圆的画法 Draft2</title><style type"text/css&quo…...

多核CPU调度是咋搞的?
其实很多情况下都有 这样的疑问 为什么多核CPU用着用着会“躺平”? 为什么手机有 8 核,跑分时性能却不是核心数的翻倍? 答案的钥匙,就藏在多核CPU的调度机制里。 为了更直观地理解,以一个《王者荣耀》游戏服务器为例…...

【Jenkins】pipeline 的基础语法以及快速构建一个 jenkinsfile
Jenkins Pipeline 是 Jenkins 中的一个强大功能,可以帮助你实现自动化构建、测试、部署等流程。Jenkins Pipeline 使用一种名为 Pipeline DSL(Domain Specific Language)的脚本语言,通常以 Jenkinsfile 形式存在,用于定…...

工作中如何提高技术实力?
点击“硬核王同学”,选择“关注/三连” 福利干货第一时间送达 大家好,我是硬核王同学。 其实这个问题困扰了我很久啊,不知道你们有没有跟我一样。 如何在工作中如何提高技术实力? 随着时间的增加,越来越觉得工作上…...

画图,matlab,
clear;close all;clc;tic;dirOutput dir(*.dat); % 罗列所有后缀-1.dat的文件列表,罗列BDDATA的数据 filenames string({dirOutput.name}); % 提取文件名%% 丢包统计 FILENAMES [""]; LOSS_YTJ [ ]; LOSS_RAD [ ]; LOSS_ETH [ ]…...

Java虚拟机类加载(解析阶段)[虚方法表的生成以及其存在意义]
class字节码文件中的常量池结构详解-CSDN博客Java虚拟机类加载(解析阶段)-CSDN博客...
电子元器件与电路之-MOS管的介绍和作用
一、基本概念 MOS 管,或MOSFET,全称是Metal-Oxide-Semiconductor Field-Effect Transistor(金属 - 氧化物 - 半导体场效应晶体管)。和三极管利用电流控制电流不同,它是一种利用电场效应来控制电流的半导体器件。和三级…...

python实现word转html
目录 使用mammoth库 使用spire.doc库 使用mammoth库 mammoth库支持将word转为HTML和markdown格式的文件。 import mammothdef word_html(word_file):html_save_name fr{word_file.split(.)[0]}.htmlwith open(word_file, rb) as f:data mammoth.convert_to_html(f)with o…...

nginx模块ngx-fancyindex 隐藏标题中的 / 和遇到的坑
首先下载nginx源码,编译时加上 --add-module/usr/local/src/ngx-fancyindex/ 例如 : ./configure --prefix/usr/local/nginx --with-select_module --with-poll_module --with-threads --with-file-aio --with-http_ssl_module --with-http_v2_module…...

第二十四天 循环神经网络(RNN)LSTM与GRU
LSTM(长短期记忆网络)和GRU(门控循环单元)是两种流行的循环神经网络变体,它们被设计来解决传统RNN在处理长序列数据时遇到的梯度消失和梯度爆炸问题。这两种网络都通过引入门控机制来控制信息的流动,从而能…...