Socket编程实践(13) --UNIX域名协议
UNIX域名协议
UNIX域套接字与TCP相比, 在同一台主机上, UNIX域套接字更有效率, 差点儿是TCP的两倍(因为UNIX域套接字不须要经过网络协议栈,不须要打包/拆包,计算校验和,维护序号和应答等,仅仅是将应用层数据从一个进程复制到还有一个进程, 并且UNIX域协议机制本质上就是可靠的通讯, 而网络协议是为不可靠的通讯设计的).
UNIX域套接字能够在同一台主机上各进程之间传递文件描写叙述符;
UNIX域套接字与传统套接字的差别是用路径名来表示协议族的描写叙述;
UNIX域套接字也提供面向流和面向数据包两种API接口,相似于TCP和UDP,可是面向消息的UNIX套接字也是可靠的,消息既不会丢失也不会顺序错乱。
使用UNIX域套接字的过程和网络socket十分相似, 也要先调用socket创建一个socket文件描写叙述符, family指定为AF_UNIX, type能够选择SOCK_DGRAM/SOCK_STREAM;
UNIX域套接字地址结构:
#define UNIX_PATH_MAX 108
struct sockaddr_un
{
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
基于UNIX域套接字的echo-server/client程序
/**Server端**/
void echoServer(int sockfd);
int main()
{
signal(SIGCHLD, sigHandlerForSigChild);
int listenfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (listenfd == -1)
err_exit("socket error"); char pathname[] = "/tmp/test_for_unix";
unlink(pathname);
struct sockaddr_un servAddr;
servAddr.sun_family = AF_UNIX;
strcpy(servAddr.sun_path, pathname);
if (bind(listenfd, (struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
err_exit("bind error");
if (listen(listenfd, 128) == -1)
err_exit("listen error"); while (true)
{
int connfd = accept(listenfd, NULL, NULL);
if (connfd == -1)
err_exit("accept error"); pid_t pid = fork();
if (pid == -1)
err_exit("fork error");
else if (pid > 0)
close(connfd);
else if (pid == 0)
{
close(listenfd);
echoServer(connfd);
close(connfd);
exit(EXIT_SUCCESS);
}
}
}
void echoServer(int sockfd)
{
char buf[BUFSIZ];
while (true)
{
memset(buf, 0, sizeof(buf));
int recvBytes = read(sockfd, buf, sizeof(buf));
if (recvBytes < 0)
{
if (errno == EINTR)
continue;
else
err_exit("read socket error");
}
else if (recvBytes == 0)
{
cout << "client connect closed..." << endl;
break;
} cout << buf ;
if (write(sockfd, buf, recvBytes) == -1)
err_exit("write socket error");
}
}
/**Client端代码**/
void echoClient(int sockfd);
int main()
{
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1)
err_exit("socket error"); char pathname[] = "/tmp/test_for_unix";
struct sockaddr_un servAddr;
servAddr.sun_family = AF_UNIX;
strcpy(servAddr.sun_path, pathname);
if (connect(sockfd, (struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
err_exit("connect error"); echoClient(sockfd);
}
void echoClient(int sockfd)
{
char buf[BUFSIZ] = {0};
while (fgets(buf, sizeof(buf), stdin) != NULL)
{
if (write(sockfd, buf, strlen(buf)) == -1)
err_exit("write socket error");
memset(buf, 0, sizeof(buf));
int recvBytes = read(sockfd, buf, sizeof(buf));
if (recvBytes == -1)
{
if (errno == EINTR)
continue;
else
err_exit("read socket error");
}
cout << buf ;
memset(buf, 0, sizeof(buf));
}
}
UNIX域套接字编程注意点
1.bind成功将会创建一个文件。权限为0777 & ~umask
2.sun_path最好用一个/tmp文件夹下的文件的绝对路径, 并且server端在指定该文件之前首先要unlink一下;
3.UNIX域协议支持流式套接口(须要处理粘包问题)与报式套接口(基于数据报)
4.UNIX域流式套接字connect发现监听队列满时,会立马返回一个ECONNREFUSED。这和TCP不同,假设监听队列满,会忽略到来的SYN,这导致对方重传SYN。
传递文件描写叙述符
socketpair
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);
创建一个全双工的流管道
參数:
domain: 协议家族, 能够使用AF_UNIX(AF_LOCAL)UNIX域协议, 并且在Linux上, 该函数也就仅仅支持这一种协议;
type: 套接字类型, 能够使用SOCK_STREAM
protocol: 协议类型, 一般填充为0;
sv: 返回的套接字对;
socketpair 函数跟pipe 函数是相似: 仅仅能在具有亲缘关系的进程间通信。但pipe 创建的匿名管道是半双工的,而socketpair 能够觉得是创建一个全双工的管道。
能够使用socketpair 创建返回的套接字对进行父子进程通信, 例如以下例:
int main()
{
int sockfds[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds) == -1)
err_exit("socketpair error"); pid_t pid = fork();
if (pid == -1)
err_exit("fork error");
// 父进程, 仅仅负责数据的打印
else if (pid > 0)
{
close(sockfds[1]);
int iVal = 0;
while (true)
{
cout << "value = " << iVal << endl;
write(sockfds[0], &iVal, sizeof(iVal));
read(sockfds[0], &iVal, sizeof(iVal));
sleep(1);
}
}
// 子进程, 仅仅负责数据的更改(+1)
else if (pid == 0)
{
close(sockfds[0]);
int iVal = 0;
while (read(sockfds[1], &iVal, sizeof(iVal)) > 0)
{
++ iVal;
write(sockfds[1], &iVal, sizeof(iVal));
}
}
}
sendmsg/recvmsg
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
它们与sendto/send 和 recvfrom/recv 函数相似,仅仅只是能够传输更复杂的数据结构,不仅能够传输一般数据。还能够传输额外的数据。如文件描写叙述符。
//msghdr结构体
struct msghdr
{
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary data buffer len */
int msg_flags; /* flags on received message */
};
struct iovec /* Scatter/gather array items */
{
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
msghdr结构体成员解释:
1)msg_name :即对等方的地址指针。不关心时设为NULL就可以;
2)msg_namelen:地址长度。不关心时设置为0就可以;
3)msg_iov:是结构体iovec 的指针, 指向须要发送的普通数据, 见下图。
成员iov_base 能够觉得是传输正常数据时的buf;
成员iov_len 是buf 的大小;
4)msg_iovlen:当有n个iovec 结构体时,此值为n。
5)msg_control:是一个指向cmsghdr 结构体的指针(见下图), 当须要发送辅助数据(如控制信息/文件描写叙述符)时, 须要设置该字段, 当发送正常数据时, 就不须要关心该字段, 并且msg_controllen能够置为0;
6)msg_controllen:cmsghdr 结构体可能不止一个(见下图):
7)flags: 不用关心;
//cmsghdr结构体
struct cmsghdr
{
socklen_t cmsg_len; /* data byte count, including header */
int cmsg_level; /* originating protocol */
int cmsg_type; /* protocol-specific type */
/* followed by unsigned char cmsg_data[]; */
};
为了对齐,可能存在一些填充字节(见下图)。跟系统的实现有关,但我们不必关心,能够通过一些函数宏来获取相关的值。例如以下:
#include <sys/socket.h>
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);
//获取辅助数据的第一条消息
struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg); //获取辅助数据的下一条信息
size_t CMSG_ALIGN(size_t length);
size_t CMSG_SPACE(size_t length);
size_t CMSG_LEN(size_t length); //length使用的是的(实际)数据的长度, 见下图(两条填充数据的中间部分)
unsigned char *CMSG_DATA(struct cmsghdr *cmsg);
进程间传递文件描写叙述符
/**演示样例: 封装两个函数send_fd/recv_fd用于在进程间传递文件描写叙述符**/
int send_fd(int sockfd, int sendfd)
{
// 填充 name 字段
struct msghdr msg;
msg.msg_name = NULL;
msg.msg_namelen = 0; // 填充 iov 字段
struct iovec iov;
char sendchar = '\0';
iov.iov_base = &sendchar;
iov.iov_len = 1;
msg.msg_iov = &iov;
msg.msg_iovlen = 1; // 填充 cmsg 字段
struct cmsghdr cmsg;
cmsg.cmsg_len = CMSG_LEN(sizeof(int));
cmsg.cmsg_level = SOL_SOCKET;
cmsg.cmsg_type = SCM_RIGHTS;
*(int *)CMSG_DATA(&cmsg) = sendfd;
msg.msg_control = &cmsg;
msg.msg_controllen = CMSG_LEN(sizeof(int)); // 发送
if (sendmsg(sockfd, &msg, 0) == -1)
return -1;
return 0;
}
int recv_fd(int sockfd)
{
// 填充 name 字段
struct msghdr msg;
msg.msg_name = NULL;
msg.msg_namelen = 0; // 填充 iov 字段
struct iovec iov;
char recvchar;
iov.iov_base = &recvchar;
iov.iov_len = 1;
msg.msg_iov = &iov;
msg.msg_iovlen = 1; // 填充 cmsg 字段
struct cmsghdr cmsg;
msg.msg_control = &cmsg;
msg.msg_controllen = CMSG_LEN(sizeof(int)); // 接收
if (recvmsg(sockfd, &msg, 0) == -1)
return -1;
return *(int *)CMSG_DATA(&cmsg);
}
int main()
{
int sockfds[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds) == -1)
err_exit("socketpair error"); pid_t pid = fork();
if (pid == -1)
err_exit("fork error");
// 子进程以仅仅读方式打开文件, 将文件描写叙述符发送给子进程
else if (pid == 0)
{
close(sockfds[1]);
int fd = open("read.txt", O_RDONLY);
if (fd == -1)
err_exit("open error");
cout << "In child, fd = " << fd << endl;
send_fd(sockfds[0], fd);
}
// 父进程从文件描写叙述符中读取数据
else if (pid > 0)
{
close(sockfds[0]);
int fd = recv_fd(sockfds[1]);
if (fd == -1)
err_exit("recv_fd error");
cout << "In parent, fd = " << fd << endl; char buf[BUFSIZ] = {0};
int readBytes = read(fd, buf, sizeof(buf));
if (readBytes == -1)
err_exit("read fd error");
cout << buf;
}
}
分析:
我们知道。父进程在fork 之前打开的文件描写叙述符。子进程是能够共享的,可是子进程打开的文件描写叙述符,父进程是不能共享的,上述程序就是举例在子进程中打开了一个文件描写叙述符,然后通过send_fd 函数将文件描写叙述符传递给父进程。父进程能够通过recv_fd 函数接收到这个文件描写叙述符。先建立一个文件read.txt 后输入几个字符,然后执行程序;
注意:
(1)仅仅有UNIX域协议才干在本机进程间传递文件描写叙述符;
(2)描写人物的价值的叙述性说明进程间传输文件传输文件不是叙述性休息(其实send_fd/recv_fd这两个值不同), 但创造在接收过程中一个新的文件中的符号的说明中被描述, 该文件和该文件识别符和发送处理的叙述性描述中的符号的说明中被描述被传递到内核在同一文件表项.
Socket编程实践(13) --UNIX域名协议的更多相关文章
- Socket编程实践(13) --UNIX域协议
UNIX域协议 UNIX域套接字与TCP相比, 在同一台主机上, UNIX域套接字更有效率, 几乎是TCP的两倍(由于UNIX域套接字不需要经过网络协议栈,不需要打包/拆包,计算校验和,维护序号和应答 ...
- C# socket编程实践
C# socket编程实践——支持广播的简单socket服务器 在上篇博客简单理解socket写完之后我就希望写出一个websocket的服务器了,但是一路困难重重,还是从基础开始吧,先搞定C# ...
- Socket编程实践(6) --TCP服务端注意事项
僵尸进程处理 1)通过忽略SIGCHLD信号,避免僵尸进程 在server端代码中添加 signal(SIGCHLD, SIG_IGN); 2)通过wait/waitpid方法,解决僵尸进程 sign ...
- Socket编程实践(6) --TCPNotes服务器
僵尸进程过程 1)通过忽略SIGCHLD信号,避免僵尸进程 在server端代码中加入 signal(SIGCHLD, SIG_IGN); 2)通过wait/waitpid方法.解决僵尸进程 sign ...
- Socket编程实践(10) --select的限制与poll的使用
select的限制 用select实现的并发服务器,能达到的并发数一般受两方面限制: 1)一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n(number)来调整或 ...
- Socket编程实践(1) 基本概念
1. 什么是socket socket可以看成是用户进程与内核网络协议栈的编程接口.TCP/IP协议的底层部分已经被内核实现了,而应用层是用户需要实现的,这部分程序工作在用户空间.用户空间的程序需要通 ...
- Socket编程实践(2) Socket API 与 简单例程
在本篇文章中,先介绍一下Socket编程的一些API,然后利用这些API实现一个客户端-服务器模型的一个简单通信例程.该例子中,服务器接收到客户端的信息后,将信息重新发送给客户端. socket()函 ...
- C# socket编程实践——支持广播的简单socket服务器
在上篇博客简单理解socket写完之后我就希望写出一个websocket的服务器了,但是一路困难重重,还是从基础开始吧,先搞定C# socket编程基本知识,写一个支持广播的简单server/clie ...
- Socket编程实践(12) --UDP编程基础
UDP特点 无连接,面向数据报(基于消息,不会粘包)的传输数据服务; 不可靠(可能会丢包, 乱序, 反复), 但因此普通情况下UDP更加高效; UDP客户/服务器模型 UDP-API使用 #inclu ...
随机推荐
- javascript 判断微信浏览器
原文:javascript 判断微信浏览器 用js判断当前环境是否是是微信内置浏览器有两个方法: 1.判断useragent 2.判断是否支持微信内置浏览器才支持的一些方法,比如WeixinJSBri ...
- Atitit.jquery 版本号新特性attilax总结
Atitit.jquery 版本号新特性attilax总结 1. Jq1.4 1 2. 1.5 1 3. 1.6 3 4. Jq1.7 3 ⒉提升了事件委派时的性能有了大幅度的提升.尤其是在ie7下: ...
- 怎样将baidu地图中的baidu logo 去掉
今天我的老大问我是不是能够将baidumap 的js版中baidu logo 去掉. 我上网查询一下,有各种方法.比方将相应的logo div remove hide 等等,这些都是须要JS 函数触发 ...
- 居然还有FindFirstChangeNotification函数
http://download.csdn.net/download/sololie/5966243
- linux-sfdisk 使用方法
功能说明:硬盘分区工具程序. 语 法:sfdisk [-?Tvx][-d <硬盘>][-g <硬盘>][-l <硬盘>][-s <分区>][-V < ...
- 【矩阵乘】【NOI 2012】【cogs963】随机数生成器
963. [NOI2012] 随机数生成器 ★★ 输入文件:randoma.in 输出文件:randoma.out 简单对照 时间限制:1 s 内存限制:128 MB **[问题描写叙述] 栋栋近期迷 ...
- Android常用开源项目
Android开源项目第一篇——个性化控件(View)篇 包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageView.Progre ...
- 给一个非常长的字符串str 另一个字符集比方{a,b,c} 找出str 里包括{a,b,c}的最短子串。要求O(n)
给一个非常长的字符串str 另一个字符集比方{a,b,c} 找出str 里包括{a,b,c}的最短子串.要求O(n). 比方,字符集是a,b,c,字符串是abdcaabcx,则最短子串为abc. 设置 ...
- Java 使用AES/CBC/PKCS7Padding 加解密字符串
介于java 不支持PKCS7Padding,只支持PKCS5Padding 但是PKCS7Padding 和 PKCS5Padding 没有什么区别要实现在java端用PKCS7Padding填充, ...
- Wix学习整理(3)——关于Windows Installer和MSI
原文:Wix学习整理(3)--关于Windows Installer和MSI 关于Windows Installer Windows Installer是微软Windows操作系统自带的一个软件安装和 ...