一、Unix域协议
Unix域协议并不是一个实际的协议族,它只是在同一台主机上进行客户-服务器通信时,使用与在不同主机上的客户和服务器间通信时相同的API(套接口或XTI)的一种方法。
当客户和服务器在同一台主机上时,Unix域协议是IPC通信方式的一种替代品。
Unix域提供了两种类型的套接口:字节流套接口(与TCP类似)和数据报套接口(与UDP类似)。
1.Unix域套接口地址结构
struct sockaddr_un {
sa_family_t sun_family; /* AF_LOCAL */
char sun_path[104]; /* null-terminated pathname */
};
存放sun_path数组中的路径名必须以空字符结尾
2.socketpair函数
socketpair函数建立一对相互连接的套接口,这个函数支队Unix域套接口使用。
#include <sys/socket.h>
int socketpair(int family, int type, int protocol, int sockfd[2]);
//返回: 成功返回0,出错返回-1
family必须为AF_LOCAL,protocol必须为0,type可以是SOCK_STREAM或SOCK_DGRAM,新创建的两个套接口描述字作为sockfd[0]和sockfd[1]返回
创建的两个套接口是没有名字的,即没有涉及隐式bind。
指定type参数为SOCK_STREAM调用socketpair所得到的结果称为流管道(stream pipe),这和一般的Unix管道(由pipe函数生成)类似,但流管道是全双工的,即两个描述字都是可读写的。
3.描述符传递
一般传递描述符的方法:
- 在fork调用后,子进程共享父进程的所有打开的描述字
- 在调用exec时所有描述字仍保持打开
第一个例子中进程打开一个描述字,调用fork,然后父进程关闭描述字,让子进程处理这个描述字。这样将一个打开的描述字从父进程传递到子进程。
两个进程之间传递描述符涉及的步骤:
1).创建一个字节流的或数据报的Unix域套接口
2).进程可以用任何返回描述字的Unix函数打开一个描述字:譬如open, pipe, mkfifo, socket或accept。
3).发送进程建立一个msghdr结构,其中包含要传递的描述字。
4).接收进程调用recvmsg在来自步骤1的Unix域套接字上接收这个描述符,传递描述字不是传递描述字的编号,而是在接收进程中创建一个新的描述字,指向内核的文件表中与发送进程发送的描述字相同的项。
4.程序实例
#include "unp.h"
int
my_open(const char *pathname, int mode)
{
int fd, sockfd[2], status;
pid_t childpid;
char c, argsockfd[10], argmode[10];
Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);
if ( (childpid = Fork()) == 0) { /* child process */
Close(sockfd[0]);
snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]);
snprintf(argmode, sizeof(argmode), "%d", mode);
execl("./openfile", "openfile", argsockfd, pathname, argmode,
(char *) NULL);
err_sys("execl error");
}
/* parent process - wait for the child to terminate */
Close(sockfd[1]); /* close the end we don't use */
Waitpid(childpid, &status, 0);
if (WIFEXITED(status) == 0)
err_quit("child did not terminate");
if ( (status = WEXITSTATUS(status)) == 0)
Read_fd(sockfd[0], &c, 1, &fd);
else {
errno = status; /* set errno value from child's status */
fd = -1;
}
Close(sockfd[0]);
return (fd);
}
二、非阻塞式I/O
1.概述
1).输入操作: read, readv, recv, recvfrom和recvmsg函数。
2).输出操作: write, writev, send, sendto和sendmsg函数。
3).接收外来连接: accept函数
4).初始化外出的连接: 用于TCP的connect函数
2.非阻塞读和写
我们维护两个缓冲区: to容纳从标准输入到服务器去的数据,fr容纳自服务器到标准输出来的数据。
这里程序仅给出包含非阻塞的前半部分:
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1, val, stdineof;
ssize_t n, nwritten;
fd_set rset, wset;
char to[MAXLINE], fr[MAXLINE];
char *toiptr, *tooptr, *friptr, *froptr;
val = Fcntl(sockfd, F_GETFL, 0);
Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);
val = Fcntl(STDIN_FILENO, F_GETFL, 0);
Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);
val = Fcntl(STDOUT_FILENO, F_GETFL, 0);
Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);
toiptr = tooptr = to; /* initialize buffer pointers */
friptr = froptr = fr;
stdineof = 0;
maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
for ( ; ; ) {
FD_ZERO(&rset);
FD_ZERO(&wset);
if (stdineof == 0 && toiptr < &to[MAXLINE])
FD_SET(STDIN_FILENO, &rset); /* read from stdin */
if (friptr < &fr[MAXLINE])
FD_SET(sockfd, &rset); /* read from socket */
if (tooptr != toiptr)
FD_SET(sockfd, &wset); /* data to write to socket */
if (froptr != friptr)
FD_SET(STDOUT_FILENO, &wset); /* data to write to stdout */
Select(maxfdp1, &rset, &wset, NULL, NULL);
3.非阻塞connect
非阻塞的connect有三种用途:
1). 我们可以在三路握手同时做一些其他的处理。完成一个connect要花一个往返时间完成,而且可以是在任何地方,从几个毫秒的局域网到几百毫秒或几秒的广域网。
2). 可以用这种技术同时建立多个连接。这在Web浏览器中很普遍
3). 由于我们用select等待连接的完成,因此可以给select设置一个时间限制,从而缩短connect的超时时间。
非阻塞connect虽然听似简单,却有一些必须处理的细节
1).即使套接口是非阻塞的,如果连接的服务器在同一台主机上,那么在调用connect建立连接时,连接通常会立即建立成功.我们必须处理这种情况;
2).源自Berkeley的实现(和Posix.1g)有两条与select和非阻塞IO相关的规则:
- 当连接建立成功时,套接字描述符变成可写;
- 当连接出错时,套接子描述符变成既可读又可写;
注意:当一个套接口出错时,它会被select调用标记为既可读又可写;
程序实例:
#include "unp.h"
int
connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
int flags, n, error;
socklen_t len;
fd_set rset, wset;
struct timeval tval;
flags = Fcntl(sockfd, F_GETFL, 0);
Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
error = 0;
if ( (n = connect(sockfd, saptr, salen)) < 0)
if (errno != EINPROGRESS)
return (-1);
/* Do whatever we want while the connect is taking place. */
if (n == 0)
goto done; /* connect completed immediately */
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset;
tval.tv_sec = nsec;
tval.tv_usec = 0;
if ( (n = Select(sockfd + 1, &rset, &wset, NULL,
nsec ? &tval : NULL)) == 0) {
close(sockfd); /* timeout */
errno = ETIMEDOUT;
return (-1);
}
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
return (-1); /* Solaris pending error */
} else
err_quit("select error: sockfd not set");
done:
Fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
if (error) {
close(sockfd); /* just in case */
errno = error;
return (-1);
}
return (0);
}
4.非阻塞accept
阻塞模式下,服务器会一直阻塞在accept调用上,知道其他某个客户建立一个连接为止,但是在此期间,服务器单纯阻塞在accept调用上,无法处理任何其他已就绪的描述符
非阻塞accept模式下解决办法
1).当使用select获悉某个监听套接字上何时有已完成连接准备被accept时候,总是把这个监听套接字设置为非阻塞
2).在后续的accept调用忽略以下错误:EWOULDBLOCK(客户终止连接时)、ECONNABORTED(客户终止连接时)、EPROTO(客户终止连接时)和EINTR(如果有信号被捕获)
三、ioctl操作
网络程序中ioctl常用于在程序启动时获得主机上所有接口的信息:接口的地址,接口是否支持广播,是否支持多播,等等。
#include <unistd.h>
int ioctl(int fd, int request, ... /* void *arg */ );
//返回:若成功0,出错-1
其中第三个参数总是一个指针,但指针的类型依赖于request参数,我们可以把和网络相关的请求(request)划分为6类:
- 套接口操作
- 文件操作
- 接口操作
- ARP高速缓存操作
- 路由表操作
- 流系统
用法详见UNP