Unix域套接字详解(附源码)

                                                 图1 Unix域套接字通信模型

1.Unix域通信模型

Unix域套接字通信模型如图1所示

2.api接口

1)socket()

  1. int socket (int domain, int type, int protocol);  

API定义是一样的,不过这里的第一个参数,也就是域一定要设置成AF_UNIX或AF_LOCAL,而不是普通TCP/IP套接字的AF_INET。第二个参数表示套接字的类型,分为流套接字(SOCK_STREAM)和数据包套接字(SOCK_DGRAM)。不同于普通的AF_INET的Socket,由于都是在本机通过内核通信,所以SOCK_STREAM和SOCK_DGRAM都是可靠的,不会丢包也不会出现发送包的次序和接收包的次序不一致的问题。它们的区别仅仅是,SOCK_STREAM无论发送多大的数据都不会被截断,而对于SOCK_DGRAM来说,如果发送的数据超过了一个报文的最大长度,则数据会被截断。而最后一个参数,表示协议,对于Unix域套接字来说,其一定是被设置成0。因此,一般通过下面的方式创建一个Unix域套接字:

int sockfd = socket(PF_LOCAL,SOCK_DGRAM, 0);    // 数据包式套接字  

2)bind()

对于流式套接字的服务器端来说,在用socket()函数获得了新创建套接字的文件描述符之后,还要将其绑定到一个地址上去:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);  

在Unix域套接字中,套接字的地址是以sockaddr_un结构体来表示的,其结构如下:

struct sockaddr_un {  

         sa_family_t sun_family;  

         char sun_path[108];  

}  

 

结构体中的第一个字段必须要设置成“AF_UNIX”。而第二个字段,表示的是一个路径名。因此,要将一个Unix域套接字绑定到一个本地地址上,需要创建并初始化一个sockaddr_un结构体,并将指向这个结构体的指针作为addr参数(需要类型转换)传入bind()函数,并将addrlen参数设置成这个结构体的实际大小。

这里还要特别提一下这个路径名,其实还要分为两种,一种是普通路径名,另一种是抽象路径名。

首先来说说普通路径名,这个很好理解,就是一个基本的Linux文件路径,其必须要以NULL('\0')结尾。在绑定一个Unix域套接字时,会在文件系统中的相应位置上创建一个文件,且这个文件的类型被标记为“Socket”,因此这个文件无法用open()函数打开。当不再需要这个Unix域套接字时,可以使用remove()函数或者unlink()函数将这个对应的文件删除。如果在文件系统中,已经有了一个文件和指定的路径名相同,则绑定会失败(返回错误EADDRINUSE)。所以,一个套接字只能绑定到一个路径上,同样的,一个路径也只能被一个套接字绑定。

接下来看看什么叫抽象路径名,这其实是Linux特有的一个特性,它允许将一个Unix域套接字绑定到一个名字上,且不会在文件系统中创建这个名字的文件。如果要创建一个抽象名字空间的绑定,必须要将sun_path字段的第一个字节设置成NULL('\0'),而且和普通的文件系统名字空间不同的是,系统会用sun_path除第一个字节之后余下的所有字节当做抽象名字。也就是说在解析抽象路径名时需要用到sun_path字段当中所有的字节,而不是像解析普通路径名一样,解析到第一个NULL就可以停止了。因为不会再在文件系统中创建文件了,所以对于抽象路径名来说,就不需要担心与文件系统中已存在的文件产生名字冲突的问题了,也不需要在使用完套接字之后删除附带产生的这个文件了,当套接字被关闭之后会自动删除这个抽象名。

最后再提一下权限的问题,因为要在文件系统中创建相应的文件,对于普通路径名来说,掉用bind()函数的进程必须要有路径名中目录部分的可写和可访问权限。还有,在默认情况下,在调用bind()函数时,会给所有者、组和其他用户赋予所有的权限(即777),如果想改变这个行为,可以在bind()之后再修改创建的文件的权限和属性

3)recvfrom()和sendto()

对于数据包事套接字来说,在服务器端recvfrom()用来接收客户端发送的请求,而在客户端这个函数用来接收服务器端发送过来的响应:

  1. int recvfrom(int sockfd, void *buf, int length, unsigned int flags, struct sockaddr *addr, int *addrlen);  

同时,在客户端sendto()用来向服务器端发送请求数据,而服务器端用这个函数来向客户端发送响应数据:

  1. int sendto (int sockfd, const void *buf, int length, unsigned int flags, const struct sockaddr *addr, int addrlen);  

前面也提到了,对于数据包套接字来说,服务器端在发送响应数据时是需要知道客户端到底是哪个的,从而后面可以将相应的响应数据发送给正确的客户端。而客户端也需要知道到底是向哪个服务器端发送数据,或者说接收到的响应数据到底来自哪个服务器端(当然,如果只保证和一个服务器端通信就没有这个问题)。

但是,按照普通的包套接字创建和连接的流程,只是在服务器端掉用bind()函数绑定了一个地址,而客户端并没有地址。这在流式套接字中没有问题,内核已经在服务器端调用accept()函数接收一个客户端连接时创建了一个新的套接字,从而将一一对应关系绑定到了这个新的套接字上了。

int sys_socket_unix_create(int proto)
{
	int fd = -1;

#ifndef WIN32
	ERROR_EXIT(!(proto == SOCK_STREAM || proto == SOCK_DGRAM), ERR_NORMAL, "proto exceed limit\n");
	
	fd = socket(PF_LOCAL, proto, 0);

	if (fd == -1)
	{
		return ERR_NORMAL;
	}
#endif

	return fd;
}

int sys_socket_unix_bind(int sockfd, const char* szPath)
{
#ifdef WIN32
	return ERR_NORMAL;
#else
	int rtn;
	struct sockaddr_un addr;

	ERROR_EXIT(sockfd < 0, ERR_NORMAL, "sockfd error\n");
	ERROR_EXIT(szPath == NULL, ERR_NORMAL, "szPath is NULL\n");
	
	memset(&addr,0,sizeof(addr));

	if (ERR_OK == sys_file_check(szPath))
	{
		unlink(szPath);
	}
	
	addr.sun_family = PF_LOCAL;
	strcpy(addr.sun_path, szPath);
	
	rtn = bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un));

	if(rtn == -1)
		return ERR_NORMAL;
	else
		return ERR_OK;
#endif
}
int app_udp_recv(int fd, uint8 *pData, uint32 nLen, uint32 nWaittime)
{
	int bytes_received = 0;				/* Bytes received from client */
	BOOL bBlock = (nWaittime == INFINITE);

	fd_set rfd;							// 描述符集 这个将用来测试有没有一个可用的连接
	int SelectRcv = 0;
	 
    struct timeval timeout;
	struct sockaddr_un clientUnix;

	if( fd < 0 )
	{
		return -1;
	}
	
	if( !bBlock )
	{
		timeout.tv_sec  = nWaittime/1000;					// 等下select用到这个
		timeout.tv_usec = (nWaittime%1000)*1000;			// timeout设置为0,可以理解为非阻塞
	}
	
    // UDP数据接收
    FD_ZERO(&rfd);						// 总是这样先清空一个描述符集
    FD_SET(fd, &rfd);					// 把sock放入要测试的描述符集

	if( bBlock )
	{
		SelectRcv = select(fd+1, &rfd, 0, 0, NULL);		// 检查该套接字是否可读
	}
	else
	{
		SelectRcv = select(fd+1, &rfd, 0, 0, &timeout); // 检查该套接字是否可读
	}

    if( SelectRcv > 0 ) 
	{
		if( FD_ISSET(fd, &rfd) )
		{
			unsigned int client_length = (unsigned int)sizeof(struct sockaddr_un);
			
			/* Clear out server struct */
			memset((void *)&clientUnix, '\0', sizeof(struct sockaddr_un));
			
			bytes_received = recvfrom(fd, (void*)pData, nLen, MSG_DONTWAIT, (struct sockaddr *)&clientUnix, &client_length);
			
			return bytes_received;
		}	
	}
	else if( SelectRcv < 0 )
	{
		ERROR(SMI_OS, "Select socket return error %d\n", SelectRcv);
		app_udp_destory(fd);
		return ERR_SOCBREAK;
	}
	
	return ERR_NORMAL;
}
static BOOL app_udp_send(int fd, uint8 *pData, uint32 size)
{
	if( fd < 0 )
	{
		return FALSE;
	}

	app_udp_clearrecv(fd);
	
	if( sys_socket_unix_sendto(fd, (const char*)pData, size, UNIX_UDP_CTRL) >= 0 )
	{
		return TRUE;
	}
	
	debug("!!!!!! failed send to %s, errno=%d\n", UNIX_UDP_CTRL, errno);
	
	if( errno == 101 )
	{
		app_udp_destory(fd);
		debug("Network is unreachable!!!\n");
	}

	return FALSE;
}

剩下的就都和普通的TCP/IP套接字相同了,只不过地址addr必须是以sockaddr_un结构体来表示罢了。

注:发送的地址要和服务端绑定的地址一致

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的 C++ 程序,用于在 UNIX 上进行套接字通信: ```cpp #include <iostream> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include <cstring> const char* SOCKET_PATH = "/tmp/mysocket"; int main() { // 创建套接字 int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd == -1) { std::cerr << "Failed to create socket" << std::endl; return 1; } // 绑定到 socket 地址 struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1); if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { std::cerr << "Failed to bind socket" << std::endl; close(sockfd); return 1; } // 监听连接请求 if (listen(sockfd, 5) == -1) { std::cerr << "Failed to listen on socket" << std::endl; close(sockfd); return 1; } // 等待客户端连接 int clientfd = accept(sockfd, nullptr, nullptr); if (clientfd == -1) { std::cerr << "Failed to accept client" << std::endl; close(sockfd); return 1; } // 读取客户端发送的数据 char buf[256]; int numBytes = read(clientfd, buf, sizeof(buf)); if (numBytes == -1) { std::cerr << "Failed to read from client" << std::endl; close(clientfd); close(sockfd); return 1; } // 打印接收到的数据 std::cout << "Received " << numBytes << " bytes: " << buf << std::endl; // 关闭套接字 close(clientfd); close(sockfd); return 0; } ``` 在这个程序,我们首先使用 `socket` 函数创建了一个 UNIX 套接字。然后,我们使用 `bind` 函数将套接字绑定到一个本地地址上,这里我们使用了 `/tmp/mysocket` 作为地址。接下来,我们使用 `listen` 函数开始监听连接请求。当有客户端连接时,使用 `accept` 函数接受连接请求,并返回一个新的套接字,可以使用这个套接字与客户端进行通信。在这个程序,我们只是简单地从客户端读取数据,并将其打印到标准输出流。最后,我们关闭套接字并退出程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值