网络编程之本地套接字和网络套接字比较与本地套接字通信案例01

1 socket IPC(本地套接字domain)

1.1 本地通信的方法

  • 1)pipe,mkfifo,两者实现最简单。
  • 2)mmap 非血缘关系进程间。
  • 3)信号,开销小。
  • 4)domain,稳定性最好。注意,在本节domain称之为本地套接字。与之对应的是网络套接字,两者需要区分一下。

1.2 本地套接字domain注意点
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。
UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIX Domain Socket通讯的。
使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,domain参数可以是AF_UNIX,AF_LOCAL(并且绑定时address family一般指定为AF_UNIX,两者是对应赋相同值),type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。
即:

int socket(int domain, int type, int protocol);
/**
* 参1:可以是AF_UNIX,AF_LOCAL,网络一般是AF_INET。
* 参2:可以是SOCK_DGRAM或SOCK_STREAM,和网络一样。
* 参3:都是传0默认协议即可。
**/

UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。

//对比网络套接字地址结构和本地套接字地址结构:
struct sockaddr_in {
__kernel_sa_family_t sin_family; 			/* Address family */  	地址结构类型
__be16 sin_port;					 		/* Port number */		端口号
struct in_addr sin_addr;					/* Internet address */	IP地址
};

struct sockaddr_un {
__kernel_sa_family_t sun_family; 			/* AF_UNIX */			地址结构类型
char sun_path[UNIX_PATH_MAX]; 				/* pathname */		socket文件名(含路径)
};

以下程序将UNIX Domain socket绑定到一个地址。

#define offsetof(type, member) ((int)&((type *)0)->MEMBER)	//用于求某个结构体类型的成员的偏移地址,type为结构体类型,member为结构体类型的内部成员
socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un serv_addr;
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, "mysocket")
int len = offsetof(struct sockaddr_un, serv_addr.sun_path) + strlen(serv_addr.sun_path);//实际len = 2 + strlen(serv_addr.sun_path),即offsetof(serv_addr, serv_addr.sun_path)求出大小为2。
unlink("mysocket");		//若mysocket文件已存在,则先删除在bind创建,否则存在会使bind失败
bind(sfd, (struct sockaddr *)&serv_addr, len);//bind后会创建mysocket文件。

跳过两字节是因为下图的sockaddr_un路径名长度是不确定的,需要跳过16位的AF_UNIX才能求出len,然后求出sockaddr_un类型变量的长度给bind。注意不要直接写2,要使用offsetof函数,否则可能出错。
在这里插入图片描述

2 本地套接字通信案例

本地套接字通信案例客户端需要注意的是:

  • 1)客户端对比网络通信,必须要显示bind,并且需要初始化两次地址结构,一个是客户端本身的文件名,一个是服务器的文件名,这两个文件相当于网络套接字的缓冲区。
  • 2)注意connect时是连接服务器的地址结构,而不是客户端本身的。
  • 3)下面的代码案例只能连接一个客户端,多个不行。注意:代码在共享目录执行时可能在bind时出现操作不允许,与共享目录的权限有关,换个普通目录即可。

注意下面的函数是经过封装出错处理,大家按正常理解即可。
server.c

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <stddef.h>

#include "wrap.h"
#define SERV_ADDR  "serv.socket"	//缓冲区文件名,伪文件,不占用磁盘空间

int main(void)
{
    int lfd, cfd, len, size, i;
    struct sockaddr_un servaddr, cliaddr;
    char buf[4096];

    lfd = Socket(AF_UNIX, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_UNIX;
    strcpy(servaddr.sun_path,SERV_ADDR);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path);     /* servaddr total len */
    unlink(SERV_ADDR);                              		/* 确保bind之前serv.sock文件不存在,bind会创建该文件 */
    Bind(lfd, (struct sockaddr *)&servaddr, len);           /* 参3不能是sizeof(servaddr) */

    Listen(lfd, 20);

    printf("Accept ...\n");
    while (1) {
        len = sizeof(cliaddr);
        cfd = Accept(lfd, (struct sockaddr *)&cliaddr, (socklen_t *)&len);

        len -= offsetof(struct sockaddr_un, sun_path);      /* 得到文件名的长度 */
        cliaddr.sun_path[len] = '\0';                       /* 确保打印时,没有乱码出现 */

        printf("client bind filename %s\n", cliaddr.sun_path);

        while ((size = read(cfd, buf, sizeof(buf))) > 0) {
            for (i = 0; i < size; i++)
                buf[i] = toupper(buf[i]);
            write(cfd, buf, size);
        }
        close(cfd);
    }
    close(lfd);

    return 0;
}

client.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <stddef.h>

#include "wrap.h"

#define SERV_ADDR "serv.socket"
#define CLIE_ADDR "clie.socket"

int main(void)
{
    int  cfd, len;
    struct sockaddr_un servaddr, cliaddr;
    char buf[4096];

    cfd = Socket(AF_UNIX, SOCK_STREAM, 0);
    bzero(&cliaddr, sizeof(cliaddr));
    cliaddr.sun_family = AF_UNIX;
    strcpy(cliaddr.sun_path,CLIE_ADDR);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(cliaddr.sun_path);     /* 计算客户端地址结构有效长度 */
    unlink(CLIE_ADDR);
    Bind(cfd, (struct sockaddr *)&cliaddr, len);                                 /* 对比网络套接字,客户端需要显示bind, 不能依赖隐式绑定*/

	/*  注意本地套接字通信需要初始化两次地址结构 */
    bzero(&servaddr, sizeof(servaddr));                                          /* 构造server 地址 */
    servaddr.sun_family = AF_UNIX;
    strcpy(servaddr.sun_path,SERV_ADDR);

    len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path);   /* 计算服务器端地址结构有效长度 */

    Connect(cfd, (struct sockaddr *)&servaddr, len);

    while (fgets(buf, sizeof(buf), stdin) != NULL) {
        write(cfd, buf, strlen(buf));
        len = read(cfd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);
    }

    close(cfd);

    return 0;
}

结果:
在这里插入图片描述

3 本地套接字和网络套接字流程的ser和cli对比

在这里插入图片描述
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值