Linux应用开发-进程间通信-Socket

本文详细介绍了Socket套接字的种类(流套接字、数据报套接字和原始套接字),以及它们在TCP、UDP协议通信和本地进程间通信中的编程模型。重点讲解了如何通过这些套接字进行TCP/TCP连接建立、UDP通信以及本地socket的使用示例。
摘要由CSDN通过智能技术生成


Socket 起源于 Unix,原意是 插座,在计算机通信领域,Socket 被翻译为 套接字,它是计算机之间进行通信的一种约定或一种方式。通过 Socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

套接字有本地套接字网络套接字两种。本地套接字的名字是Linux文件系统中的文件名,一般放在/tmp或/usr/tmp目录中;网络套接字的名字是与客户连接的特定网络有关的服务标识符(端口号或访问点)。这个标识符允许Linux将进入的针对特定端口号的连接转到正确的服务器进程。

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/IP协议的3种套接字类型如下所示。

流套接字(SOCK_STREAM):

流套接字用于提供面向连接、可靠的数据传输服务。看到这个我们想到了什么,是不是TCP

该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。

数据报套接字(SOCK_DGRAM):

数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

原始套接字(SOCK_RAW):

原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW

原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。

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 地址和端口,而是绑定一个本地文件,这也就是它们之间的最大区别。

Example

server

#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);
}

client

#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);
}

  • 30
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萌新程序猿~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值