UNIX domain socket传递文件描述符

本文深入探讨了C语言在UNIX系统中进行网络编程的方法,包括发送和接收文件描述符的实现细节。通过实例展示了如何使用自定义函数来封装网络通信操作,提供了从创建到连接、发送和接收数据的完整流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

c版本(UNIX高级编程中的例子):

 

// sendmsg.h
#ifndef SENDMSG_H
#define SENDMSG_H

#include <sys/types.h>

int send_fd(int fd, int fd_to_send);
int recv_fd(int fd, ssize_t (*userfunc)(int, const void*, size_t));

#endif

 

 

// semdmsg.c
#include "sendmsg.h"

#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>

static struct cmsghdr* cmptr = NULL;
static int CONTROLLEN = sizeof(struct cmsghdr) + sizeof(int);
static int MAXLINE = 100;


int send_fd(int fd, int fd_to_send)
{
	struct iovec iov[1];
	struct msghdr msg;
	char buf[2];

	iov[0].iov_base = buf;
	iov[0].iov_len = 2;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;
	msg.msg_name = NULL;
	msg.msg_namelen = 0;

	if (fd_to_send <= 0)
	{
		return -1;
	}

	if (cmptr == NULL && (cmptr = (struct cmsghdr*)malloc(CONTROLLEN)) == NULL)
		return -1;
	cmptr->cmsg_level = SOL_SOCKET;
	cmptr->cmsg_type = SCM_RIGHTS;
	cmptr->cmsg_len = CONTROLLEN;
	msg.msg_control = cmptr;
	msg.msg_controllen = CONTROLLEN;
	*(int*)CMSG_DATA(cmptr) = fd_to_send;
	buf[1] = 0;
	buf[0] = 0;
	if (sendmsg(fd, &msg, 0) != 2)
		return -1;
	return 0;
}


int recv_fd(int fd, ssize_t (*userfunc)(int, const void*, size_t))
{
	int newfd, nr, status;
	char *ptr;
	char buf[MAXLINE];
	struct iovec iov[1];
	struct msghdr msg;

	status = -1;
	for ( ; ; )
	{
		iov[0].iov_base = buf;
		iov[0].iov_len = sizeof(buf);
		msg.msg_iov = iov;
		msg.msg_iovlen = 1;
		msg.msg_name = NULL;
		msg.msg_namelen = 0;

		if (cmptr == NULL & (cmptr = (struct cmsghdr*)malloc(CONTROLLEN)) == NULL)
		{
			return -1;
		}

		msg.msg_control = cmptr;
		msg.msg_controllen = CONTROLLEN;
		if ((nr = recvmsg(fd, &msg, 0)) < 0)
		{
//			err_sys("recvmsg error");
		}
		else if (nr == 0)
		{
//			err_ret("connection closed by server");
			return -1;
		}

		for (ptr = buf; ptr < &buf[nr]; )
		{
			if (*ptr++ == 0)
			{
				if (ptr != &buf[nr-1])
				{
//					err_dump("message format error");
				}
				status = *ptr & 0xFF;
				if (status == 0)
				{
					if (msg.msg_controllen != CONTROLLEN)
					{
//						err_dump("status = 0but no fd");
					}
					newfd = *(int*)CMSG_DATA(cmptr);
				}
				else
				{
					newfd = -status;
				}
				nr -= 2;
			}
		}

//		if (nr > 0 && (&userfunc)(STDERR_FILENO, buf, nr) != nr)
//		{
//			return -1;
//		}
		if (status >= 0)
			return newfd;
	}
}

 

 

c++版本,自己封装的:

 

//domainsocket.h
/*
 * domainsocket.h
 *
 *  Created on: Apr 13, 2012
 *      Author: song
 */

#ifndef DOMAINSOCKET_H_
#define DOMAINSOCKET_H_

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>


class DomainSocket
{
	// the exact name of this class may be "DomainSocketListener"
	// but we call it "DomainSocket" temporary

public:
	DomainSocket();
	virtual ~DomainSocket();

	int Bind(const char* path);
	int Listen(int n);
	int Accept();
	int Connect(const char* path);

	// client call this to send data
	int Send(const void* buf, size_t len, int flags);

	// server call this to send data
	int Send(int sockfd, const void* buf, size_t len, int flags);

	// client receive
	int Recv(void *buf, size_t len, int flags);

	// server receive
	int Recv(int sockfd, void *buf, size_t len, int flags);

	/*
	 * the four methods below used to send and receive file descriptor.
	 * if you are server, you have one more argument than client whatever
	 * send or receive.
	 *
	 */

	// send file descriptor

	// domain socket client send fd need call this.
	int Sendfd(int fdToSend);

	// domain socket server send fd need call this.
	int Sendfd(int sockfd, int fdToSend);

	// received file descriptor as return value
	// return :
	//              > 0 all right!
	//              = 0 receive fail
	int Recvfd();

	// received file descriptor as return value
	// ... ... ... ...
	int Recvfd(int sockfd);


protected:
	void DelDSocketFilePath();


private:
	int m_fd;
	struct sockaddr_un m_addr;
	const char* m_path;
	struct cmsghdr* cmptr;
	int CONTROLLEN;
	int MAXLINE;

	int _Sendfd(int sockfd, int fdToSend);
	int _Recvfd(int sockfd);

};

#endif /* DOMAINSOCKET_H_ */

 

 

//domainsocket.cpp
#include "domainsocket.h"

using namespace std;


DomainSocket::DomainSocket()
{
	m_fd = 0;
	m_path = NULL;
	cmptr = NULL;
	CONTROLLEN = sizeof(struct cmsghdr) + sizeof(int);
	MAXLINE = 100;
	//////////////////////////////////

	int len;
	memset(&m_addr, 0, sizeof(m_addr));

	if ((m_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
	{
		cout<<"create domain socket fail!"<<endl;
//		return -1;
	}

	cout<<"create domain socket ... , fd is '"<<m_fd<<"'"<<endl;
}


DomainSocket::~DomainSocket()
{
	close(m_fd);
	DelDSocketFilePath();
}


int DomainSocket::Bind(const char* path)
{
	m_path = path;

	// if file exist delete it
	DelDSocketFilePath();

	m_addr.sun_family = AF_UNIX;
	strcpy(m_addr.sun_path, (char*)m_path);

	// bind
	if (bind(m_fd, (struct sockaddr*)&m_addr, sizeof(m_addr)) < 0)
	{
		cout<<"domain bind fail!, errno is '"<<errno<<"' [reason:"
											<<strerror(errno)<<"]"<<endl;

		close(m_fd);
		DelDSocketFilePath();
		return -1;
	}

	cout<<"domain bind (socket and sockaddr) ..."<<endl;
	return 0;
}


int DomainSocket::Listen(int n)
{
	if (listen(m_fd, n) < 0)
	{
		cout<<"domain listen fail!, errno is '"<<errno<<"' [reason:"
											<<strerror(errno)<<"]"<<endl;

		close(m_fd);
		DelDSocketFilePath();
		return -1;
	}

	cout<<"domain listen ..."<<endl;
	return 0;
}


int DomainSocket::Accept()
{
	struct sockaddr cli_addr;
	int new_fd;

	int len = sizeof(cli_addr);


	if ((new_fd = accept(m_fd, (struct sockaddr*)&cli_addr, (socklen_t*)&len)) < 0)
	{
		cout<<"domain accept fail!, errno is '"<<errno<<"' [reason:"<<strerror(errno)<<"]"<<endl;
		close(m_fd);
		DelDSocketFilePath();
		return -1;
	}
	cout<<"  domain accept a new fd '"<<new_fd<<"'"<<endl;

	return new_fd;

}


int DomainSocket::Connect(const char* path)
{
	sockaddr_un server_addr;
	server_addr.sun_family = AF_UNIX;
	strcpy(server_addr.sun_path, path);

	if (connect(m_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
	{
		cout<<"connect fail, err msg: "<<strerror(errno)<<endl;
		close(m_fd);
		return -1;
	}
}


int DomainSocket::Send(const void *buf, size_t len, int flags)
{
	return send(m_fd, buf, len, flags);
}


int DomainSocket::Send(int sockfd, const void *buf, size_t len, int flags)
{
	return send(sockfd, buf, len, flags);
}


int DomainSocket::Recv(void *buf, size_t len, int flags)
{
	return recv(m_fd, buf, len, flags);
}


int DomainSocket::Recv(int sockfd, void *buf, size_t len, int flags)
{
	return recv(sockfd, buf, len, flags);
}


int DomainSocket::Sendfd(int fdToSend)
{
	return _Sendfd(m_fd, fdToSend);
}


int DomainSocket::Sendfd(int sockfd, int fdToSend)
{
	return _Sendfd(sockfd, fdToSend);
}


int DomainSocket::Recvfd()
{
	return _Recvfd(m_fd);
}


int DomainSocket::Recvfd(int sockfd)
{
	return _Recvfd(sockfd);
}


int DomainSocket::_Sendfd(int sockfd, int fdToSend)
{
		struct iovec iov[1];
		struct msghdr msg;
		char buf[2];

		iov[0].iov_base = buf;
		iov[0].iov_len = 2;
		msg.msg_iov = iov;
		msg.msg_iovlen = 1;
		msg.msg_name = NULL;
		msg.msg_namelen = 0;

		if (fdToSend <= 0)
		{
			return -1;
		}

		if (cmptr == NULL && (cmptr = (struct cmsghdr*)malloc(CONTROLLEN)) == NULL)
			return -1;
		cmptr->cmsg_level = SOL_SOCKET;
		cmptr->cmsg_type = SCM_RIGHTS;
		cmptr->cmsg_len = CONTROLLEN;
		msg.msg_control = cmptr;
		msg.msg_controllen = CONTROLLEN;
		*(int*)CMSG_DATA(cmptr) = fdToSend;
		buf[1] = 0;
		buf[0] = 0;
		if (sendmsg(sockfd, &msg, 0) != 2)
			return -1;
		return 0;
}


int DomainSocket::_Recvfd(int sockfd)
{
	int newfd, nr, status;
	char *ptr;
	char buf[MAXLINE];
	struct iovec iov[1];
	struct msghdr msg;

	status = -1;
	for ( ; ; )
	{
		iov[0].iov_base = buf;
		iov[0].iov_len = sizeof(buf);
		msg.msg_iov = iov;
		msg.msg_iovlen = 1;
		msg.msg_name = NULL;
		msg.msg_namelen = 0;

		if (cmptr == NULL & (cmptr = (struct cmsghdr*)malloc(CONTROLLEN)) == NULL)
		{
			return -1;
		}

		msg.msg_control = cmptr;
		msg.msg_controllen = CONTROLLEN;
		if ((nr = recvmsg(sockfd, &msg, 0)) < 0)
		{
//			err_sys("recvmsg error");
		}
		else if (nr == 0)
		{
//			err_ret("connection closed by server");
			return -1;
		}

		for (ptr = buf; ptr < &buf[nr]; )
		{
			if (*ptr++ == 0)
			{
				if (ptr != &buf[nr-1])
				{
//					err_dump("message format error");
				}
				status = *ptr & 0xFF;
				if (status == 0)
				{
					if (msg.msg_controllen != CONTROLLEN)
					{
//						err_dump("status = 0but no fd");
					}
					newfd = *(int*)CMSG_DATA(cmptr);
				}
				else
				{
					newfd = -status;
				}
				nr -= 2;
			}
		}

//		if (nr > 0 && (&userfunc)(STDERR_FILENO, buf, nr) != nr)
//		{
//			return -1;
//		}
		if (status >= 0)
			return newfd;
	}
}


void DomainSocket::DelDSocketFilePath()
{
	if (!m_path)
		return;

	unlink(m_path);
}

 

### 如何实现Socket文件描述符的复用 在编程中,`socket` 文件描述符的复用通常涉及两种主要场景:一是允许多个进程或线程共享同一个 `socket` 资源;二是利用 I/O 多路复用技术在一个进程中高效处理多个 `socket` 连接。 #### 场景一:跨进程复用 Socket 文件描述符 为了使不同进程能够共享同一个 `socket` 文件描述符,可以采用以下方式: 1. **通过 UNIX Domain Socket 传递文件描述符** 使用 `sendmsg()` 和 `recvmsg()` 函数可以在两个进程之间传递文件描述符。这种方式依赖于控制消息(Control Message),其中包含要传递文件描述符的信息[^2]。 下面是一个简单的代码示例展示如何通过 UNIX Domain Socket 传递文件描述符: ```c #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> void send_fd(int fd_to_send, int unix_socket) { struct msghdr msg = {0}; struct cmsghdr *cmsg; char buf[CMSG_SPACE(sizeof(int))]; memset(buf, 0, sizeof(buf)); struct iovec io = { .iov_base = "dummy", .iov_len = strlen("dummy") }; msg.msg_iov = &io; msg.msg_iovlen = 1; cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); *((int *)CMSG_DATA(cmsg)) = fd_to_send; msg.msg_control = buf; msg.msg_controllen = sizeof(buf); sendmsg(unix_socket, &msg, 0); } ``` 2. **继承父进程的文件描述符** 如果子进程是由父进程派生而来,则子进程会自动继承父进程已打开的所有文件描述符,包括 `socket` 描述符。这种情况下无需额外的操作即可实现复用[^2]。 #### 场景二:在同一进程中使用 I/O 多路复用技术 对于单进程内的多个 `socket` 文件描述符,推荐使用 I/O 多路复用机制来提高效率。常用的 API 包括 `select`, `poll`, 和 `epoll`。 1. **Select 方法** `select` 是一种早期的多路复用接口,允许程序监控一组文件描述符的状态变化。它的缺点在于性能随着监听数量增加而下降,因为每次调用都需要遍历所有的文件描述符集合[^4]。 示例代码如下: ```c fd_set readfds; FD_ZERO(&readfds); FD_SET(socket_fd1, &readfds); FD_SET(socket_fd2, &readfds); select(max_fd + 1, &readfds, NULL, NULL, NULL); if (FD_ISSET(socket_fd1, &readfds)) { handle_read(socket_fd1); } if (FD_ISSET(socket_fd2, &readfds)) { handle_read(socket_fd2); } ``` 2. **Poll 方法** 相较于 `select`,`poll` 不受固定大小数组限制,并且更易于扩展支持大量文件描述符。然而其内部仍然存在轮询开销问题,在高并发环境下表现不如现代替代方案如 `epoll`。 3. **Epoll 方法** Linux 特有的高性能事件通知接口——`epoll` 提供了一种更为灵活的方式管理大批量活动连接。相比前两者,它具有更好的时间复杂度以及更低内存消耗特性。 示例代码片段展示了基本用法: ```c int epfd = epoll_create1(0); struct epoll_event event, events[MAX_EVENTS]; event.data.fd = socket_fd1; event.events = EPOLLIN | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, socket_fd1, &event); while (true) { int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); for (int n = 0; n < nfds; ++n) { if ((events[n].events & EPOLLIN) && (events[n].data.fd == socket_fd1)) { handle_read(socket_fd1); } } } ``` 以上介绍了几种常见方法来实现 `socket` 文件描述符的复用。具体选择取决于应用场景需求及目标平台的支持情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值