[Linux] Linux进程间通信(IPC)总结

本文为转载,原文链接

在linux下的多个进程间的通信机制叫做IPC(Inter-Process Communication),它是多个进程之间相互沟通的一种方法。在linux下有多种进程间通信的方法:半双工管道、命名管道、消息队列、信号、信号量、共享内存、内存映射文件,套接字等等。使用这些机制可以为linux下的网络服务器开发提供灵活而又坚固的框架。

管道(pipe)

管道实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读取出来。管道具有如下特点:

  • 管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道;
  • 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程),比如forkexec创建的新进程,在使用exec创建新进程时,需要将管道的文件描述符作为参数传递给exec创建的新进程,当父进程与使用fork创建的子进程直接通信时,发送数据的进程关闭读端,接受数据的进程关闭写端;
  • 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中;
  • 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出,写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

管道的实现机制

管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

管道只能在本地计算机中使用,而不可用于网络间的通信。

代码示例

如下代码示例管道实现父进程和子进程之间的通信:

#include<stdio.h>
#include<unistd.h>

int main()
{
    int fd[2];  // 两个文件描述符
    pid_t pid;
    char buff[20];

    if(pipe(fd) < 0)  // 创建管道
        printf("Create Pipe Error!\n");

    if((pid = fork()) < 0)  // 创建子进程
        printf("Fork Error!\n");
    else if(pid > 0)  // 父进程
    {
        close(fd[0]); // 关闭读端
        write(fd[1], "hello world\n", 12);
    }
    else
    {
        close(fd[1]); // 关闭写端
        read(fd[0], buff, 20);
        printf("%s", buff);
    }

    return 0;
}

通过使用底层的readwrite调用来访问数据。 向fd[1]写数据,从fd[0]中读数据,写入与读取的顺序原则是先进先出。

管道的读写规则

  • 当没有数据可读时:

    • 如果disable管道读端文件描述符的O_NONBLOCK,则read调用阻塞,即进程暂停执行,一直等到有数据来到为止;
    • 如果enable管道读端文件描述符的O_NONBLOCK,则read调用返回-1,errno值为EAGAIN;
  • 当管道满的时候

    • 如果disable管道写端文件描述符的O_NONBLOCK,则write调用阻塞,直到有进程读走数据;
    • 如果enable管道写端文件描述符的O_NONBLOCK,则调用返回-1,errno值为EAGAIN;
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0;

  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE;
  • 当要写入的数据量不大于PIPE_BUF(Posix.1要求PIPE_BUF至少512字节)时,linux将保证写入的原子性;
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性;

命名管道

命名管道是一种特殊类型的文件,它在系统中以文件形式存在,这样克服了管道的弊端,他可以允许没有亲缘关系的进程间通信。

扫描二维码关注公众号,回复: 2569350 查看本文章

创建命名管道有两种方式:

int mkfifo(const char *path, mode_t mode); // 创建一个名字为path的命名管道,参数mode为该文件的权限,若成功则返回0,否则返回-1,错误原因存于errno中。

DESCRIPTION:
mkfifo() creates a new fifo file with name path. The access permissions are specified by mode and restricted by the umask(2) of the calling process.The fifo’s owner ID is set to the process’s effective user ID. The fifo’s group ID is set to that of the parent directory in which it is created.
RETURN VALUES:
A 0 return value indicates success. A -1 return value indicates an error, and an error code is stored in errno.

int mknod(const char *path, mode_t mode, dev_t dev); // 第一个参数表要创建的文件名称,第二个参数表示文件类型,第三个参数表示对应的设备文件的设备号只有当文件类型为 S_IFCHR 或 S_IFBLK 的时候该文件才有设备号,创建普通文件时传入0即可。

// 如下所示为创建管道文件,mode参数指定文件类型为管道文件
mknod(FIFO_FILE,S_IFIFO|0666,0); 

DESCRIPTION:
The device special file path is created with the major and minor device numbers extracted from mode. The access permissions of path are constrained by the umask(2) of the parent process.If mode indicates a block or character special file, dev is a configuration-dependent specification of a character or block I/O device and the superblock of the device. If mode does not indicate a block special or character special device, dev is ignored. mknod() requires super-user privileges.
RETURN VALUES:
Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned and errno is set to indicate the error.

管道和命名管道的区别

对于命名管道FIFO来说,IO操作和普通管道IO操作基本一样,但是两者有一个主要的区别,在命名管道中,管道可以是事先已经创建好的,比如我们在命令行下执行:

mkfifo myfifo

就是创建一个命名通道,我们必须用open函数来显示地建立连接到管道的通道,而在管道中,管道已经在主进程里创建好了,然后在fork时直接复制相关数据或者是用exec创建的新进程时把管道的文件描述符当参数传递进去。

一般来说FIFO和PIPE一样总是处于阻塞状态。也就是说如果命名管道FIFO打开时设置了读权限,则读进程将一直阻塞,一直到其他进程打开该FIFO并向管道写入数据。这个阻塞动作反过来也是成立的。如果不希望命名管道操作的时候发生阻塞,可以在open的时候使用O_NONBLOCK标志,以关闭默认的阻塞操作。

信号(signal)

信号机制是unix系统中最为古老的进程之间的通信机制,用于一个或几个进程之间传递异步信号。信号可以有各种异步事件产生,比如键盘中断等。shell也可以使用信号将作业控制命令传递给它的子进程。

Linux中可以使用signal或sigaction为特定的信号安装信号处理函数,当进程收到该信号2会去执行信号处理函数:

void (*signal(int sig, void (*func)(int)))(int); // signal返回值为void (*)(int)类型的函数指针,接收一个int型的sig参数和void (*func)(int)类型的函数指针

sigaction的定义如下所示:

struct  sigaction {
             union __sigaction_u __sigaction_u;  /* signal handler */
             sigset_t sa_mask;               /* signal mask to apply */
             int     sa_flags;               /* see signal options below */
     };

     union __sigaction_u {
             void    (*__sa_handler)(int);
             void    (*__sa_sigaction)(int, siginfo_t *,
                            void *);
     };

     #define sa_handler      __sigaction_u.__sa_handler
     #define sa_sigaction    __sigaction_u.__sa_sigaction


     int
     sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact);

可以通过如下方式向进程发送信号:

int kill(pid_t pid,int sig); //kill函数向进程号为pid的进程发送信号,信号值为sig。当pid为0时,向当前系统的所有进程发送信号sig。  
int raise(int sig);//向当前进程中自举一个信号sig, 即向当前进程发送信号。  
#include <unistd.h>   
unsigned int alarm(unsigned int seconds); //alarm()用来设置信号SIGALRM在经过参数seconds指定的秒数后传送给目前的进程。如果参数seconds为0,则之前设置的闹钟会被取消,并将剩下的时间返回。使用alarm函数的时候要注意alarm函数的覆盖性,即在一个进程中采用一次alarm函数则该进程之前的alarm函数将失效。  
int pause(void); //使调用进程(或线程)睡眠状态,直到接收到信号,要么终止,或导致它调用一个信号捕获函数。  

消息队列(Message queues)

消息队列是内核地址空间中的内部链表,通过linux内核在各个进程直接传递内容,消息顺序地发送到消息队列中,并以几种不同的方式从队列中获得,每个消息队列可以用IPC标识符唯一地进行识别。内核中的消息队列是通过IPC的标识符来区别,不同的消息队列直接是相互独立的。每个消息队列中的消息,又构成一个独立的链表。

消息队列克服了信号承载信息量少,管道只能承载无格式字符流的缺点。

使用msgget创建消息队列,msgget定义如下,参数可参考man手册:

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

       int msgget(key_t key, int msgflg); 

使用msgsnd和msgrcv向消息队列中发送和接收消息:

      #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
                      int msgflg);

其中msgp参数具有如下结构:

struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[1];    /* message data */
};

结构体中数组mtext的大小由参数msgsz指定。

可以通过msgctl获取消息队列信息或删除消息队列,msgctl定义如下:

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msqid_ds结构体定义如下:

struct msqid_ds {
               struct ipc_perm msg_perm;     /* Ownership and permissions */
               time_t          msg_stime;    /* Time of last msgsnd(2) */
               time_t          msg_rtime;    /* Time of last msgrcv(2) */
               time_t          msg_ctime;    /* Time of last change */
               unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
               msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
               msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
               pid_t           msg_lspid;    /* PID of last msgsnd(2) */
               pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
           };

struct ipc_perm {
               key_t          __key;       /* Key supplied to msgget(2) */
               uid_t          uid;         /* Effective UID of owner */
               gid_t          gid;         /* Effective GID of owner */
               uid_t          cuid;        /* Effective UID of creator */
               gid_t          cgid;        /* Effective GID of creator */
               unsigned short mode;        /* Permissions */
               unsigned short __seq;       /* Sequence number */
           };

消息队列的本质

消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。

与命名管道相比,消息队列的优势在于:

  • 消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。
  • 同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。
  • 接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

信号量

信号量是一种计数器,用于控制对多个进程共享的资源进行的访问。它们常常被用作一个锁机制,在某个进程正在对特定的资源进行操作时,信号量可以防止另一个进程去访问它。

信号量是特殊的变量,它只取正整数值并且只允许对这个值进行两种操作:等待(wait)和信号(signal)。(P、V操作,P用于等待,V用于信号):

  • 如果sv的值大于0,就给它减1;如果它的值等于0,就挂起该进程的执行
  • 如果有其他进程因等待sv而被挂起,就让它恢复运行;如果没有其他进程因等待sv而挂起,则给它加1

简单理解就是P相当于申请资源,V相当于释放资源。

可以通过semget创建信号量:

    // semget函数用于创建一个新的信号量集合 , 或者访问一个现有的集合
    // (不同进程只要key值相同即可访问同一信号量集合)。第一个参数key
    // 是ftok生成的键值,第二个参数num_sems可以指定在新的集合应该创建的信号量的数目
    // ,第三个参数sem_flags是打开信号量的方式。 
    int semget(key_t key, int nsems, int semflg); 
    // 例如
    int semid = semget(key, 0, IPC_CREATE | IPC_EXCL | 0666);//第三个参数参考消息队列int msgget(key_t key,int msgflag);第二个参数。 

通过semop改变信号量的值:

       // semop函数用于改变信号量的值。第二个参数是要在信号集合上执行操作的一个数
       // 组,第三个参数是该数组操作的个数
       int semop(int semid, struct sembuf *sops, size_t nsops);
       // 例如
       //对索引值为0的信号量加一
       struct sembuf sops = {0, +1, IPC_NOWAIT};
       semop(semid, &sops, 1);//以上功能执行的次数为一次

可以通过semctl对信号量集合执行控制操作:

    // semctl函数用于信号量集合执行控制操作,初始化信号量的值,删除一个信号量等。 
    // 类似于调用msgctl(), msgctl()是用于消息队列上的操作。第一个参数是指定的
    // 信号量集合(semget的返回值),第二个参数是要执行操作的信号量在集合中的索
    // 引值(例如集合中第一个信号量下标为0),第三个command参数代表要在集合上
    // 执行的命令,根据第三个参数决定是否有第四个参数。     
    int semctl(int semid, int semnum, int cmd, ...);
    // 其中cmd有如下取值
    IPC_STAT:获取某个集合的semid_ds结构,并把它存储到semun联合体的buf参数指向的地址。  
    IPC_SET:将某个集合的semid_ds结构的ipc_perm成员的值。该命令所取的值是从semun联合体的buf参数中取到。  
    IPC_RMID:内核删除该信号量集合。  
    GETVAL:返回集合中某个信号量的值。  
    SETVAL:把集合中单个信号量的值设置成为联合体val成员的值。

共享内存

共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在该进程的地址空间(这里的地址空间具体是哪个地方?)中。其他进程可以将同一段共享内存连接到自己的地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是malloc分配的一样。如果一个进程向共享内存中写入了数据,所做的改动将立刻被其他进程看到。

共享内存是IPC最快捷的方式,因为共享内存方式的通信没有中间过程,而管道、消息队列等方式则是需要将数据通过中间机制进行转换。共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是同一块的物理空间,仅仅映射到各进程的地址不同而已,因此不需要进行复制,可以直接使用此段空间。

note: 共享内存本身并没有同步机制,需要程序员自己控制。

共享内存函数定义:

// shmget函数用来创建一个新的共享内存段, 或者访问一个现有的共享内存段
//(不同进程只要key值相同即可访问同一共享内存段)。第一个参数key是ftok
// 生成的键值,第二个参数size为共享内存的大小,第三个参数sem_flags是
// 打开共享内存的方式。  
int shmget(key_t key,size_t size,int shmflg);  
// 第三个参数参考消息队列int msgget(key_t key,int msgflag);  
int shmid = shmget(key, 1024, IPC_CREATE | IPC_EXCL | 0666);


// shmat函数通过shm_id将共享内存连接到进程的地址空间中。第二个参数可
// 以由用户指定共享内存映射到进程空间的地址,shm_addr如果为0,则由内
// 核试着查找一个未映射的区域。返回值为共享内存映射的地址。
void *shmat(int shm_id,const void *shm_addr,int shmflg); 
// shmid由shmget获得  
int shmdt(const void *shm_addr); 
// shmdt函数将共享内存从当前进程中分离。 参数为共享内存映射的地址。  
char *shms = (char *)shmat(shmid, 0, 0);eg.shmdt(shms);  


// shmctl函数是控制函数,使用方法和消息队列msgctl()函数调用完全
// 类似。参数一shm_id是共享内存的句柄,cmd是向共享内存发送的命令,
// 最后一个参数buf是向共享内存发送命令的参数。 
int shmctl(int shm_id,int cmd,struct shmid_ds *buf);
// shmid_ds定义如下所示
struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };
struct ipc_perm {
               key_t          __key;    /* Key supplied to shmget(2) */
               uid_t          uid;      /* Effective UID of owner */
               gid_t          gid;      /* Effective GID of owner */
               uid_t          cuid;     /* Effective UID of creator */
               gid_t          cgid;     /* Effective GID of creator */
               unsigned short mode;     /* Permissions + SHM_DEST and
                                           SHM_LOCKED flags */
               unsigned short __seq;    /* Sequence number */
           };

消息队列、信号量以及共享内存的相似之处

它们被统称为XSI IPC,它们在内核中有相似的IPC结构(消息队列的msgid_ds,信号量的semid_ds,共享内存的shmid_ds),而且都用一个非负整数的标识符加以引用(消息队列的msg_id,信号量的sem_id,共享内存的shm_id,分别通过msgget、semget以及shmget获得),标志符是IPC对象的内部名,每个IPC对象都有一个键(key_t key)相关联,将这个键作为该对象的外部名。

XSI IPC和PIPE、FIFO的区别

  • XSI IPC的IPC结构是在系统范围内起作用,没用使用引用计数。如果一个进程创建一个消息队列,并在消息队列中放入几个消息,进程终止后,即使现在已经没有程序使用该消息队列,消息队列及其内容依然保留。而PIPE在最后一个引用管道的进程终止时,管道就被完全删除了。对于FIFO最后一个引用FIFO的进程终止时,虽然FIFO还在系统,但是其中的内容会被删除。
  • 和PIPE、FIFO不一样,XSI IPC不使用文件描述符,所以不能用ls查看IPC对象,不能用rm命令删除,不能用chmod命令删除它们的访问权限。只能使用ipcs和ipcrm来查看可以删除它们。

内存映射(Memory Map)

内存映射文件,是由一个文件到一块内存的映射。内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对文件进行映射。使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作。每一个使用该机制的进程通过把同一个共享的文件映射到自己的进程地址空间来实现多个进程间的通信(这里类似于共享内存,只要有一个进程对这块映射文件的内存进行操作,其他进程也能够马上看到)。

使用内存映射文件不仅可以实现多个进程间的通信,还可以用于处理大文件提高效率。因为我们普通的做法是把磁盘上的文件先拷贝到内核空间的一个缓冲区再拷贝到用户空间(内存),用户修改后再将这些数据拷贝到缓冲区再拷贝到磁盘文件,一共四次拷贝。如果文件数据量很大,拷贝的开销是非常大的。那么问题来了,系统在在进行内存映射文件就不需要数据拷贝?mmap()确实没有进行数据拷贝,真正的拷贝是在在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,所以只进行一次数据拷贝。效率高于read/write。

内存映射函数定义如下:

// mmap函数将一个文件或者其它对象映射进内存。 第一个参数为映射区的开始地址,
// 设置为0表示由系统决定映射区的起始地址,第二个参数为映射的长度,第三个参
// 数为期望的内存保护标志,第四个参数是指定映射对象的类型,第五个参数为文
// 件描述符(指明要映射的文件),第六个参数是被映射对象内容的起点。成功
// 返回被映射区的指针,失败返回MAP_FAILED[其值为(void *)-1]。  
void *mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset); 

// munmap函数用来取消参数start所指的映射内存起始地址,参数length则是欲取消
// 的内存大小。如果解除映射成功则返回0,否则返回-1,错误原因存于errno中
// 错误代码EINVAL。   
int munmap(void* start,size_t length); 

// msync函数实现磁盘文件内容和共享内存内容一致,即同步。第一个参数为文件映
// 射到进程空间的地址,第二个参数为映射空间的大小,第三个参数为刷新的参数设置。
int msync(void *addr,size_t len,int flags); 

共享内存和内存映射文件的区别

  • 内存映射文件是利用虚拟内存把文件映射到进程的地址空间中去,在此之后进程操作文件,就像操作进程空间里的地址一样了,比如使用c语言的memcpy等内存操作的函数。这种方法能够很好的应用在需要频繁处理一个文件或者是一个大文件的场合,这种方式处理IO效率比普通IO效率要高
  • 共享内存是内存映射文件的一种特殊情况,内存映射的是一块内存,而非磁盘上的文件。共享内存的主语是进程(Process),操作系统默认会给每一个进程分配一个内存空间,每一个进程只允许访问操作系统分配给它的哪一段内存,而不能访问其他进程的。而有时候需要在不同进程之间访问同一段内存,怎么办呢?操作系统给出了 创建访问共享内存的API,需要共享内存的进程可以通过这一组定义好的API来访问多个进程之间共有的内存,各个进程访问这一段内存就像访问一个硬盘上的文件一样

内存映射文件与虚拟内存的区别和联系

内存映射文件和虚拟内存都是操作系统内存管理的重要部分,两者有相似点也有不同点。

  • 联系:虚拟内存和内存映射都是将一部分内容加载到内存,另一部放在磁盘上的一种机制。对于用户而言都是透明的。
  • 区别:虚拟内存是硬盘的一部分,是内存和硬盘的数据交换区,许多程序运行过程中把暂时不用的程序数据放入这块虚拟内存,节约内存资源。内存映射是一个文件到一块内存的映射,这样程序通过内存指针就可以对文件进行访问。虚拟内存的硬件基础是分页机制。另外一个基础就是局部性原理(时间局部性和空间局部性),这样就可以将程序的一部分装入内存,其余部分留在外存,当访问信息不存在,再将所需数据调入内存。而内存映射文件并不是局部性,而是使虚拟地址空间的某个区域映射磁盘的全部或部分内容,通过该区域对被映射的磁盘文件进行访问,不必进行文件I/O也不需要对文件内容进行缓冲处理。

套接字(Socket)

套接字机制不但可以单机的不同进程通信,而且使得跨网机器间进程可以通信。 套接字的创建和使用与管道是有区别的,套接字明确地将客户端与服务器区分开来,可以实现多个客户端连到同一服务器。

如下图所示,socket层位于应用层和传输层之间,socket层以下都由操作系统实现的:

这里写图片描述

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

如下图所示为socket编程中服务端和客户端的通信流程:

这里写图片描述

首先,服务器应用程序用socket创建一个套接字,它是系统分配服务器进程的类似文件描述符的资源。 接着,服务器调用bind给套接字命名。这个名字是一个标示符,它允许linux将进入的针对特定端口的连接转到正确的服务器进程。 然后,系统调用listen函数开始接听,等待客户端连接。listen创建一个队列并将其用于存放来自客户端的进入连接。 当客户端调用connect请求连接时,服务器调用accept接受客户端连接,accept此时会创建一个新套接字,用于与这个客户端进行通信。客户端首先调用socket创建一个未命名套接字,然后将服务器的命名套接字作为地址来调用connect与服务器建立连接。 只要双方连接建立成功,我们就可以像操作底层文件一样来操作socket套接字实现通信。

对应的TCP状态转换图如下所示:

这里写图片描述

参考文章

linux基础——linux进程间通信(IPC)机制总结
【Linux编程】进程间通信(IPC)
linux 管道读写规则

猜你喜欢

转载自blog.csdn.net/zkp_java/article/details/80809119