Unix域套接字

Unxi域套接字并不是一个实际的协议族,而是在单个主机上执行客户/服务器通信的一种方法,所用API就是在不同主机上执行客户/服务器通信所用的API。可以视为IPC方法之一。
UNix域提供两类套接字:字节流套接字(类似TCP),数据报套接字(类似UDP)

UNIX域协议特点:

1.Unix域套接字往往比通信两端位于同一主机的TCP套接字快出一倍。X Window System发挥了Unix域套接字的这个优势。当一个X11客户打开到一个X11服务器的连接时,该客户检查DISPLAY环境变量的值(其中指定服务器的主机名,窗口和屏幕)。如果服务器和客户处于同一主机,客户就打开一个到服务器的Unix域套接字字节流连接,否则打开一个到服务器的TCP连接。
2.Unix域套接字可用于在同一主机上的不同进程之间传递描述符
3.unix域套接字较新的实现把客户的凭证提供给服务器,从而能提供额外的安全检查措施。
4.Unix域套接字与传统套接字的区别是用路径名来表示协议族的描述。

域套接字比流式套接字快的原因?

UNIX域套接字用于同一台pc上运行的进程之间通信,它仅仅复制数据,不执行协议处理,不需要增加删除网络报头,无需计算校验和,不产生顺序号,无需发送确认报文。

UNIX域套接字地址结构

struct sockaddr_un{
    sa_family sun_family;    //AF_LOCAL,地址族
    char   sun_path[104];    //必须以空字符结尾,通过这样一个路径描述地址
};

UNIX域字节流回射客户/服务

服务端

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

#define ERR_EXIT(m) \
	do \
	{ \
		perror(m); \
		exit(EXIT_FAILURE); \
	}while(0)

void echo_srv(int conn)
{
	char recvbuf[1024];
	int n;
	while(1)
	{
		memset(recvbuf,0,sizeof(recvbuf));
		//不断接收一行数据到recvbuf中
		n=read(conn,recvbuf,sizeof(recvbuf));
		if(n==-1)
		{
			if(n==EINTR)
				continue;
			ERR_EXIT("read");
		}
		//客户端关闭
		else if(n==0)
		{
			printf("client close\n");
			break;
		}
		fputs(recvbuf,stdout);
		write(conn,recvbuf,strlen(recvbuf));
	}
	close(conn);
}
int main(void)
{
	int listenfd;
	//创建一个监听套接字
	//它的协议家族是PF_UNIX,用流式套接字SOCK_STREAM
	if((listenfd=socket(PF_UNIX,SOCK_STREAM,0))<0)
		ERR_EXIT("socket");

	//unlink表示删除这个文件,先删除,再绑定,重新创造了一个文件
	unlink("/tmp/test_socket");

	//初始化一个地址绑定监听
	struct sockaddr_un servaddr;
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sun_family=AF_UNIX;
	strcpy(servaddr.sun_path,"/tmp/test_socket");

	//绑定
	//绑定的时候会产生test_socket文件
	if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
		ERR_EXIT("bind");
	//监听
	//监听队列的最大值SOMAXCONN
	if(listen(listenfd,SOMAXCONN)<0)
		ERR_EXIT("listen");

	int conn;
	pid_t pid;
	//接受客户端的连接
	while(1)
	{
		//返回一个已连接套接字
		conn=accept(listenfd,NULL,NULL);
		if(conn==-1)
		{
			if(conn==EINTR)
				continue;
			ERR_EXIT("accept");
		}		
		
		pid=fork();
		if(pid==-1)
			ERR_EXIT("fork");
	
		//pid==0(子进程)说明是客户端,执行回射
		if(pid==0)
		{
			//子进程不需要处理监听
			close(listenfd);
			echo_srv(conn);
			exit(EXIT_SUCCESS);
		}
		//父进程不需要处理连接
		close(conn);
	}
	return 0;

客户端
 

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

#define ERR_EXIT(m) \
	do \
	{ \
		perror(m); \
		exit(EXIT_FAILURE); \
	}while(0)

void echo_cli(int sock)
{
	char sendbuf[1024]={0};
	char recvbuf[1024]={0};
	//不停地从标准输入获取一行数据到一个缓冲区当中
	while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)	
	{
		//发送给服务器端
		write(sock,sendbuf,strlen(sendbuf));
		//接收回来
		read(sock,recvbuf,sizeof(recvbuf));
		fputs(recvbuf,stdout);
		memset(sendbuf,0,sizeof(sendbuf));
		memset(recvbuf,0,sizeof(recvbuf));
	}
	close(sock);
}
int main(void)
{
	int sock;
	if((sock=socket(PF_UNIX,SOCK_STREAM,0))<0)
		ERR_EXIT("socket");

	struct sockaddr_un servaddr;
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sun_family=AF_UNIX;
	strcpy(servaddr.sun_path,"/tmp/test_socket");

	if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
		ERR_EXIT("connect");

	//连接成功后,执行回射客户端的函数
	echo_cli(sock);
	return 0;
}

UNIX域套接字编程注意点

1.bind成功后会创建一个文件,权限为0777 & ~umask

2.sun_path最好用一个绝对路径,如果是相对路径的话,如果客户端和服务端不在同一个目录,就会找不到路径。

3.UNIX域流式套接字connect发现监听队列满时,会立刻返回一个ECONNREFUSED,这和TCP不同,如果监听队列满,会忽略到来的SYN,这导致对方重传SYN

socketpair

功能:创建一个全双工的流管道,也是只能用于亲缘关系的进程间通信。而有名管道和匿名管道是半双工的

原型:int socketpair(int domain,int type,int protocol,int sv[2]);

参数:

domain:协议家族

type:套接字类型

protocol:协议类型

sv:返回套接字对

返回值:成功返回0;失败返回-1

利用socketpair实现全双工通信

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

#define ERR_EXIT(m) \
	do \
	{ \
		perror(m); \
		exit(EXIT_FAILURE); \
	}while(0)
//父子进程间实现全双工通信
int main(void)
{
	//定义一个数组用来接收套接字对
	int sockfds[2];
	//全双工通信的流管道,sockfds[0]和sockfds[1]都是即可读又可写
	if(socketpair(PF_UNIX,SOCK_STREAM,0,sockfds)<0)
		ERR_EXIT("socketpair");

	pid_t pid;
	//在父子进程间实现全双工的通信
	pid=fork();
	if(pid==-1)
		ERR_EXIT("fork");

	//父进程
	if(pid>0)
	{
		int val=0;
		close(sockfds[1]);
		while(1)
		{
			++val;
			printf("sending data: %d\n",val);
			//父进程写给子进程
			write(sockfds[0],&val,sizeof(val));
			//sockfds既可读又可写,用于接收回来
			read(sockfds[0],&val,sizeof(val));
			printf("data received: %d\n",val);
		}
	}
	
	//子进程
	else if(pid==0)
	{
		int val;
		close(sockfds[0]);
		while(1)
		{
			//sockfds[1]既能读又能写
			read(sockfds[1],&val,sizeof(val));
			++val;
			write(sockfds[1],&val,sizeof(val));
		}
	}
	return 0;
}

Unix域套接字_第1张图片

sendmsg/recvmsg

ssize_t recvmsg(int sockfd,  struct msghdr * msg, int flags);

ssize_t sendmsg(int sockfd,  struct msghdr * msg, int flags);

成功时候返回读写字节数,出错时候返回-1.

这2个函数只用于套接口,不能用于普通的I/O读写,参数sockfd则是指明要读写的套接口。

flags用于传入控制信息,一般包括以下几个
MSG_DONTROUTE                   send可用
MSG_DONWAIT                     send与recv都可用
MSG_PEEK                        recv可用
MSG_WAITALL                     recv可用
MSG_OOB                         send可用
MSG_EOR                         send recv可用

返回信息都记录在struct msghdr * msg中。 

struct msghdr {
       void * msg_name;//协议地址和套接口信息,在非连接的UDP中,发送者要指定对方地址端口,接受方
                        //用于的到数据来源,如果不需要的话可以设置为NULL(在TCP或者连接的UDP
                        //中,一般设置为NULL)。
        socklen_t msg_namelen;//上面的长度
        struct iovec * msg_lov;//指向真正要发送数据的缓冲区
        ssize_t msg_lovlen;//缓冲区的个数
        void * msg_control;//辅助数据的指针
        socklen_t msg_controllen;//辅助数据的长度
        int msg_flags; //用于返回之前flags的控制信息
}
#include 

struct iovec{
     void *iov_base; /* Pointer to data. */
     size_t iov_len; /* Length of data. */
};

UNIX域套接字传递描述符字

void send_fd(int sock_fd, int fd)
{
	int ret;
	struct msghdr msg;
	struct cmsghdr *p_cmsg;
	struct iovec vec;
	char cmsgbuf[CMSG_SPACE(sizeof(fd))];
	int *p_fds;
	char sendchar = 0;
	msg.msg_control = cmsgbuf;
	msg.msg_controllen = sizeof(cmsgbuf);
	p_cmsg = CMSG_FIRSTHDR(&msg);
	p_cmsg->cmsg_level = SOL_SOCKET;
	p_cmsg->cmsg_type = SCM_RIGHTS;
	p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
	p_fds = (int*)CMSG_DATA(p_cmsg);
	*p_fds = fd;

	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = &vec;
	msg.msg_iovlen = 1;
	msg.msg_flags = 0;

	vec.iov_base = &sendchar;
	vec.iov_len = sizeof(sendchar);
	ret = sendmsg(sock_fd, &msg, 0);
	if (ret != 1)
		ERR_EXIT("sendmsg");
}

int recv_fd(const int sock_fd)
{
	int ret;
	struct msghdr msg;
	char recvchar;
	struct iovec vec;
	int recv_fd;
	char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))];
	struct cmsghdr *p_cmsg;
	int *p_fd;
	vec.iov_base = &recvchar;
	vec.iov_len = sizeof(recvchar);
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = &vec;
	msg.msg_iovlen = 1;
	msg.msg_control = cmsgbuf;
	msg.msg_controllen = sizeof(cmsgbuf);
	msg.msg_flags = 0;

	p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg));
	*p_fd = -1;  
	ret = recvmsg(sock_fd, &msg, 0);
	if (ret != 1)
		ERR_EXIT("recvmsg");

	p_cmsg = CMSG_FIRSTHDR(&msg);
	if (p_cmsg == NULL)
		ERR_EXIT("no passed fd");


	p_fd = (int*)CMSG_DATA(p_cmsg);
	recv_fd = *p_fd;
	if (recv_fd == -1)
		ERR_EXIT("no passed fd");

	return recv_fd;
}

文件描述符的传递只能通过UNIX域协议套接字,当前使用的是sockpair,实现了父子进程间文件描述字的传递,如果是不相干的进程之间文件描述字的传递,就不能用sockpair,就要用UNIX域协议套接字进行传递。普通的tcp套接字、udp套接字是不能传递文件描述字。

你可能感兴趣的:(socket编程,UNIX域套接字,C/S)