Linux网络学习之本地套接字通信(Unix Domain Socket)

1、UNIX Domain Socket

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程序之间就是通过UNIXDomain Socket通讯的。

使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为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绑定到一个地址。

  size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
  #define offsetof(type, member) ((int)&((type *)0)->member)

2、本地套接字通信函数

1、socket函数

通过查询: man 7 unix 可以查到unix本地域socket通信相关信息:

#include <sys/socket.h>
#include <sys/un.h>
int socket(int domain, int type, int protocol);
函数说明: 创建本地域socket
函数参数:	
	domain: AF_UNIX or AF_LOCAL
	type: SOCK_STREAM或者SOCK_DGRAM
	protocol: 0 表示使用默认协议
函数返回值:
	成功: 返回文件描述符.
	失败: 返回-1, 并设置errno值.
	

创建socket成功以后, 会在内核创建缓冲区, 下图是客户端和服务端内核缓冲区示意图.

在这里插入图片描述

2、bind函数

通过man 2 bind, 可以查看bind函数的相关信息

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数说明: 绑定套接字
函数参数:
	socket: 由socket函数返回的文件描述符
	addr: 本地地址
	addlen: 本地地址长度
函数返回值:
	成功: 返回文件描述符.
	失败: 返回-1, 并设置errno值.
      
struct sockaddr_un {
    sa_family_t sun_family;  / AF_UNIX or AF_LOCAL/
    char sun_path[108];  / pathname /
};*

需要注意的是: bind函数会自动创建socket文件, 若在调用bind函数之前socket文件已经存在, 则调用bind会报错, 可以使用unlink函数在bind之前先删除文件

在这里插入图片描述

3、本地套接字实现流程

本地套接字服务器的流程:

  • 可以使用TCP的方式, 必须按照tcp的流程
  • 也可以使用UDP的方式, 必须按照udp的流程

下面我们使用tcp的方式来完成本地套接字通信

服务端

tcp的本地套接字服务器流程:

  1. 创建套接字 socket(AF_UNIX,SOCK_STREAM,0)
  2. 绑定 struct sockaddr_un &强转
  3. 侦听 listen
  4. 获得新连接 accept
  5. 循环通信 read-write
  6. 关闭文件描述符 close

客户端

tcp本地套接字客户端流程:

  1. 调用socket创建套接字

  2. 调用bind函数将socket文件描述和socket文件进行绑定.

    不是必须的, 若无显示绑定会进行隐式绑定,但服务器不知道谁连接了.

  3. 调用connect函数连接服务端

  4. 循环通信read-write、

  5. 关闭文件描述符 close

4、TCP实现本地套接字通信

编写代码并进行测试
测试客户端工具:
man nc
-U Specifies to use UNIX-domain sockets.
例如: nc -U sock.s

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

size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

#define offsetof(type, member) ((int)&((type *)0)->member)

在这里插入图片描述

服务端

//本地socket通信服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/un.h>


int main()
{
	//创建socket
	int lfd = socket(AF_UNIX, SOCK_STREAM, 0);
	if(lfd<0)
	{
		perror("socket error");
		return -1;
	}

	//删除socket文件,避免bind失败
	unlink("./server.sock");

	//绑定bind
	struct sockaddr_un serv;
	bzero(&serv, sizeof(serv));
	serv.sun_family = AF_UNIX;
	strcpy(serv.sun_path, "./server.sock"); 
	int ret = bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
	if(ret<0)
	{
		perror("bind error");
		return -1;
	}

	//监听listen
	listen(lfd, 10);

	//接收新的连接-accept
	struct sockaddr_un client;
	bzero(&client, sizeof(client));
	int len = sizeof(client);
	int cfd = accept(lfd, (struct sockaddr *)&client, &len);
	if(cfd<0)
	{
		perror("accept error");	
		return -1;
	}
	printf("client->[%s]\n", client.sun_path);

	int n;
	char buf[1024];

	while(1)
	{
		//读数据
		memset(buf, 0x00, sizeof(buf));		
		n = read(cfd, buf, sizeof(buf));
		if(n<=0)
		{
			printf("read error or client close, n==[%d]\n", n);
			break;
		}
		printf("n==[%d], buf==[%s]\n", n, buf);

		//发送数据
		write(cfd, buf, n);
	}

	close(lfd);

	return 0;
}


客户端

//本地socket通信客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/un.h>


int main()
{
	//创建socket
	int cfd = socket(AF_UNIX, SOCK_STREAM, 0);
	if(cfd<0)
	{
		perror("socket error");
		return -1;
	}

	//删除socket文件,避免bind失败
	unlink("./client.sock");

	//绑定bind
	struct sockaddr_un client;
	bzero(&client, sizeof(client));
	client.sun_family = AF_UNIX;
	strcpy(client.sun_path, "./client.sock"); 
	int ret = bind(cfd, (struct sockaddr *)&client, sizeof(client));
	if(ret<0)
	{
		perror("bind error");
		return -1;
	}

	struct sockaddr_un serv;
	bzero(&serv, sizeof(serv));
	serv.sun_family = AF_UNIX;
	strcpy(serv.sun_path, "./server.sock");
	ret = connect(cfd, (struct sockaddr *)&serv, sizeof(serv));
	if(ret<0)
	{
		perror("connect error");	
		return -1;
	}

	int n;
	char buf[1024];

	while(1)
	{
		memset(buf, 0x00, sizeof(buf));
		n = read(STDIN_FILENO, buf, sizeof(buf));

		//发送数据
		write(cfd, buf, n);

		//读数据
		memset(buf, 0x00, sizeof(buf));		
		n = read(cfd, buf, sizeof(buf));
		if(n<=0)
		{
			printf("read error or client close, n==[%d]\n", n);
			break;
		}
		printf("n==[%d], buf==[%s]\n", n, buf);
	}

	close(cfd);

	return 0;
}


5、网络通信中常用的其他函数

1、名字与地址转换

  • gethostbyname根据给定的主机名,获取主机信息。

    过时,仅用于IPv4,且线程不安全。

  • gethostbyaddr函数。

    此函数只能获取域名解析服务器的url和/etc/hosts里登记的IP对应的域名。

  • getservbyname

    getservbyport

    根据服务程序名字或端口号获取信息。使用频率不高。

  • getaddrinfo

    getnameinfo

    freeaddrinfo

    可同时处理IPv4和IPv6,线程安全的。

2、套接口和地址关联

  • getsockname

    根据accpet返回的sockfd,得到临时端口号

  • getpeername

    根据accpet返回的sockfd,得到远端链接的端口号,在exec后可以获取客户端信息。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值