UNIX域套接字

本文详细阐述了UNIX域套接字在UDP和TCP协议下的服务端与客户端工作流程,包括创建套接字、地址绑定、通信处理及示例代码。重点介绍了UDP的无连接通信和TCP的连接导向,以及客户端使用bind()的注意事项。
摘要由CSDN通过智能技术生成

socket通信流程

udp

udp

服务端工作流程和示例

服务端工作流程
  1. 创建服务端的socket。
  2. 把服务端用于通信的地址绑定到本地socket套接字上。
  3. 与客户端通信,接收客户端发过来的报文后处理。
  4. 不断重复recvfrom()/sendto(),直到客户端断开连接。
  5. 关闭socket,释放资源。
服务端示例
/*
 * function: 演示本地unix域套接字用于进程间通信(udp协议)
 *           此进程为服务端
 *
 * 2020-12-05
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>     // struct sockaddr_un

int main(int argc, char *argv[])
{
    int sockfd = 0;

    // 第一步: 创建unix域udp协议socket套接字
    if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) 
    {   
        perror("socket faild");
        exit(1);
    }   

    unlink("udpserver.sock");   // 删除文件udpserver.sock,防止文件被占用
    							// 注意: 不要删除正在使用的socket

    struct sockaddr_un serveraddr; // 服务端的地址
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sun_family = AF_UNIX;
    strcpy(serveraddr.sun_path, "udpserver.sock");
    
    // 第二步: 把服务端用于通信的地址绑定到本地socket套接字上。
    if (bind(sockfd, (struct sockaddr *)&serveraddr, (socklen_t)sizeof(serveraddr)) == -1) 
    {   
        perror("bind faild");
        close(sockfd);
        exit(1);
    }


    struct sockaddr_un clientaddr;     // 用来存放客户端的地址
    memset(&clientaddr, 0, sizeof(clientaddr));
    socklen_t client_addr_len = sizeof(clientaddr); // struct sockaddr_un 的大小

    char buf[BUFSIZ] = {0};

    // 第三步: 与客户端通信,接收客户端发过来的报文后处理。
    // 第四步: 不断重复recvfrom()/sendto(),直到客户端断开连接。
    while (1)
    {
        // 注意: recvfrom最后一个参数表示clientaddr可接收多大的数据,应为clientaddr的大小,即sizeof(clientaddr), 而不是0.
        size_t n = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&clientaddr, &client_addr_len);
        if (n == -1)
        {
            perror("recvfrom faild");
            unlink("udpserver.sock");
            close(sockfd);
            exit(1);
        }
        printf("receive: %s\n", buf);

        n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&clientaddr, client_addr_len);
        if (n == -1)
        {
            perror("sendto faild");
            unlink("udpserver.sock");
            close(sockfd);
            exit(1);
        }
        printf("send: %s\n", buf);
    }

    unlink("udpserver.sock");   // 删除文件udpserver.sock

    // 第五步: 关闭socket,释放资源
    close(sockfd);

    return 0;
}

客户端工作流程和示例

客户端工作流程
  1. 创建客户端的socket。
  2. 把客户端用于通信的地址绑定到本地socket套接字上。
  3. 与服务端通信,将报文发送给服务端。
  4. 不断重复sendto()/recvfrom(),直到数据发送完毕。
  5. 关闭socket,释放资源。
客户端示例
/*
 * function: 演示本地unix域套接字用于进程间通信(udp协议)
 *           此进程为客户端
 *
 * 2020-12-05
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>     // struct sockaddr_un

int main(int argc, char *argv[])
{
    int sockfd = 0;

    // 第一步:创建unix域udp协议socket套接字
    if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) 
    {   
        perror("socket faild");
        exit(1);
    }   

    unlink("udpclient.sock");   // 删除文件udpclient.sock,防止文件被占用
    							// 注意: 不要删除正在使用的socket

    struct sockaddr_un clientaddr; // 客户端的地址
    clientaddr.sun_family = AF_UNIX;
    strcpy(clientaddr.sun_path, "udpclient.sock");

    // 第二步:  把客户端用于通信的地址绑定到本地socket套接字上
    // 注意: 如果不进行绑定,服务端的recvfrom函数将不能收到客户端的地址
    if (bind(sockfd, (struct sockaddr *)&clientaddr, (socklen_t)sizeof(clientaddr)) == -1) 
    {   
        perror("bind faild");
        close(sockfd);
        exit(1);
    }

    struct sockaddr_un serveraddr;    // 服务端的地址
    serveraddr.sun_family = AF_UNIX;
    strcpy(serveraddr.sun_path, "udpserver.sock");

    char buf[BUFSIZ] = {0};
    char sendbuf[] = "hello world";

    // 第三步: 与服务端通信,将报文发送给服务端
    // 第四步: 不断重复sendto()/recvfrom(),直到数据发送完毕。
    while (1)
    {
        // 发送报文
        // 注意: sendbuf[], 内容为字符串, 因此才使用strlen(sendbuf), strlen()函数需要谨慎使用。
        size_t n = sendto(sockfd, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&serveraddr, (socklen_t)sizeof(serveraddr));
        if (n == -1)
        {
            perror("sendto faild");
            unlink("udpclient.sock");
            close(sockfd);
            exit(1);
        }
        printf("send: %s\n", sendbuf);

        // 接收报文
        n = recvfrom(sockfd, buf, sizeof(buf), 0, 0, 0);
        if (n == -1)
        {
            perror("sendto faild");
            unlink("udpclient.sock");
            close(sockfd);
            exit(1);
        }
        printf("receive: %s\n", buf);

        sleep(2);
    }

    unlink("udpclient.sock");   // 删除文件udpclient.sock

    // 第五步: 关闭socket,释放资源
    close(sockfd);

    return 0;
}

udp

tcp

tcp

服务端工作流程和示例

服务端工作流程
  1. 创建服务端的socket。
  2. 把服务端用于通信的地址和端口绑定到socket上。
  3. 把socket设置为监听模式。
  4. 接受客户端的连接。
  5. 与客户端通信,接收客户端发过来的报文后,回复处理结果。
  6. 不断的重复第5步,直到客户端断开连接。
  7. 关闭socket,释放资源。
服务端示例
/*
 * function: 演示本地unix域套接字用于进程间通信(tcp协议)
 *           此进程为服务端
 *
 * 2020-12-07
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>     // struct sockaddr_un

int main(int argc, char *argv[])
{
    int serverFd = 0;

    // 第一步: 创建unix域tcp协议socket套接字
    if ((serverFd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) 
    {   
        perror("socket faild");
        exit(1);
    }   

    unlink("tcpserver");  // 删除tcpserver,防止文件被占用
                          // 注意: 不要删除正在使用的socket

    struct sockaddr_un serveraddr;      // 服务端的地址
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sun_family = AF_UNIX;
    strcpy(serveraddr.sun_path, "tcpserver");

    // 第二步: 指定用于通信的地址
    if (bind(serverFd, (struct sockaddr *)&serveraddr, (socklen_t)sizeof(serveraddr)) == -1) 
    {   
        perror("bind faild");
        exit(1);
    }

    // 第三步: 把socket设置为监听
    if (listen(serverFd, 5) == -1)
    {
        perror("listen faild");
        unlink("tcpserver");
        close(serverFd);
        exit(1);
    }

    int clientFd = 0;
    struct sockaddr_un clientaddr;              // 客户端的地址信息
    memset(&clientaddr, 0, sizeof(clientaddr));
    socklen_t socklen = sizeof(clientaddr);     // struct sockaddr_un 的大小

    // 第四步: 接受客户端的连接
    if ((clientFd = accept(serverFd, (struct sockaddr *)&clientaddr, &socklen)) == -1)
    {
        perror("accept faild");
        unlink("tcpserver");
        close(serverFd);
        exit(1);
    }
    printf("客户端(%s)连接成功!!!\n", clientaddr.sun_path);

    char buf[BUFSIZ] = {0};

    // 第五步: 与客户端通信,接收客户端发过来的报文后,回复处理结果。
    // 第六步: 不断的重复第五步,直到客户端断开连接。
    while (1)
    {
        // 接收报文
        size_t n = recv(clientFd, buf, sizeof(buf), 0);
        if (n == -1)
        {
            perror("recv faild");
            unlink("tcpserver");
            close(serverFd); close(clientFd);
            exit(1);
        }
        printf("receive: %s\n", buf);

        // 发送报文
        n = send(clientFd, buf, strlen(buf), 0);
        if (n == -1)
        {
            perror("send faild");
            unlink("tcpserver");
            close(serverFd); close(clientFd);
            exit(1);
        }
        printf("send: %s\n", buf);
    }

    unlink("tcpserver");   // 删除文件tcpserver

    // 第七步: 关闭socket,释放资源
    close(serverFd); close(clientFd);

    return 0;
}

客户端工作流程和示例

客户端工作流程
  1. 创建客户端的socket。
  2. 向服务器发起连接请求。
  3. 与服务端通信,发送一个报文后等待回复,然后再发下一个报文。
  4. 不断的重复第3步,直到全部的数据被发送完。
  5. 第4步:关闭socket,释放资源。
客户端示例
/*
 * function: 演示本地unix域套接字用于进程间通信(tcp协议)
 *           此进程为客户端
 *
 * 2020-12-07
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>     // struct sockaddr_un

int main(int argc, char *argv[])
{
    int sockfd = 0;

    // 第一步: 创建unix域tcp协议的socket套接字
    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) 
    {   
        perror("socket faild");
        exit(1);
    }   

    unlink("tcpclient");    // 删除tcpclient,防止文件被占用
                            // 注意: 不要删除正在使用的socket

#if 0       
    struct sockaddr_un clientaddr;  // 客户端的地址
    clientaddr.sun_family = AF_UNIX;
    strcpy(clientaddr.sun_path, "tcpclient");

    // 如果使用bind绑定客户端的通信地址后,服务端的accept()函数的第二个参数可以接收到客户端的地址,否则不能接收到。
    if (bind(sockfd, (struct sockaddr *)&clientaddr, (socklen_t)sizeof(clientaddr)) == -1)
    {
        perror("bind faild");
        close(sockfd);
        exit(1);
    }
#endif

    struct sockaddr_un serveraddr;    // 服务端的地址
    serveraddr.sun_family = AF_UNIX;
    strcpy(serveraddr.sun_path, "tcpserver");

    // 第二步: 向服务器发起连接请求
    if (connect(sockfd, (struct sockaddr *)&serveraddr, (socklen_t)sizeof(serveraddr)) == -1)
    {
        perror("connect faild");
        unlink("tcpclient");
        close(sockfd);
        exit(1);
    }
    printf("连接服务端成功!!!\n");

    char sendBuf[] = "hello world";
    char buf[BUFSIZ] = {0};

    // 第三步: 与服务端通信,发送一个报文后等待回复, 然后发下一个报文。
    // 第四步: 不断的重复第三步,直到全部的数据被发送完。
    while (1)
    {
        // 发送报文
        // 注意: sendBuf[], 内容为字符串, 因此才使用strlen(sendBuf), strlen()函数需要谨慎使用。
        size_t n = send(sockfd, sendBuf, strlen(sendBuf), 0);
        if (n == -1)
        {
            perror("send faild");
            unlink("tcpclient");
            close(sockfd);
            exit(1);
        }
        printf("send: %s\n", sendBuf);

        // 接收报文
        n = recv(sockfd, buf, sizeof(buf), 0);
        if (n == -1)
        {
            perror("recv faild");
            unlink("tcpclient");
            close(sockfd);
            exit(1);
        }
        printf("recevie: %s\n", buf);

        sleep(2);
    }

    unlink("tcpclient");    // 删除文件tcpclient

    // 第五步: 关闭socket,释放资源
    close(sockfd);

    return 0;
}

客户端代码没有打开注释位置,即未使用bind绑定客户端的地址
tcp1
客户端代码打开了注释位置,即使用bind绑定客户端的地址
tcp2

注意事项

1. 关于客户端使用bind()函数

1)对于udp协议,如果客户端未使用bind() 函数,那么我们只能实现客户端向服务端发送信息,但服务端不能向客户端回复信息,因为服务端得不到客户端的地址。
2)对于tcp协议,如果客户端未使用bind() 函数,服务端与客户端可以正常通信,只是服务端同样得不到客户端本地socket的名字。

2. 关于recvfrom()函数

函数原型:

ssize_t recvfrom(int socket, void *restrict buffer, size_t length, 
				 int flags, struct sockaddr *restrict address,
				 socklen_t *restrict address_len);

对于最有一个参数address_len,看着像一个传出参数,得到的是address的大小。
实际上,address_len表示的是address可以接收多大,为传入参数,应该传入sizeof(address_len).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值