网络编程:本地套接字

一、介绍
本地套接字是IPC, 即本地进程间通信的一种实现方式。出来本地套接字以外,其他技术,如管道、共享信息队列等也是进程间通信的常用方法。但因为本地套接字开发便捷,接受度高,所以普遍适用于同一台主机上进程间通信的各种场景。
利用本地套接字可完成可靠字节流和数据报两种协议。
PS:
可通过netstat命令查看Linux系统内的本地套接字状况。
查看所使用的本地套接字描述符:

套接字是一种特殊类型的套接字,与TCP/UDP套接字不同。TCP/UDP即使在本地地址通信,也要走系统网络协议栈,而本地套接字,严格意义上来说提供了一种单主机跨进程间调用的手段,减少 协议栈实现的复杂度,效率比TCP/UDP套接字高很多。类似的IPC机制还有UNIX管道、共享内存和RPC调用等。
本地地址就是本地套接字专属的。

二、本地字节流套接字
服务端:

//
// Created by shengym on 2019-07-14.
//

#include  "lib/common.h"


int main(int argc, char **argv) {
    if (argc != 2) {
        error(1, 0, "usage: unixstreamserver <local_path>");
    }

    int listenfd, connfd;
    socklen_t clilen;
    struct sockaddr_un cliaddr, servaddr;

    listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);
    if (listenfd < 0) {
        error(1, errno, "socket create failed");
    }

    char *local_path = argv[1];
    unlink(local_path);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, local_path);

    if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
        error(1, errno, "bind failed");
    }

    if (listen(listenfd, LISTENQ) < 0) {
        error(1, errno, "listen failed");
    }

    clilen = sizeof(cliaddr);
    if ((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0) {
        if (errno == EINTR)
            error(1, errno, "accept failed");
        else
            error(1, errno, "accept failed");
    }

    char buf[BUFFER_SIZE];

    while (1) {
        bzero(buf, sizeof(buf));
        if (read(connfd, buf, BUFFER_SIZE) == 0) {
            printf("client quit");
            break;
        }
        printf("Receive: %s", buf);

        char send_line[MAXLINE];
        bzero(send_line, MAXLINE);
        sprintf(send_line, "Hi, %s", buf);

        int nbytes = sizeof(send_line);

        if (write(connfd, send_line, nbytes) != nbytes)
            error(1, errno, "write error");
    }

    close(listenfd);
    close(connfd);

    exit(0);

}

客户端:

//
// Created by shengym on 2019-07-15.
//
#include "lib/common.h"

int main(int argc, char **argv) {
    if (argc != 2) {
        error(1, 0, "usage: unixstreamclient <local_path>");
    }

    int sockfd;
    struct sockaddr_un servaddr;

    sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
    if (sockfd < 0) {
        error(1, errno, "create socket failed");
    }

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, argv[1]);

    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
        error(1, errno, "connect failed");
    }

    char send_line[MAXLINE];
    bzero(send_line, MAXLINE);
    char recv_line[MAXLINE];

    while (fgets(send_line, MAXLINE, stdin) != NULL) {

        int nbytes = sizeof(send_line);
        if (write(sockfd, send_line, nbytes) != nbytes)
            error(1, errno, "write error");

        if (read(sockfd, recv_line, MAXLINE) == 0)
            error(1, errno, "server terminated prematurely");

        fputs(recv_line, stdout);
    }

    exit(0);
}

这里创建的套接字类型,注意是 AF_LOCAL,并且使用字节流格式。
关于本地文件路径,需要明确一点,它必须是“绝对路径”,这样的话,编写好的程序可以在任何目录里被启动和管理。如果是“相对路径”,为了保持同样的目的,这个程序的启动路径就必须固定,这样一来,对程序的管理反而是一个很大的负担。
另外还要明确一点,这个本地文件,必须是一个“文件”,不能是一个“目录”。如果文件不存在,后面 bind 操作时会自动创建这个文件
运行结果:
服务端:

客户端:

三:本地数据报套接字
服务端:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define BUFFER_SIZE 4096
#define MAX_LINE 4096


int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        perror("usage: unixdataserver <local path>");
        return -1;
    }

    int socket_fd;
    socket_fd = socket(AF_LOCAL, SOCK_DGRAM, 0);
    if(socket_fd < 0)
    {
        perror("socket create failed");
        return -1;
    }

    struct sockaddr_un servaddr;
    char *local_path = argv[1];
    unlink(local_path);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path,local_path);

    if(bind(socket_fd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)
    {
        perror("bind faild\n");
        return -1;
    }

    char buf[BUFFER_SIZE];

    struct sockaddr_un client_addr;
    socklen_t client_len = sizeof(client_addr);
    while(1)
    {
        bzero(buf,sizeof(buf));
        if(recvfrom(socket_fd, buf, BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &client_len) == 0)
        { 
            printf("client quit\n");
            break;
        }
        printf("Receive: %s \n",buf);

        char send_line[MAX_LINE];
        bzero(send_line,MAX_LINE);
        sprintf(send_line,"Hi, %s",buf);

        size_t nybtes = strlen(send_line);
        printf("now sending :%s \n",send_line);

        if(sendto(socket_fd,send_line,nybtes, 0, (struct sockaddr *)&client_addr, client_len) != nybtes)
        {
            perror("sendto error\n");
            break;
        }
    }
    close(socket_fd);

    exit(0);

}

这里创建的套接字类型,注意是 AF_LOCAL,协议类型为 SOCK_DGRAM。
收发数据recvfrom、sendto等都与UDP网络程序一致。

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define MAXLINE 4096

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        perror("usage: unixdataclient <local path>");
        return -1;
    }

    int sockfd;
    sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0 );

    struct sockaddr_un client_addr, server_addr;
    
    bzero(&client_addr,sizeof(client_addr));
    client_addr.sun_family = AF_LOCAL;
    strcpy(client_addr.sun_path, tmpnam(NULL));

    if(bind(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0)
    {
        perror("bind error\n");
        return -1;
    }

    bzero(&server_addr,sizeof(server_addr));
    server_addr.sun_family = AF_LOCAL;
    strcpy(server_addr.sun_path, argv[1]);

    char send_line[MAXLINE];
    bzero(send_line, sizeof(send_line));
    char recv_line[MAXLINE];

    while (fgets(send_line,MAXLINE,stdin) != NULL)
    {
        int i = strlen(send_line);
        if(send_line[i - 1] == '\n')
        {
            send_line[i - 1] = 0;
        }

        size_t nbytes = strlen(send_line);
        printf("now sending %s \n",send_line);

        if(sendto(sockfd, send_line, nbytes, 0, (struct sockaddr *)&server_addr,sizeof(server_addr)) != nbytes)
        {
            perror("sendto error\n");
            break;
        }

        int n = recvfrom(sockfd, recv_line, MAXLINE, 0, NULL, NULL);
        recv_line[n] = 0;

        fputs(recv_line, stdout);
        fputs("\n",stdout);
    }
    exit(0);
    
}

执行结果:

【记】关于本地文件路径,需要明确一点,它必须是“绝对路径”,这样的话,编写好的程序可以在任何目录里被启动和管理。如果是“相对路径”,为了保持同样的目的,这个程序的启动路径就必须固定,这样一来,对程序的管理反而是一个很大的负担。

附:
Unix Domain Socket 使用
C语言tmpnam()函数
详解struct sockaddr
unlink

posted @ 2022-03-07 00:11  牛犁heart  阅读(904)  评论(0)    收藏  举报