问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

进程间的六种通信方式之socket

创作时间:
作者:
@小白创作中心

进程间的六种通信方式之socket

引用
CSDN
1.
https://blog.csdn.net/ZHNEYU/article/details/126086778

进程间的通信方式多种多样,其中socket通信是一种非常重要的方式。它不仅可以实现跨网络的主机间通信,还可以在同主机上进行进程间通信。本文将详细介绍socket通信的创建、编程模型以及具体实现方式。

socket通信概述

前面提到的管道和信号(还有未写的消息队列、共享内存、信号量)都是在同一台主机上进行进程间通信。而要实现跨网络与不同主机上的进程之间通信,就需要使用Socket通信。

实际上,Socket通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信。

我们来看看创建socket的系统调用:

int socket(int domain, int type, int protocal)

三个参数分别代表:

  • domain参数用来指定协议族,比如AF_INET用于IPV4、AF_INET6用于IPV6、AF_LOCAL/AF_UNIX用于本机;
  • type参数用来指定通信特性,比如SOCK_STREAM表示的是字节流,对应TCP、SOCK_DGRAM表示的是数据报,对应UDP、SOCK_RAW表示的是原始套接字;
  • protocal参数原本是用来指定通信协议的,但现在基本废弃。因为协议已经通过前面两个参数指定完成,protocol目前一般写成0即可;

根据创建socket类型的不同,通信的方式也就不同:

  • 实现TCP字节流通信:socket类型是AF_INET和SOCK_STREAM;
  • 实现UDP数据报通信:socket类型是AF_INET和SOCK_DGRAM;
  • 实现本地进程间通信:「本地字节流socket」类型是AF_LOCAL和SOCK_STREAM,「本地数据报socket」类型是AF_LOCAL和SOCK_DGRAM。另外,AF_UNIX和AF_LOCAL是等价的,所以AF_UNIX也属于本地socket;

接下来,简单说一下这三种通信的编程模式。

TCP协议通信的socket编程模型

  • 服务端和客户端初始化socket,得到文件描述符;
  • 服务端调用bind,将绑定在IP地址和端口;
  • 服务端调用listen,进行监听;
  • 服务端调用accept,等待客户端连接;
  • 客户端调用connect,向服务器端的地址和端口发起连接请求;
  • 服务端accept返回用于传输的socket的文件描述符;
  • 客户端调用write写入数据;服务端调用read读取数据;
  • 客户端断开连接时,会调用close,那么服务端read读取数据的时候,就会读取到了EOF,待处理完数据后,服务端调用close,表示连接关闭。

这里需要注意的是,服务端调用accept时,连接成功了会返回一个已完成连接的socket,后续用来传输数据。所以,监听的socket和真正用来传送数据的socket,是「两个」socket,一个叫作监听socket,一个叫作已完成连接socket。成功连接建立之后,双方开始通过read和write函数来读写数据,就像往一个文件流里面写东西一样。

UDP协议通信的socket编程模型

UDP是没有连接的,所以不需要三次握手,也就不需要像TCP调用listen和connect,但是UDP的交互仍然需要IP地址和端口号,因此也需要bind。对于UDP来说,不需要要维护连接,那么也就没有所谓的发送方和接收方,甚至都不存在客户端和服务端的概念,只要有一个socket多台机器就可以任意通信,因此每一个UDP的socket都需要bind。另外,每次通信时,调用sendto和recvfrom,都要传入目标主机的IP地址和端口。

本地进程间通信的socket编程模型

本地socket被用于在同一台主机上进程间通信的场景:

  • 本地socket的编程接口和IPv4、IPv6套接字编程接口是一致的,可以支持「字节流」和「数据报」两种协议;
  • 本地socket的实现效率大大高于IPv4和IPv6的字节流、数据报socket实现;

对于本地字节流socket,其socket类型是AF_LOCAL和SOCK_STREAM。
对于本地数据报socket,其socket类型是AF_LOCAL和SOCK_DGRAM。
本地字节流socket和本地数据报socket在bind的时候,不像TCP和UDP要绑定IP地址和端口,而是绑定一个本地文件,这也就是它们之间的最大区别。

代码示例

server.c

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/un.h>
#define FILE_NAME "socket.log"
#define MSG     "OK, I get it!"
int main(int argc, char *argv[])
{
    int                     listen_fd, client_fd;
    int                     rv = -1;
    int                     on = 1;
    socklen_t               len = 32;
    char                    buf[64];
    struct  sockaddr_un     serv_addr, cli_addr;
    listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(listen_fd < 0)
    {
        printf("create socket failure: %s\n", strerror(errno));
        return -1;
    }
    printf("create socket[fd:%d] scuess\n", listen_fd);
    if(!access(FILE_NAME, F_OK))
    {
        unlink(FILE_NAME);
    }
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strncpy(serv_addr.sun_path, FILE_NAME, sizeof(serv_addr.sun_path)-1);//这里绑定的是文件
    if(bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        printf("bind() failure: %s\n", strerror(errno));
        unlink(FILE_NAME);
        return -1;
    }
    if(listen(listen_fd, 13) < 0)
    {
        printf("listen error: %s\n", strerror(errno));
        return -1;
    }
    while(1)
    {
        client_fd = accept(listen_fd, (struct sockaddr*)&cli_addr, &len);
        if(client_fd < 0)
        {
            printf("accept() failure: %s\n", strerror(errno));
            return -1;
        }
        memset(buf, 0, sizeof(buf));
        rv = read(client_fd, buf, sizeof(buf));
        if(rv < 0)
        {
            printf("read() failure: %s\n", strerror(errno));
            return -1;
        }
        else if(0 == rv)
        {
            printf("client_fd get disconnected...\n");
            return -1;
        }
        printf("read %d bytes data: %s\n", rv, buf);
        if(write(client_fd, MSG, strlen(MSG)) < 0)
        {
            printf("write() failure: %s\n", strerror(errno));
            return -1;
        }
        close(client_fd);
    }
    close(listen_fd);
}

clinet.c

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/un.h>
#define FILE_NAME "socket.log"
#define MSG     "Hello, unix server socket!"
int main(int argc, char *argv[])
{
    int                     con_fd;
    int                                     rv = -1;
    char                                    buf[64];
    struct  sockaddr_un     serv_addr;
    con_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(con_fd < 0)
    {
            printf("create socket failure: %s\n", strerror(errno));
            return -1;
    }
    printf("create socket[fd:%d] scuess\n", con_fd);
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strncpy(serv_addr.sun_path, FILE_NAME, sizeof(serv_addr.sun_path)-1);
    if(connect(con_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
    {
            printf("connect() failure: %s\n", strerror(errno));
            return -1;
    }
    //printf("connect server on '%s' success!\n", FILE_NAME);
    if(write(con_fd, MSG, strlen(MSG)) < 0)
    {
            printf("write() failure: %s\n", strerror(errno));
            return -1;
    }
    memset(buf, 0, sizeof(buf));
    rv = read(con_fd, buf, sizeof(buf));
    if(rv < 0)
    {
            printf("read() failure: %s\n", strerror(errno));
            return -1;
    }
    else if(0 == rv)
    {
            printf("client_fd get disconnected...\n");
            return -1;
    }
    printf("read %d bytes data: %s\n", rv, buf);
    close(con_fd);
}
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号