美文网首页
linux用户空间 - 多进程编程(四)

linux用户空间 - 多进程编程(四)

作者: 404Not_Found | 来源:发表于2021-06-12 15:26 被阅读0次
  • 作者: 雪山肥鱼
  • 时间:20210608 23:23
  • 目的:Unix domain Socket and Socket pair
# Unix domain
  ## 关于STREAM 和 DGRAM的 Connect
  ## unix domain 的文件路径
     ## 面向流代码举例
     ## 面向数据报代码举例
  ## asbtract path name
  ## uname path name
#lsof 工具简介

Unix domain

  • Unix domain socket 不同于 internet (address family)domain socket(AF_INET), 他没有ip地址和端口号,而是靠一个文件路径来区分地址的。
    unix socket domain 使用结构体 sockaddr_un
  • 文件路径有三种 man unix 即可查阅
    1. pathname 正常路径
    2. unname 无名 used for socket pair
    3. abstract name 抽象 与2相同,不会与文件系统产生关联
  • 面向流 与 面向数据报
    unix domain 的 面向数据包是可靠的, 保序的,也不会乱序。unix domain 是本机的。


    面向流.png

    面向流的 对于 服务来说,每次connect 一次,就会新生成一个fd。

面向数据报.png

关于 STREAM 和 DRAM 的 connect

  • 面向流的 connect 有个 三次握手的概念,另一端的Server 在accept 等,C端 则取 connect
  • 面向数据报的 connect 并没有三次握手的概念,而是default 的去连谁,报默认发给谁。无需事先建立连接
    • 只要知道路径,大家都可以互相通信
    • connect + send 指名了发给默认
    • 如果没有connect 需要调用 sendto system call

man connect 即可查阅相关区别
If the socket sockfd is of type SOCK_DGRAM then addr is the address to which datagrams are sent by default, and the only address from which datagrams are received. If the socket is of type
SOCK_STREAM or SOCK_SEQPACKET, this call attempts to make a connection to the socket that is bound to the address specified by addr.

pathname 文件路径

面向流的 代码举例:

/*server.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

//定义用于通信的文件名

#define UNIX_DOMAIN "/tmp/UNIX.domain"

int main()
{

    socklen_t clt_addr_len;
    int listen_fd;
    int com_fd;
    int ret;
    int i;
    static char recv_buf[1024];
    int len;
    struct sockaddr_un clt_addr;
    struct sockaddr_un srv_addr;

    //创建用于通信的套接字,通信域为UNIX通信域

    listen_fd=socket(PF_UNIX,SOCK_STREAM,0);
    if(listen_fd<0){
        perror("cannot create listening socket");
        return 1;
    }

    /*
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_prot = htons(SERV_PORT);
    */
    //设置服务器地址参数
    srv_addr.sun_family=AF_UNIX;
    strncpy(srv_addr.sun_path,UNIX_DOMAIN,sizeof(srv_addr.sun_path)-1);

    //绑定套接字与服务器地址信息
    ret=bind(listen_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
    if(ret==-1){
        perror("cannot bind server socket");
        close(listen_fd);
        unlink(UNIX_DOMAIN);
        return 1;

    }

    //对套接字进行监听,判断是否有连接请求
    ret=listen(listen_fd,1);
    if(ret==-1){
        perror("cannot listen the client connect request");
        close(listen_fd);
        unlink(UNIX_DOMAIN);
        return 1;

    }

    //当有连接请求时,调用accept函数建立服务器与客户机之间的连接
    len=sizeof(clt_addr);
    com_fd=accept(listen_fd,(struct sockaddr*)&clt_addr,&len);
    if(com_fd<0){
        perror("cannot accept client connect request");
        close(listen_fd);
        unlink(UNIX_DOMAIN);

        return 1;
    }

    //读取并输出客户端发送过来的连接信息
    printf("\n=====info=====\n");
    for(i=0;i<4;i++){
        memset(recv_buf,0,1024);
        int num=read(com_fd,recv_buf,sizeof(recv_buf));
        printf("Message from client (%d)) :%s\n",num,recv_buf);
    }

    close(com_fd);
    close(listen_fd);

    unlink(UNIX_DOMAIN);

    return 0;
}

/*client.c*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

//定义用于通信的文件名
#define UNIX_DOMAIN "/tmp/UNIX.domain"
int main(void)
{
    int connect_fd;
    int ret;
    char snd_buf[1024];
    int i;

    static struct sockaddr_un srv_addr;
    //创建用于通信的套接字,通信域为UNIX通信域

    connect_fd=socket(PF_UNIX,SOCK_STREAM,0);
    if(connect_fd<0){
        perror("cannot create communication socket");
        return 1;
    }

    srv_addr.sun_family=AF_UNIX;
    strcpy(srv_addr.sun_path,UNIX_DOMAIN);

    //连接服务器
    ret=connect(connect_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
    if(ret==-1){
        perror("cannot connect to the server");
        close(connect_fd);

        return 1;
    }


    memset(snd_buf,0,1024);
    strcpy(snd_buf,"message from client");

    //给服务器发送消息
    for(i=0;i<4;i++)
        write(connect_fd,snd_buf,sizeof(snd_buf));

    close(connect_fd);
    return 0;
}

面向数据报的代码举例

/*server*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

char * server_filename = "/tmp/socket-server";

int main(void)
{
    int s;
    struct sockaddr_un srv_un = {0};

    if ((s = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
        perror("socket server");
        exit(1);
    }

    srv_un.sun_family = AF_UNIX;
    strncpy(srv_un.sun_path, server_filename, sizeof(srv_un.sun_path));
    /*If you leave the file behind when you're finished, or perhaps crash after binding, the next bind will fail
    / with "address in use". Which just means, the file is already there.*/
    unlink(srv_un.sun_path);

    if (bind(s, (struct sockaddr *)&srv_un, sizeof(srv_un)) == -1) {
        perror("bind server");
        exit(1);
    }

    for(;;) {

        char buf[1024] = {0};
        read(s, buf, sizeof(buf));
        printf("RECEIVED: %s", buf);

    }

    close(s);

    return 0;
}

/*client*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

char * server_filename = "/tmp/socket-server";
char * client_filename = "/tmp/socket-client";

int main(void)
{

    int s;
    char obuf[100];
    struct sockaddr_un srv_un, cli_un = { 0 };
    
    srv_un.sun_family = AF_UNIX;
    strncpy(srv_un.sun_path, server_filename, sizeof(srv_un.sun_path));

    cli_un.sun_family = AF_UNIX;
    strncpy(cli_un.sun_path, client_filename, sizeof(cli_un.sun_path));
    unlink(cli_un.sun_path);

    if ((s = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
        perror("socket server");
        exit(1);
    }

    /* (http://stackoverflow.com/questions/3324619/unix-domain-socket-using-datagram-communication-between-one-server-process-and)
    Here, we bind to our client node, and connect to the server node. As Unix domain sockets need to have endpoints on either end
    of the connection. For more info, visit the URL.*/
    if (bind(s, (struct sockaddr *)&cli_un, sizeof(cli_un)) == -1) {
        perror("bind client");
        exit(1);
    }

    if (connect(s, (struct sockaddr *) &srv_un, sizeof(srv_un)) == -1) {
        perror("connect client");
        exit(1);
    }

    //printf("Connected.\n");

    while(printf("> "), fgets(obuf, 100, stdin), !feof(stdin)) {
        /*send 函数并没指名发给谁,因为上面已经有了 connect 默认的 发送对象*/
        if (send(s, obuf, strlen(obuf), 0) == -1) {
            perror("send");
            exit(1);
        }
        break;
    }

    //printf("Sent successfully.\n");

    close(s);

    return 0;
}

  1. 编译运行 server.c
    可以看到 /tmp/UNIX.domain, 文件类型是socket


    UNIX.domain.png
  2. netstat -anp|grep UNIX


    netstat.png
  3. 编译运行 client.c
    随着 server.c 的关闭,/tmp/UNIX.domain 消失, 文件也没了,netstat 也没了


    图片.png
  4. 查看更多信息
    pidof server => lsof pid


    lsof.png

一个socket 对于 一个 fd来讲,无非也就是一个fd

abstract path 文件路径

C/S 都应该使用抽象的路径
实际上就是把第一个字符修改成 '0' 即可。


lsof.png

从路径来看抽象的path 在路径里换成了@

这条路径是与文件系统没有关系的。所以 在/tmp 路径下也看不到 unix.domain

unnmae 无名 即socketpair

int s[2]; /*pair of sockets*/
z = socketpair(AF_LOCAL, SOCKET_STREAM, o, s);

socket pair 与 pipe 相似,但最大不同是 socketpair 是 双工的,pipe 是单工的。
工程中经常约定,一个进程在s[0] 读写,另一个在s[1] 读写

#include <stdio.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <errno.h> 
#include <sys/socket.h> 
#include <stdlib.h> 

const char* str = "Hello World";

int main(int argc, char* argv[]){
    char buf[128] = {0};
    int fd[2]; 
    pid_t pid; 

    if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1 ) { 
        return EXIT_FAILURE; 
    } 

    pid = fork();
    if(pid < 0) {
        return EXIT_FAILURE;
    } else if(pid > 0) {
        close(fd[1]);

        write(fd[0], str, strlen(str));
        read(fd[0], buf, sizeof(buf));
        printf("parent received: %s\n", buf);
    } else if(pid == 0) {
        close(fd[0]);

        read(fd[1], buf, sizeof(buf));  
        write(fd[1], str, strlen(str));
        printf("child received: %s\n",buf);
    }

    while(1);
} 

父子进程彼此发送了 hello world

进程间的通讯,只要是有血缘关系,通信起来就简单很多

lsof 工具简介

  • 看进程打开的文件
  • 看文件被什么进程打开 lsof + 文件名
  • 看IPC
    1. ipcs
    2. lsof | grep shmid
  • 看 socket
    lsof -p 进程ID

相关文章

  • 转载---Binder

    知识储备 Linux进程空间划分 一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & ...

  • Binder(一)Linux进程通信

    用户空间、内核空间 Linux分为内核进程和用户进程:1、内核进程共享一块内存空间,称为内核空间。2、内核进程不能...

  • Android多进程通信之 Binder

    进程空间划分 在 Linux 中一个进程空间可以分为用户空间和内核空间,不同的进程它们的用户空间数据不可共享,但是...

  • 16 Binder-1

    1 Binder 1.1 知识储备 1.1.1 ### Linux进程空间划分 一个进程空间分为 用户空间 & 内...

  • 面试准备——Binder相关

    Linux中的进程通信方式 进程间,用户空间的数据不可共享,所以用户空间相当于私有空间 进程间,内核空间的数据可共...

  • Android进程整理

    init进程(1号进程),是Linux系统的用户空间进程,或者说是Android的第一个用户空间进程。 下面列举常...

  • 进程和计划任务详解(一)

    学习内容: 1、进程相关知识(用户空间、内核空间、进程创建、进程优先级、进程内存)2、Linux进程查看及管理工具...

  • 中级Android开发应该了解的Binder原理

    一、基础概念 Linux的进程空间是相互隔离的。 Linux将内存空间在逻辑上划分为内核空间与用户空间。Linux...

  • Linux 下传统的进程间通信原理

    Linux 下传统的进程间通信原理 在Linux中跨进程通信涉及到几个基本的概念 进程间隔离 进程空间划分:用户空...

  • Binder原理

    Linux进程划分 用户空间内核空间用户空间是不共享的空间,内核空间是共享的空间,所以两个用户空间传递数据就需要内...

网友评论

      本文标题:linux用户空间 - 多进程编程(四)

      本文链接:https://www.haomeiwen.com/subject/ptdseltx.html