(二)socket编程(六)

目录

socket编程(十六)

UNIX域套接字(命名套接字)

UNIX域协议特点

UNIX域地址结构

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

UNIX域套接字编程注意点

socket编程(十七)

socketpair

利用socketpair实现全双工通信

sendmsg/recvmsg

UNIX域套接字传递描述符字


socket编程(十六)

UNIX域套接字(命名套接字)

Unix域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务通信的一种方式。是进程间通信(IPC)的一种方式。
它提供了两类套接字:字节流套接字(有点像TCP)和数据报套接字(有点像UDP)
UNIX域数据报服务是可靠的,不会丢失消息,也不会传递出错。

IP协议标识客户服务器是通过IP地址和端口号实现的,UNIX域协议中用于标识客户机和服务器的协议地址的是普通文件系统中的路径名。

UNIX域协议特点

  • 1)UNIX域套接字域TCP套接字相比,在同一台主机的传输速度前者是后者的两倍。UNIX域套接字仅仅复制数据,并不执行协议处理,不需要添加或删除网络报头,无需计算校验和,不产生顺序号,也不需要发送确认报文
  • 2)UNIX域套接字可以在同一台主机上各进程之间传递文件描述符
  • 3)UNIX域套接字域传统套接字的区别是用路径名表示协议族的描述

UNIX域地址结构

#define UNIX_PATH_MAX 128

struct sockaddr_un{
     sa_family_t sun_family;          /* AF_UNIX 或者 AF_LOCAL */
     char sun_path[UNIX_PATH_MAX];    /* path name */
};

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

服务端

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
 
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
 
#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 <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
 
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
 
#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

socket编程(十七)

socketpair

函数原形:

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

函数功能:

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

所属头文件:

#include <sys/types.h>     #include <sys/socket.h>

返回值

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

参数说明:

domain:协议家族

type:套接字类型

protocol:协议类型

sv:返回套接字对

 

利用socketpair实现全双工通信

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
 
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
 
#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;
}

sendmsg/recvmsg

函数原形:

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

函数功能:

利用sendmsg和recvmsg来指定发送接口或者获取接收数据接口

所属头文件:

 #include <sys/types.h>
 #include <sys/socket.h>

返回值

成功时候返回读写字节数,出错时候返回-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 <sys/uio.h>
 
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套接字是不能传递文件描述字。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值