UNP总结 Chapter 15~17 Unix域协议、非阻塞式I/O、ioctl操作

时间:2021-06-30 22:50:23


一、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域套接字上接收这个描述符,传递描述字不是传递描述字的编号,而是在接收进程中创建一个新的描述字,指向内核的文件表中与发送进程发送的描述字相同的项。

 

UNP总结 Chapter 15~17 Unix域协议、非阻塞式I/O、ioctl操作

 

 

4.程序实例

UNP总结 Chapter 15~17 Unix域协议、非阻塞式I/O、ioctl操作
#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);
}
UNP总结 Chapter 15~17 Unix域协议、非阻塞式I/O、ioctl操作

 

二、非阻塞式I/O

1.概述

1).输入操作: read, readv, recv, recvfrom和recvmsg函数。

2).输出操作: write, writev, send, sendto和sendmsg函数。

3).接收外来连接: accept函数

4).初始化外出的连接: 用于TCP的connect函数

 

2.非阻塞读和写

我们维护两个缓冲区: to容纳从标准输入到服务器去的数据,fr容纳自服务器到标准输出来的数据

UNP总结 Chapter 15~17 Unix域协议、非阻塞式I/O、ioctl操作

UNP总结 Chapter 15~17 Unix域协议、非阻塞式I/O、ioctl操作

这里程序仅给出包含非阻塞的前半部分:

UNP总结 Chapter 15~17 Unix域协议、非阻塞式I/O、ioctl操作
#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);
UNP总结 Chapter 15~17 Unix域协议、非阻塞式I/O、ioctl操作

 

3.非阻塞connect

非阻塞的connect有三种用途:

1). 我们可以在三路握手同时做一些其他的处理。完成一个connect要花一个往返时间完成,而且可以是在任何地方,从几个毫秒的局域网到几百毫秒或几秒的广域网。

2). 可以用这种技术同时建立多个连接。这在Web浏览器中很普遍

3). 由于我们用select等待连接的完成,因此可以给select设置一个时间限制,从而缩短connect的超时时间。

 

非阻塞connect虽然听似简单,却有一些必须处理的细节

1).即使套接口是非阻塞的,如果连接的服务器在同一台主机上,那么在调用connect建立连接时,连接通常会立即建立成功.我们必须处理这种情况;

2).源自Berkeley的实现(和Posix.1g)有两条与select和非阻塞IO相关的规则:

  • 当连接建立成功时,套接字描述符变成可写;
  • 当连接出错时,套接子描述符变成既可读又可写;

注意:当一个套接口出错时,它会被select调用标记为既可读又可写;

 

程序实例:

UNP总结 Chapter 15~17 Unix域协议、非阻塞式I/O、ioctl操作
#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);
}
UNP总结 Chapter 15~17 Unix域协议、非阻塞式I/O、ioctl操作

 

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