《UNIX网络编程卷1》--笔记

1. 简介

  • POSIX:Portable Operating System Interface of UNIX,即可移植操作系统接口
  • ISO:International Organization for Standardization,即国际标准化组织
  • IEC:International Electrotechnical Commission,即国际电工委员会
  • Inc:the Institute for Electrical and Electronics Engineers,即电气与电子工程师协会
  • ANSIC C :American National Standards Institute,美国国家标准协会(ANSI)及国际标准化组织(ISO)推出的关于C语言的标准

2. 传输层:TCP、UDP、SCTP

2.5 流控制传输协议(SCTP)

  • SCTP中使用”关联”一词取代”连接”是为了避免这样的内涵:连接只涉及两个IP地址之间的通信,一个”关联”指代两个系统之间的一次通信,它可能因为SCTP支持多宿而涉及不知两个地址
  • SCTP是面向消息的

2.8 TIME_WAIT状态

  • Time_wait状态存在意义
    1, 可靠实现TCP全双工连接的终止:避免最后一个ack丢失,提供重传机制
    2, 允许重复分节在网络中消逝:防止端口被重用,引发错误

。。。。。。。(先把理论放一下)

3. 套接字编程简介

3.2.1 IPv4套接字的地址结构

  • 发现我的linux上struct sockaddr_in是这样定义的:
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;         /* Port number.  */
    struct in_addr sin_addr;        /* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
               __SOCKADDR_COMMON_SIZE -
               sizeof (in_port_t) -
               sizeof (struct in_addr)];
  };

确实符合POSIX标准,不过没有增加额外字段

  • 使用的地址的时候需要小心,注意in_addr是一个结构,而in_addr_t是通常是一个无符号32位整数
  • sin_zero字段未曾使用,最好置0(绑定一个非通配的IPv4的时候必须为0)
  • 套接字地址结构仅在给定主机上使用,不在主机之间传递

3.3.2 通用套接字的地址结构

  • 因为套接字地址结构总是以引用(指针)方式来传递,而套接字函数必须兼容任何协议簇的套接字地址结构,,当然有了ANSI C 之后可以用void *解决,不过这是1983年的事,1982年的解决办法是定义一个通用的地址结构:
struct osockaddr
  {
    //uint8_t sa_len;书上还有这一句话
    unsigned short int sa_family;
    unsigned char sa_data[14];
  };
  • 这就要求对指向任何 特定于协议的套接字地址结构的指针进行强制转化,例如:
struct sockaddr_in serv;
/* fill in serv{} */
bind(sockd, (struct sockadd *) &serv, sizeof(serv));

3.2.3 IPv6套接字地址结构

  • 我的机器中是下面这样定义的,注意顺序经过编排(从小到大),见Checklist 6.1,使得128的sockaddr_in6也按64位对齐(如果本身64位对齐)
/* IPv6 address */
struct in6_addr
  {
    union
      {
    uint8_t __u6_addr8[16];
#ifdef __USE_MISC
    uint16_t __u6_addr16[8];
    uint32_t __u6_addr32[4];
#endif
      } __in6_u;
#define s6_addr         __in6_u.__u6_addr8
#ifdef __USE_MISC
# define s6_addr16      __in6_u.__u6_addr16
# define s6_addr32      __in6_u.__u6_addr32
#endif
  };
/* Ditto, for IPv6.  */
struct sockaddr_in6
  {
    __SOCKADDR_COMMON (sin6_);
    in_port_t sin6_port;    /* Transport layer port # */
    uint32_t sin6_flowinfo; /* IPv6 flow information */
    struct in6_addr sin6_addr;  /* IPv6 address */
    uint32_t sin6_scope_id; /* IPv6 scope-id */
  };

3.2.5 套接字地址结构比较

这里写图片描述

3.3 值-结果参数

  • 从进程到内核传递传递套接字地址结构的函数有三个:bing,connect,sendto
  • 从内核到进程的有:accept,recvfrom,getsockname,getpeername
struct sockaddr-un cli;  /* Unix domain */
socklen_t len;
len= sizeof(cli);
/ len is a value */
getpeername( unixfd,(SA *) &cli, &len);
/* len may have changed */

注:当函数被调用时,结构大小是一个值(此值告诉内核该结构的大小,使内核在写此结构时不至于越界),当函数返回时,结构大小又是一个结果(它告诉进程内核在此结构中确切存储了多少信息),这种参数类型叫值结果参数
### 3.4字节排序函数
+ 大端:高字节放起始地址,如0x0102,内存中放的是0x0102
+ 小端:高字节放高地址,低字节放低地址如0x0102,内存中放的是0x0201
+ 网际协议使用大端字节序传送多字节整数

#include <netinet/in h>
uint16_t htons (uint16t host16bitvalue): //h:host
uint32_t htonl (uint32t host32bitvalue); //n:network
uint16_t ntohs (uint16t net16bitvalue);  //s:short
uint32_t ntohl (uint32t net32bitvalue);  //l:long

3.5 字节操纵函数

  • str开头:处理C字符串(即以\0结尾)
  • b开头:起源与4.2BSD,这里给出源自Berkeley的函数:
 #ifdef __USE_MISC
/* Copy N bytes of SRC to DEST (like memmove, but args reversed).  */
extern void bcopy (const void *__src, void *__dest, size_t __n)
     __THROW __nonnull ((1, 2));

/* Set N bytes of S to 0.  */
extern void bzero (void *__s, size_t __n) __THROW __nonnull ((1));

/* Compare N bytes of S1 and S2 (same as memcmp).  */
extern int bcmp (const void *__s1, const void *__s2, size_t __n)
     __THROW __attribute_pure__ __nonnull ((1, 2));

以后不要用memset了,bzero()好记多了

  • mem开头的,来看一下ANSI C给出的函数
__BEGIN_NAMESPACE_STD
/* Set N bytes of S to C.  */
extern void *memset (void *__s, int __c, size_t __n) __THROW __nonnull ((1));

/* Compare N bytes of S1 and S2.  */
extern int memcmp (const void *__s1, const void *__s2, size_t __n)
     __THROW __attribute_pure__ __nonnull ((1, 2));
__BEGIN_NAMESPACE_STD
/* Copy N bytes of SRC to DEST.  */
extern void *memcpy (void *__restrict __dest, const void *__restrict __src,
             size_t __n) __THROW __nonnull ((1, 2));
/* Copy N bytes of SRC to DEST, guaranteeing
   correct behavior for overlapping strings.  */
extern void *memmove (void *__dest, const void *__src, size_t __n)
     __THROW __nonnull ((1, 2));
__END_NAMESPACE_STD

/* Copy no more than N bytes of SRC to DEST, stopping when C is found.
   Return the position in DEST one byte past where C was copied,
   or NULL if C was not found in the first N bytes of SRC.  */
#if defined __USE_MISC || defined __USE_XOPEN
extern void *memccpy (void *__restrict __dest, const void *__restrict __src,
              int __c, size_t __n)
     __THROW __nonnull ((1, 2));
#endif /* Misc || X/Open.  */
  • 源字节串与目标字节串重叠时:bcopy能正确处理,memcpy不能
  • 助记:
    • desc = src
    • ANSI C的memxxx函数都需要一个长度参数且都在最后
    • memcmp:ptr1>ptr2返回大于0

3.6 inet_aton、inet_addr、inet_ntoa函数

  • int inet_aton(const char *strptr, struct in_addr *addrptr);
    • 将strptr指向的C字符串转换成32位网络字节序二进制值,通过addrptr存储,若addrptr为空,不存储有效值
  • in_addr_t inet_addr(const char *strptr);
    • 功能同上,出错返回INADDR_NONE常量(通常全1,即广播地址)或-1
  • char *inet_ntoa(struct in_addr inaddr);
    • 网络字节序二进制值转换成C字符串

3.7 inet_pton和inet_ntop函数

  • int inet_pton(int family, const char *strptr, void *addrptr);
    • family:AF_INET或AF_INET6
    • 成功返回1
  • const char inet_ntop(int family, const char *strptr, char strptr, size_t len);
    • 指定size_t防止溢出
      • #define INET_ADDRSTRLEN 16
      • #define INET6_ADDRSTRLEN 46
    • 总结
      地址转换
/* include inet_pton */
int
inet_pton(int family, const char *strptr, void *addrptr)
{
    if (family == AF_INET) {
        struct in_addr  in_val;

        if (inet_aton(strptr, &in_val)) {    //调用了上一节讲的函数,返回的是C字符串形式的IP地址
            memcpy(addrptr, &in_val, sizeof(struct in_addr));
            return (1);
        }
        return(0);
    }
    errno = EAFNOSUPPORT;
    return (-1);
}
/* end inet_pton */

3.8 scok_ntop和相关函数

  • 看部分自定义源码
/* include sock_ntop */
char *sock_ntop(const struct sockaddr *sa, socklen_t salen)
{
    char        portstr[8];
    static char str[128];       /* Unix domain is largest */

    switch (sa->sa_family) {
    case AF_INET: {
        struct sockaddr_in  *sin = (struct sockaddr_in *) sa;
        //地址转换:成功则返回c字符串形式的IP地址,str指定转换格式
        if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
            return(NULL);
        //字节排序:网络转换为主机的字节序
        if (ntohs(sin->sin_port) != 0) {
            snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
            strcat(str, portstr);
        }
        return(str);
    }
    //。。。。。。

3.9 字节流套接字

  • 不用通常问文件IO是因为内核中用于套接字的缓冲区可能已经到达了极限,会造成实际比请求的少
  • 以readn()为例,看自定义的源码,感觉没有特别要注意的地方
ssize_t                     /* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n)
{
    size_t  nleft;
    ssize_t nread;
    char    *ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {

        //如果读取失败
        if ( (nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR) /* 查找EINTR错误,表示系统被一个捕获信号中断 */
                nread = 0;      /* and call read() again */
            else
                return(-1);
        //如果读成功了    
        } else if (nread == 0)
            break;              /* EOF */
        //计算漏读的字节数,再读文件
        nleft -= nread;
        ptr   += nread;
    }
    return(n - nleft);      /* return >= 0 */
}
/* end readn */
  • readline函数因为效率低,书上给了一个高效的写法(代码太长,就不粘贴了)代码在这

4. 基本TCP套接字编程

4.2 socket函数

  • int socket(int family, int type, int protocol)
    • family:指明协议簇
      • AF_INET :IPv4协议
      • AF_INET6 :IPv6协议
      • AF_LOCAL : Unix域协议
      • AF_ROUTE :路由套接字
      • AF_KEY : 密钥套接字
    • type:套接字类型
    • protocol:传输协议
    • 返回:套接字描述符
  • 注意:有的传输协议只支持特定的套接字类型,如TCP只支持SOCK_STREAM,可以填0由type和family组合默认选择

4.3 connect函数

  • int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
    • sockfd:socket()函数返回的套接字描述符
    • servaddr:指向套接字地址结构的指针
  • 如果是TCP套接字会激发三次握手过程,仅在连接建立成功或出错时才返回,其中出错返回可能有以下几种情况
    • TCP客户没有收到SYN分节的响应,则返回BTIMEDOUT错误
    • 对客户的SYN的响应是RST(例如服务器进程也许没在运行),这是硬错误会马上返回
    • 客户发出的SYN在中间的某个路由器上引发了一个“destination unreachable”(目的地不可达) ICMP错误(软错误)–>24s后再发,共75s未回作为EHOSTUNREACH或ENETUNREACH错误返回给进程
      • 按照本地系统的转发表,根本没有到达远程系统的路径
      • connect调用根本不等待就返回

4.3 bind()函数

  • int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
    • myaddr:因为有const修饰,所以如果使用默认值内核分配的端口是无法返回的,不过可以用getsockname()来返回协议地址

4.4 listen()函数

把一个未连接的套接字转换成一个被动套接字,即从CLOSE转换到LISTEN状态

  • int listen(int sockfd, int backlog);
    • backlog:指定内核应为相应套接字排队的最大连接数
      • 未完成连接队列:处于SYN_RCVD状态
      • 已完成连接队列:处于ESTABLISHED状态
    • 可以通过允许环境变量LISTENQ覆写由调用者指定的值
    • 如果设置设置成未连接队列最大长度易遭受SYN泛滥攻击

4.5 accept()函数

  • int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
    • 用于从已完成队列头返回下一个已完成连接
    • cliaddr:返回客户地址
    • addrlen:值-结果参数
    • 返回值:内核自动生成的一个全新描述符,也叫已连接套接字描述符,它的第一个参数叫监听套接字描述符

4.6 fork()函数和exec()函数

  • pid_t fork(); :这个函数再《UNIX环境高级编程》有更详细的叙述
  • execX(...):见书本90页
    这里写图片描述

4.9 close()函数

  • int close(sockfd);:可以用来关闭套接字,并终止TCP连接
  • 确实想终止连接可以用 shutdown()函数

4.10 getsockname()和getpeername()函数

  • int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);:返回本地IP和端口号
    • sockfd:必须时已连接套接字描述符(socket()函数的返回值)
  • int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
    • 获取客户身份的唯一途径

5. TCP 客户/服务器程序实例

  • 1.编译代码: gcc tcpserv01.c -o serv -L. ../libunp.a
    gcc tcpcli01.c -o cli -L. ../libunp.a
  • 2.运行代码:./serv &
    ./cli
  • 3.查看运行情况:netstat -ant
  • 4.产看进程间关系:ps -ef –forest | sed -n ‘/tcp/p’
  • 5.客户端进程输入:ctrl+D,就是EOF
  • 6.查看:netstat -ant | grep 9877 ,得到下面的输出结果(-代替空格)

    tcp—0—-0 0.0.0.0:9877———-0.0.0.0:*————-LISTEN
    tcp—-0—-0 127.0.0.1:39960—-127.0.0.1:9877—-TIME_WAIT

  • 7.查看进程:ps -ef –forest -o pid,ppid,tty,stat,args,wchan | sed -n ‘/tcp/p’

110361 109527 pts/18   S     \_ ./tcpserv01 XDG_VTNR=7  inet_csk_accept
110366 110361 pts/18   Z     |   \_ [tcpserv01] <defunc -
110517 109527 pts/18   S+    \_ sed -n /tcp/p XDG_VTNR= pipe_wait

可以看到僵死进程

5.8 POSIX信号处理

  • 1.signal:有时也叫软件中断(software interrupt),每个信号都有与之关联的处置,也叫行为
    • 1.除SIGKILL和SIGSTOP两个信号不能被忽略外,其他信号都可以设置成SIG_IGN来忽略
    • 2.设SIG_DFL来启用默认设置
  • 2.signal函数原型:void (*signael (int signo, void (*fun)(int)))(int);
    • 1.为了简化添加宏定义:#typedef void Sigfunc(int);
    • 2.简化后:Sigfunc *signal(int signo, Sigfunc *func);

5.9 处理SIGCHLD信号

SIGCLD:在一个进程终止或者停止时,将SIGCHLD信号发送给其父进程;设置僵死进程是为了维护子进程信息,不处理可能导致耗尽资源

5.10 wait和waitpid函数

#include<sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int option);
  • 返回子进程的进程ID号
  • statloc:代表进程终止状态的一个整数
  • option:附加选项,常用的是WNOHANG(告知没有子进程时不终止,有尚未终止的子进程在运行时不要阻塞)
  • (1)当fork子进程时,必须捕获SIGCHLD信号;
  • (2)当捕获信号时,必须处理被中断的系统调用;
  • (3) SIGCHLD的信号处理函数必须正确编写,应使用waitpid函数以免留下僵死进程。

5.11 accept()返回连接前终止

这里写图片描述
+ 服务器:已由TCP排队,等待进程调用accept()却受到RST,稍后执行accept()
+ 处理方法:
+ Berkeley:完全在内核中处理已终止的连接,服务器进程根本看不到
+ 大多数SVR4:返回一个EPROTO(protocol error:协议错误)
+ POSIX:errno值必须是ECONNABORTED(software caused connection abort,软件引起的连接终止),因为流子系统其他一些致命协议相关事件时也会返回EPROTO

5.12 服务器进程终止

杀死服务器子进程(模拟服务器进程崩溃),server发送FIN,然而client阻塞在fgets(同时应对两个文件描述符,套接字和用户输入),从未无法返回ACK,此时键入一行文本,客户端将发送到服务器,此时有两种情况:
+ 1.调用readline(),然后收到RET,由于前面的FIN,readline()立刻返回0,收到未预期的EOF,提示出错信息:server terminated prematurely,(服务器过早终止)
+ 先收到RET,调用readline()时返回一个ECONNRESET(connection reset by peer),对方复位连接错误

5.13 SIGPIPE信号

上节情况下的客户端不理会readline()的错误反而写入更多的数据到服务器,即一个进程向某个已收到RST的套接字执行写操作,内核会向进程发送一个SIGPIPE信号,默认行为是终止进程。无论是捕获还是忽略该信号都将返回EPIPE错误。没有特殊事情要做就设置成SIG_IGN(忽略)
+ 需要注意:如果使用多个套接字,该信号的提交没法告诉我们是哪一个套接字出错了
- 所以要么不理会
- 要么从信号函数返回后再处理来自wirite的EPIPE

5.14 服务主机崩溃

  • 服务器崩溃后再向服务器发送数据,客户TCP会持续重传数据分节,试图重服务器接收一个ACK,会有三种结果:
    • 1.对客户TCP数据分节没有响应:返回ETIMEDOUT
    • 2.ICMP不可达,返回 EHOSTUNREACH
    • 3.ICMP不可达,返回 ENETUNREACH

5.15 服务器崩溃后重启

  • 崩溃的主机重启后丢失崩溃前的所有连接信息,再次收到客户TCP的信息返回RST,当收到服务器的RST时,客户TCP正阻塞于readline(),的导致返回ECONNRESET错误

5.16 服务器主机关机

  • unix关机时,init进程先给所有进程发送 SIGTERM(sigterm,往往5~20s时间),然后给所有进程发送SIGKILL,后面发生的事情跟5.12节一样

5.17 TCP程序例子小结

  • 本地端口由bind指定,bind指定的IP地址通常是通配IP地址。
  • 多宿主机上绑定通配IP地址后可以通过getsockname确定本地IP
  • 两个外地址由accept调用返回给服务器(4.10节讲了)

5.18 数据格式

  • 穿越套接字传递二进制结构绝对是不明智的
    • 可以把所有的数据当作文本串来传递
    • 显示定义所有数据的二进制格式,并以这样的格式在客户端与服务器之间传递数据。远程过程调用(remote Procedure call,RPC)软件包通常使用这种技术

6. I/O复用:select和poll函数

6.1 概述

  • I/O复用:内核一旦发现进程指定的一个或多个I/O条件就绪(也就是说输入已准备好被读取,或者描述符已能承接更多的输出),就通知进程。

6.2 I/O模型

  • I/O模型
    • 阻塞式I/O:系统直到数据包到达且被复制到应用进程缓冲区或发生错误才返回
    • 非阻塞式I/O:应用进程持续轮询(polling)内核,以查看操作是否就绪(对一个非阻塞描述符循环调用recvfrom)。
    • I/O复用(select 和 poll):阻塞在这两个系统调用的某一个上,而不是阻塞在真正的I/O系统调用上,优势在于可以等待多个描述符就绪
    • 信号驱动式I/O(SIGIO):当数据报准备好读取时,内核为进程产生一个SIGIO信号
    • 异步I/O(POSIX的aio_系列函数):告知内核启动某个动作,并让内核在整个操作完成后通知我们。
      输入操作:1.等待数据准备好,2.从内核向进程复制数据
      这里写图片描述

6.3 select函数

  • int select(int maxfdp1,fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
    • timeout:告知内核等待指定描述符中的任何一个就绪需要花多少时间
    • struct timeval {
      long tv_sec; /* seconds */
      long tv_usec; /* microseconds ,许多UNIX向上舍10ms整数倍,再加调度延迟,时间更不准确*/
      }

      • 表示永远等待下去:置空指针,仅在有描述符准备好I/O时才返回
      • 等待一段固定的时间:由timeout指定
      • 根本不等待:定时器值置为0,这称为轮询(poll)
    • fd_set变量使用例子(maxfdp1设置为6):注意时值-结果参数(返回以后需要重新对感兴趣的位置1)
      • fd_set rset;
        FD_ZERO(&rset);
        FD_SET(1, &rset);
        FD_SET(4, &rset);
        FD_SET(5, &rset);
    • 计时器到时返回0,-1表示出错
  • 描述符就绪的条件
    • 一个套接字准备好读的情况:
      • 1.接收缓冲区中字节数 >= 接收缓冲区低水位标记的当前大小(默认1,由SO_RCVLOWAT设置)
      • 2.读半部关闭(接收了FIN)将不阻塞并返回0
      • 3.监听套接字的已连接数不为0,这时accept通常不阻塞
      • 4.其上有一个套接字错误待处理,返回-1,error设置成具体的错误条件,可通过SO_ERROR套接字选项调用getsockopt获取并清除
    • 一个套接字准备好写
      • 1.以连接套接字或UDP套接字发送缓冲区中的可用字节数 >= 发送缓冲区低水位标记的当前大小(默认2048,可用SO_SNDLOWAT)
      • 2.写半部关闭的套接字,写操作将产生一个SIGPIPE信号
      • 3.非阻塞式connect的套接字已建立连接,或者connect以失败告终
      • 4.其上有一个套接字错误待处理,返回-1,error设置成具体的错误条件,可通过SO_ERROR套接字选项调用getsockopt获取并清除
        这里写图片描述
  • 混合使用stdio和select被认为是非常容易犯错误的
    • readline缓冲区中可能有不完整的输入行
    • 也可能有一个或多个完整的输入行

6.6 shutdown 函数

int shutdown(int sockfd, int howto)

  • close()把描述符的引用计数减1,shutdown直接激发TCP的正常连接序列的终止
  • shutdown告诉对方我已近完成了数据的发送(对方仍然可以发给我)
  • SHUT_RD:关闭连接的读这一半
    • 可以把第二个参数置为SHUT_RD防止回环复制
    • 关闭SO_USELOOPBACK套接字选项也能防止回环
  • SHUT_WR:关闭连接的写这一半,也叫半关闭
  • SHUT_RDWR:连接的读半部和写半部都关闭

6.8 TCP回射服务器程序

见代码注释:github链接

6.9 pselect函数

#include<sys/select.h>
#include<signal.h>
#include<time.h>
int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *execptset, const struct timespec *timeout,const sigset_t *sigmask);

相比较于select:
+ 1.使用timespec 而不用timeval

struct timespec {
    time_t tv_sec;  //秒
    long tv_nsec;   //注意:这个表示纳秒
}
  • 2.增加了第六个参数:指向信号掩码的指针
    • 允许先禁止递交某些信号,再测试由禁止信号的信号处理函数设置全局变量,然后调用pselect,告诉新设置的信号掩码

6.10 poll函数

功能与select类似,不过在处理流设备时能提供更多的信息

#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
  • fdarray:pollfd结构体指针


    • struct pollfd {
      int fd ; //需要检查的描述符,负数将被忽略
      short events; //测试条件
      short revents; //返回的描述符状态
      }
  • nfds:结构体数组中元素的个数
  • timeout:返回前需要等待多长的时间
    • INFTIM:永远等待,可能在

6.11 TCP回射服务器程序(再修版)

见代码注释:github链接


7. 套接字选项

7.2 getsockopt和setsockopt函数:仅用于套接字

#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
均返回:成功0,出错-1
  • sockfd:一个打开的套接字描述符
  • level:(级别)指定系统中解释选项的代码或为通用套接字代码,或为特定于某个协议的代码(IPV4、IPV6、TCP、SCTP)
  • optval:指向某个变量的指针,set..通过它获取新值,get..把已获取的选项当前值存放到*optval
  • optlen:optval的长度
  • 套接字选项粗分为两大基本类型:
    • 标志选项:启用或禁止某个特性的二元选项
      • getsockopt:optval为0表示禁止,否则表示启用
      • setsockopt:optval为0用来禁止
    • 值选项:取得并返回我们可以设置或检查的特定值选项
      • 用户进程与系统之间传递所指数据类型的值

7. 套接字选项

7.2 getsockopt和setsockopt函数:仅用于套接字

#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
均返回:成功0,出错-1
  • sockfd:一个打开的套接字描述符
  • level:(级别)指定系统中解释选项的代码或为通用套接字代码,或为特定于某个协议的代码(IPV4、IPV6、TCP、SCTP)
  • optval:指向某个变量的指针,set..通过它获取新值,get..把已获取的选项当前值存放到*optval
  • optlen:optval的长度
  • 套接字选项粗分为两大基本类型:
    • 标志选项:启用或禁止某个特性的二元选项
      • getsockopt:optval为0表示禁止,否则表示启用
      • setsockopt:optval为0用来禁止
    • 值选项:取得并返回我们可以设置或检查的特定值选项
      • 用户进程与系统之间传递所指数据类型的值

7.3 检查选项是否受支持并获取默认值

见注释:代码链接

7.4 套接字状态

accept一直要到三次握手完成以后才返回给服务器已连接的套接字,想在三次握手完成时确保这些套接字选项中的某一个是给 已连接套接字 设置的,必须先设置 监听套接字

7.5 通用套接字选项

7.5.1 SO_BROADCAST

  • 本选项开启或禁止进程发送广播(仅数据报套接字支持,且需在支持广播消息的网络上如以太网和令牌环网)
  • 可以防止没有设置成广播时发送广播数据:如UDP发送一个的目的地址是一个广播地址,但是该选项没设置,就会返回EACCES错误

7.5.2 SO_DEBUG

仅由TCP支持,选项开启时内核将为TCP在该套接字发送和接收所有分组保留详细信息,可用trpt查看

7.5.3 SO_DONTROUTE

规定外出分组将绕过底层协议的正常路由机制,用来绕过路由表,以强制将分组从特定的接口发出

7.5.4 SO_ERROR(可获取不能设置)

套接字发生错误时,将套接字的so_error变量设置为为Unix Exxx值中的一个,也叫待处理错误(pending error),可以用下面两种方式中的一种立即通知进程
+ 1.阻塞在select时,返回设置RW中一个或两个条件
+ 2.信号驱动IO模型:产生SIGIO信号,进程通过访问SO_ERROR获取so_error的值getsockopt()返回的整个数值就是待处理错误,处理后复位为0
+ 阻塞在read()且没有数据且so_error非0,返回-1,error设置为so_error的值,so_error设置为0。
+ 阻塞在read()但有数据,则返回数据
+ 调用write时so_error非0,返回-1,error设置为so_error的值,so_error设置为0

7.5.5 SO_KEEPALIVE

设置保活选项后,2小时后(期间没有数据)TCP自动发送保活探测分节(keep-alive probe),会导致三种情况
+ 1.以期望ACK响应,进程得不到通知
+ 2.响应RST,表对端已崩溃并重启,套接字的待处理错误设置为ECONNRESET
+ 3.没有任何响应,间隔75s再发8个探测分节,11m15s后放弃且带错李错误设置为ETIMEOUT.如果收到ICMP错误就返回相应错误。
+ 这是一个清理通向不可达客户的半开连接的好方法

7.5.6 SO_LINGER

本选项指定close()函数对面向连接的协议如何操作,默认立即返回,如果有数据残留将尝试把这些数据发送给对端
检测TCP条件的各种方法
要求传送给内核如下结构:

struct linger {
     int l_onoff;    /*0=off,l_linger被忽略,>nonzero=on*/
     int l_linger;   /*linger time*/
}
  • linger=0:丢弃缓冲区的任何数据,发送RST给对端,没有四分节终止序列,可避免TCP的TIME_WAIT状态。可能引发错误:在2MSL内创建另一个化身,刚终止的连接上的旧的分节不被正确的传递到新的化身上
  • linger!=0,套接字关闭时内核拖延一段时间:进程将睡眠到所有数据已发送并被确认或延滞时间到
  • 套接字是非阻塞型的,延滞时间到之前数据没发送完返回EWOULDBLOCK错误
  • close()成功返回只能说明发送的数据和FIN已有对端确认,但不代表进程已经读取,所以改用shutdown好一点,当然也能用应用级ACK
    shutdown和SO_LINGER情况总结

7.5.7 SO_OOBINLINE

  • 带外数据将被留存在正常的输入队列中(即在线留存),此时接收函数的MSG_OOB标志不能用来读取带外数据

7.5.8 SO_CVBUF和SO_SNDBUF

  • 套接字接收缓冲区中可用空间大小限定了TCP通告对端窗口的大小
  • 注意顺序:窗口规模选项是在建立连接时用SYN分节得到的,所以客户需在connect之前,serv需在listen之前
  • 根据快恢复算法,缓冲区大小至少应是MSS值的四倍,最好是偶数倍

7.5.9 SO_RCVLOWAT和SO_SNDLOWAT(低水位标记)

  • 接收低水位标记:select返回可读时接收缓冲区所需的数据量,T/UDP、SCTP默认为1
  • 发送缓冲区:select()返回可写时发送缓冲区所需的可用空间。tcp默认2048,UDP的发送缓冲区的可用字节数从不改变(不保留副本)

7.5.10 SO_RCVTIMEO和SO_SNDTIMEO

设置超时值,默认设置为0,即禁止超时

7.5.11 SO_REUSEADDR和SO_REUSEPORT(重用地址端口)

  • 监听服务器终止,子进程继续处理连接,重启监听服务器时会出错,而开启了SO_REUSEADDR就不会。
  • SO_REUSEADDR允许同一个端口的多个服务器实例(只要不同的本地IP地址即可),通配地址捆绑一般放到最后
  • SO_REUSEADDR允许同一个端口捆绑同一个进程的不同套接字
  • SO_REUSEADDR允许UDP完全重复捆绑(一般来说),用于多播

7.5.12 SO_TYPE

  • 本选项返回套接字类型,返回值是一个诸如SOCK_STREAM或SOCK_DGRAM之类的值,通常由启动时继承了套接字的进程使用

7.5.12 SO_USELOOPBACK

仅用于路由域(AF_ROUTE)套接字,默认打开。开启时,相应套接字将接收在其上发送的任何数据报的一个副本。

7.6 IPv4套接字选项

级别为IPPROTO_IP(get/setsockopt()的第二个参数)

7.6.1 IP_HDRINCL

  • 如果是给原始IP套接字设置的,必须自己构造首部,下列情况例外:
    • 见Page168

7.6.2 IP_OPTIONS

允许在IPv4首部总设置IP选项

7.6.3 IP_RECVDSTADDR

  • 开启导致所收到的UDP数据报的目的地址由recvmsg作为辅助函数返回

7.6.4 IP_RECVIF

  • 开启导致所收到的UDP数据报的接收接口索引由recvmsg函数作为辅助函数返回

7.6.5 IP_TOS

  • 本套接字选项允许我们为TCP、UDP、SCTP设置IP首部中的服务类型字段。。。

7.6.6 IP_TTL

  • 用于设置或获取系统用在从某个给定套接字发送的单播分组上的默认TTL值

7.7 ICMPv6套接字选项

  • 级别为IPPROTO_ICMPV6
  • ICMP6_FILTER:用于获取或设置一个icmp6_filter结构

7.8 IPv6套接字选项

  • 级别为:IPPORTO_IPV6

7.8.1 IPV6_CHECKSUM

  • 指定用户数据中校验和所处位置的字节偏移

7.8.2 IPV6_DONTFRAG

  • 禁止为UDP套接字或原始套接字自动插入分片首部,将外出分组中大小超过发送接口MTU的那些分组将被丢弃

7.8.3 IPV6_NEXTHOP

  • 将外出数据报的吓一跳地址指定为一个套接字地址结构

7.8.4 IPV6_PATHMTU

  • 返回路径MTU发现功能确定的当前MTU

7.8.5 IPV6_RECVDSTOPTS

  • 任何接收的IPv6地址都将由recvmsg作为辅助函数返回,默认关闭

7.8.6 IPV6_RECVHOPLIMIT

  • 开启后,任何接收的跳限字段都将由recvmsg作为辅助函数返回

7.8.7 IPV6_RECVHOPOPTS

  • 开启时,任何接收的IPv6跳选项作为辅助函数返回

。。。不懂这节在说什么

7.9 TCP套接字选项

级别:IPPROTO_TCP

7.9.1 TCP_MAXSEG

  • 该选项允许设置或获取TCP连接的最大分节大小,返回值是TCP可以发送给对端的最大数据量

7.9.2 TCP_NODELAY

  • 本选项将禁止TCP的Nagle算法(防止一个连接在任何时刻有多个小分组待确认)
  • Nagle算法通常和ACK延滞算法(delayed ACK algorithm),该算法使得TCP在接收到数据后延滞一段时间(50-200ms)再确认
    +对于客户以若干小片发送数据向服务器发送单个逻辑请求:首选方法是调用writev(),次一点是两次数据放到缓冲区然后调用write(),最次方法是先设置TCP_NODELAY再调用两次write()

7.10 SCTP套接字选项

级别:IPPROTO_SCTP

………………………………………(后面再看)

7.11 fcntl函数

  • fcntl()函数(file control)可执行各种描述符控制操作
    小结
#include <fcntl.h>
int fcntl(int fd, int cmd, .../* int arg */);
  • 每种描述符都有一组由F_GETFL命令获取或由F_SETFL命令设置的文件标志,影响套接字描述符的有两个:O_NONBLOCK(非阻塞式IO),O_ASYNC(信号驱动式IO)
  • 正确设置非阻塞式IO的写法:
int flag;
/* Set a socket as nonblocking */
if((flag=fcntl(fd, F_GETFL, 0)) < 0){    //必须要先获取其他文件标志
    err_sys("F_GETFL, error");
}
flag |=O_NONBLOCK;                       //或运算,打开非阻塞标志
if(fcntl(fd, F_SETFL, flags) <0 ){
    err_sys("F_SETFL error");
}
flag &=~O_NONBLOCK;                      //与运算,关闭非阻塞标志
if(fcntl(fd, F_SETFL, flags) <0 ){
    err_sys("F_SETFL error");
}
  • F_SETOWN的参数是正值则指出接收信号的进程ID,是负数则绝对值指出信号的组ID
  • F_GETOWN与上面类似
  • 使用socket()函数创建的套接字没有属组。如果一个新的套接字是从一个监听套接字创建而来,属组将继承过来。

8. 基本UDP套接字编程

recvfrom()和sendto()函数

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flag, struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flag, const struct sockaddr *to, socklen_t addrlen);
  • buff:接收的内容,可以为0
  • falg:将在后面讨论
  • from:发送方的地址

8.3 UDP回射服务程序:main函数

代码地址

8.5 UDP回射客户程序

代码地址

8.9服务器进程未运行

  • 用tcpdump工具可以看到返回一个端口不可达的ICMP错误,不过不会通知进程,也叫异步错误。有一个重要的决定:仅在进程已将其UDP套接字连接到恰恰一个对端后,这个异步 错误才返回给进程

8.10 UDP程序例子小结

8-13_从IP数据报获取信息

8.11 UDP的connect函数

  • 这里的connect()不同于TCP,只检查时候存在立即可知的错误,记录对端IP地址和端口号,然后返回给进程。连接后主要发生三点变化:

    • 1.不指定目的地址,即不用sendto(或第六个参数为空指针),改用write()或send().
    • 2.不必使用recvfrom(),而改用read(),recv(),recvmsg(),这说明一个UDP套接字仅与一个IP地址作数据交换(多播、广播地址)
    • 3.异步错误将返回给进程
    • 4.可多次调用connect(),以断开套接字或指定新的IP地址和端口号
      • 最便于移植的方法是清零一个地址结构后把它的地址簇成员设置为AF_UNSPEC
    • 如果要给同一个目的地址发送多个数据报, 显式 connect()好一点

    /etc/resolv.conf:保存服务器主机IP的文件

8.12 dg_cli函数(修订版)

代码是书上的
+ 当连接一个没有运行的udp服务器的程序时,连接不会出错,但是发送数据时会返回一个目的端口不可达的ICMP错误,被内核映射成ECONNREFUSED,UnixWare内核不会返回这种错误(page200)

8.13 UDP缺乏流量控制

  • netstat -s:显示丢失的数据包
  • UDP发送端淹没接收端是轻而易举的事
  • FreeBSD5.1限制一个套接字接收缓存区默认大下262 144字节(实际233 016字节)
  • 修改缓存区代码

8.14 UDP的外出接口的确定

  • connect()函数的一个副作用是可用来确定特定目的地的外出接口(本地IP通过为目的地址搜索路由表得到)
  • 代码见地址

8.15 使用select()的TCP和UDP回射服务器程序

8.16 小结

8.1
recvfrom返回2048个字节的数据,它不会返回大于一个数据报大小的数据

9. 基本SCTP套接字编程

9.1 概述

  • 是一个面向消息的协议,在端点之间提供多个流,并为多宿提供传输级支持,

9.2 接口模型

  • 一到一形式(方便把现有TCP引应用移植到STCP中)
    • TCP套接字选项必须映射成SCTP选项
    • SCTP保存边界消息
    • SCTP没有半关闭,需要在数据流中告知对端
    • sendto或sendmesg指定的任何地址都被认为是对目的地址的重写,send()使用变普通
  • 一到多形式
    • 客户关闭一个关联时,服务器自动关闭
    • 在四路握手的三四分组中捎带数据的唯一方式
    • 没有建立的连接的IP使用sendto等导致对端主动打开尝试
    • 用户不能使用send()或write()
    • 任何时候调用分组发送函数时所用的目的地址是在系统关联建立阶段选定的
    • 关联事件可能被启动

9.3 stcp_bindx()函数

#include <netinet/sctp.h>
int sctp_bindx(int sockfd, const struct sockaddr *addrs, int addrcnt, int flags);
  • SCTP允许捆绑主机一个特定的子集
  • sockfd:返回的套接字描述符
  • addrs:紧凑的地址列表指针
    int sctp_bindx(int sockfd, const struct sockaddr *addrs, int addrcnt, int flags);
  • addrcnt:操作的指针数
  • flags:
    • SCTP_BINDX_REM_ADDR :删除
    • SCTP_BINDX_ADD_ADDR :增加
  • 所有地址结构和端口号必须相同

9.4 sctp_connectx()函数

#include <netinet/sctp.h>
int sctp_connectx(int sockfd, const struct sockaddr *addrs, int addrcnt);
Returns: 0 for success, –1 on error
  • 用于连接到一个多宿对端主机

9.5 sctp_getpaddrs()函数

#include <netinet/sctp.h>

int sctp_getpaddrs(int sockfd, sctp_assoc_t id, struct sockaddr **addrs);

Returns: the number of peer addresses stored in addrs, –1 on error
  • getpeername :仅仅返回主目的地址
  • sctp_getpaddrs:返回对端所有地址

9.6 sctp_freepaddrs()函数

#include <netinet/sctp.h>

void sctp_freepaddrs(struct sockaddr *addrs);
  • 上一个函数中addrs所指向 目的地址是动态分配的,需要用这个函数释放

9.7 sctp_getladdrs()函数

#include <netinet/sctp.h>

int sctp_getladdrs(int sockfd, sctp_assoc_t id, struct sockaddr **addrs);

Returns: the number of local addresses stored in addrs, –1 on error
  • 用于回去某个关联的本地地址

9.8 sctp_freeladdrs()函数

#include <netinet/sctp.h>

void sctp_freeladdrs(struct sockaddr *addrs);
  • 上一个函数中addrs所指向 目的地址是动态分配的,需要用这个函数释放

9.9 sctp_sendmsg()函数

ssize_t sctp_sendmsg(int sockfd, const void *msg, size_t msgsz, const struct sockaddr *to, socklen_t tolen, uint32_t ppid, uint32_t flags, uint16_t stream, uint32_t timetolive, uint32_t context);

Returns: the number of bytes written, –1 on error
  • sockfd:socket()函数返回的套接字描述符
  • msg:长度为msgsz字节的缓冲区,内容将发送到对端
  • ppid:指定将随数据块传递的净荷协议标识符(the pay-load protocol identifier )
  • flag:传递给SCTP栈,用于标识SCTP选项
  • stream:SCTP流号
  • timetolive:生命周期,0表示无限长
  • context:指定可能有的用户上下文

9.10 sctp_recvmsg()函数

ssize_t sctp_recvmsg(int sockfd, void *msg, size_t msgsz, struct sockaddr *from, socklen_t *fromlen, struct sctp_sndrcvinfo *sinfo, int *msg_flags);

Returns: the number of bytes read, –1 on error
  • 不仅能获取对端地址,也能获取通常伴随recvmsg()函数调用返回的msg_flags参数(e.g., MSG_NOTIFICATION, MSG_EOR, etc.)

9.11 sctp_opt_info()函数

int sctp_opt_info(int sockfd, sctp_assoc_t assoc_id, int opt void *arg, socklen_t *siz);

Returns: 0 for success, –1 on error
  • This inability to use the getsockopt function is because some of the SCTP socket options, for example, SCTP_STATUS, need an in-out variable to pass the association identification
  • assoc_id:给出可能存在的关联标识
  • siz:存放参数大小

9.12 sctp_peeloff()函数

int sctp_peeloff(int sockfd, sctp_assoc_t id);

Returns: a new socket descriptor on success, –1 on error
  • 从一个一到多套接字中抽取一个关联

9.13 shutdown()函数_

  • 用于一到一接口的SCTP端点(但是反应不同于TCP)
  • 发起关联终止序列时,这两个端点都得把已排队的任何数据发送掉再关
  • SHUT_RD: The same semantics as for TCP discussed in Section 6.6;
  • SHUT_WR
    Disables further send operations and initiates the SCTP shutdown procedures, which will terminate the association
  • SHUT_RDWR
    Disables all read and write operations, and initiates the SCTP shutdown procedure. Any queued data that was in transit to the local endpoint will be acknowledged and then silently discarded.

9.14 通知

  • SCTP_EVENTS 套接字选项可以预定8个事件
  • 当有通知时, recvmsg function or the sctp_recvmsg function 返回的msg_flags 将包含MSG_NOTIFICATION flag.
  • 通知采用TVL格式(tag-length-value)
  • 通知格式:
struct sctp_tlv {
  u_int16_t sn_type;
  u_int16_t sn_flags;
  u_int32_t sn_length;
};

/* notification event */
union sctp_notification {
  struct sctp_tlv sn_header;  //用于解释类型值
  struct sctp_assoc_change sn_assoc_change;
  struct sctp_paddr_change sn_paddr_change;
  struct sctp_remote_error sn_remote_error;
  struct sctp_send_failed sn_send_failed;
  struct sctp_shutdown_event sn_shutdown_event;
  struct sctp_adaption_event sn_adaption_event;
  struct sctp_pdapi_event sn_pdapi_event;
};
  • 通知的类型举例(sn_header. sn_type ),具体查阅page218
    • SCTP_ASSOC_CHANGE:关联本身发生变动
    • SCTP_PEER_ADDR_CHANGE:对端的某个地址经历了状态变动
    • SCTP_REMOTE_ERROR:远程端点可能给本地端点发送了一个操作性错误消息
    • SCTP_SEND_FAILED:无法递送到对端的消息通过本通知会送用户
    • SCTP_SHUTDOWN_EVENT:对端发送一个SHUTDOWN 到本地端点
    • SCTP_ADAPTION_INDICATION:有些实现支持适应层指示参数,该参数在INIT和INIT-ACK中交换,用于通知对端将执行什么类型的适应行为
    • SCTP_PARTIAL_DELIVERY_EVENT:部分递送应用程序接口用于经由套接字缓冲区向用户传递大消息

9.15 小结

.。。。先放一下,留给我的时间不多了。

10.SCTP客户/服务器程序例子

10.1 Introduction

这里写图片描述

10.2 SCTP One-to-Many-Style Streaming Echo Server: main Function

  • lsmod | grep sctp:查看是否安装了sctp
  • 然后安装可用的安装包,,,,
  • 突然听说sctp章节先不看,,,i++

10.3 SCTP One-to-Many-Style Streaming Echo Client: main Function

10.4 SCTP Streaming Echo Client: str_cli Function

10.5 Exploring Head-of-Line Blocking

10.6 Controlling the Number of Streams

10.7 Controlling Termination

10.8 Summary

11. 名字与地址转换

11.1 Introduction

11.2 Domain Name System (DNS)

  • 解析器使用UDP向本地名字服务器发出查询,如果本地名字服务器不知道,再使用UDP在整个因特网中查询其他名字服务器(答案太长会自动切换到TCP)
  • /etc/hosts:静态主机文件
  • FreeBSD 5.x、HP-UX 10..使用:/etc/nsswitch.conf
  • AIX使用:/etc/netsvc.conf
  • BIND 9.2.2 supplies its own version :/etc/irs.conf
  • Fortunately, these differences are normally hidden to the application programmer

11.3 gethostbyname()

#include <netdb.h>
//函数的局限是只能返回IPv4地址
struct hostent *gethostbyname (const char *hostname);

Returns: non-null pointer if OK,NULL on error with h_errno set
  • 返回指针结构(都以空字符结尾)
struct hostent {
   char  *h_name;       /* 主机的规范名字 */
   char **h_aliases;    /* 别名 */
   int    h_addrtype;   /* host address type: AF_INET */
   int    h_length;     /* length of address: 4 */
   char **h_addr_list;  /* ptr to array of ptrs with IPv4 addrs */
};
  • 下面这张图对这些参数说得很清楚
    hostent结构
    代码地址

11.4 gethostbyaddr()

#include <netdb.h>

struct hostent *gethostbyaddr (const char *addr, socklen_t len, int family);

Returns: non-null pointer if OK, NULL on error with h_errno set
  • 在in_addr.arpa域中查询PTR记录
  • addr实际是一个指向存放in_addr结构体指针

11.5 getservbyname() and getservbyport()

  • /etc/services:名字到端口的映射
#include <netdb.h>

struct servent *getservbyname (const char *servname, const char *protoname);

Returns: non-null pointer if OK, NULL on error
  • 返回的结构如下:
struct servent {
  char   *s_name;      /* official service name */
  char  **s_aliases;   /* alias list */
  int     s-port;      /* port number, network-byte order:网络字节序 */
  char   *s_proto;     /* protocol to use */
};
  • 典型调用如下:
struct servent *sptr;

sptr = getservbyname("domain", "udp"); /* DNS using UDP */
sptr = getservbyname("ftp", "tcp");    /* FTP using TCP */
sptr = getservbyname("ftp", NULL);     /* FTP using TCP */
sptr = getservbyname("ftp", "udp");    /* this call will fail */
  • 根据给定端口号和可选协议查找相应服务
#include <netdb.h>

struct servent *getservbyport (int port, const char *protoname);

Returns: non-null pointer if OK, NULL on error
  • 典型调用如下:
struct servent *sptr;

sptr = getservbyport (htons (53), "udp"); /* DNS using UDP */
sptr = getservbyport (htons (21), "tcp"); /* FTP using TCP */
sptr = getservbyport (htons (21), NULL);  /* FTP using TCP */
sptr = getservbyport (htons (21), "udp"); /* this call will fail */

11.6 getaddrinfo()

#include <netdb.h>

int getaddrinfo (const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result) ;

Returns: 0 if OK, nonzero on error (see Figure 11.7)
  • hints:期望返回的信息
  • 返回的result结构体如下:
struct addrinfo {
   int          ai_flags;           /* AI_PASSIVE, AI_CANONNAME */
   int          ai_family;          /* AF_xxx */
   int          ai_socktype;        /* SOCK_xxx */
   int          ai_protocol;        /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
   socklen_t    ai_addrlen;         /* length of ai_addr */
   char        *ai_canonname;       /* ptr to canonical name for host */
   struct sockaddr    *ai_addr;     /* ptr to socket address structure */
   struct addrinfo    *ai_next;     /* ptr to next structure in linked list */
};

。。。后面的太多先放一下

11.7 gai_strerror()

  • 对getaddrinfo返回的非0错误值返回一个错误信息串
#include <netdb.h>

const char *gai_strerror (int error);

Returns: pointer to string describing error message

11.8 freeaddrinfo()

  • 释放getaddrinfo()申请的空间
#include <netdb.h>
void freeaddrinfo (struct addrinfo *ai);
  • 注意浅复制和深复制的问题

11.9 getaddrinfo(): IPv6

  • 在汇总该函数为IPv4或IPv6的返回信息之前,需要注意下面几点:
    • 1.getaddrinfo处理两个不同的输入,套接字地址结构类型和资源记录类型
    • 在hints中指定AF_INET,就不会返回sockaddr_in6结构
    • POSIX says that specifying AF_UNSPEC will return addresses that can be used with any protocol family that can be used with the hostname and service name
    • if the AI_PASSIVE flag is specified without a hostname, then the IPv6 wildcard address (IN6ADDR_ANY_INIT or 0::0) should be returned as a sockaddr_in6 structure, along with the IPv4 …
    • hint structure’s ai_family member指定的地址簇协议以及在ai_flags成员中指定的AI_V4MAPPED and AI_ALL 决定了在DNS中查找的资源记录类型
    • The hostname can also be either an IPv6 hex string or an IPv4 dotted-decimal string. The validity of this string depends on the address family specified by the caller
      11-8_getaddrinfo函数机器行为和结果汇总.jpg

11.10 getaddrinfo(): Examples

11.11 host_serv()

#include "unp.h"
struct addrinfo *host_serv (const char *hostname, const char *service, int family, ints socktype);
Returns: pointer to addrinfo structure if OK, NULL on error

函数源码

11.12 tcp_connect()

#include "unp.h"

int tcp_connect (const char *hostname, const char *service);

Returns: connected socket descriptor if OK, no return on error

函数源码

用到该函数的一个例子

11.13 tcp_listen()

#include "unp.h"

int tcp_listen (const char *hostname, const char *service, socklen_t *addrlenp);

Returns: connected socket descriptor if OK, no return on error
  • 执行TCP服务器的通常步骤
  • 下面的程序允许用户作为程序命令行参数输入一个IP地址或主机名
    • 键入:server,则默认IPv6
    • 键入:server 0.0.0.0,则默认IPv4
    • 键入:server 0::0,则默认IPv6
  • 程序源码

11.14 udp_client ()

  • 创建未连接的UDP套接字
#include "unp.h"

int udp_client (const char *hostname, const char *service, struct sockaddr **saptr, socklen_t *lenp);

Returns: unconnected socket descriptor if OK, no return on error

函数源码
+ 协议无关获取时间客户程序

11.15 udp_connect()

  • 创建已连接的UDP套接字
#include "unp.h"

int udp_connect (const char *hostname, const char *service);

Returns: connected socket descriptor if OK, no return on error

函数源码

11.16 udp_server()

#include "unp.h"

int udp_server (const char *hostname, const char *service, socklen_t *lenptr);

Returns: unconnected socket descriptor if OK, no return on error

函数源码
+ 协议无关获取时间服务器程序

11.17 getnameinfo()


int getnameinfo (const struct sockaddr *sockaddr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) ;

Returns: 0 if OK, nonzero on error (see Figure 11.7)
  • sockaddr:指向套接字地址的结构,通常由accept(),recvfrom()返回
  • hostlen=0,表调用者不想返回主机字符串
  • servlen=0, 表调用者不想返回服务字符串
  • flags:可改变getnameinfo的操作
    • NI_DGRAM:数据报服务
    • NI_NAMEREQD:不能解析名字则返回错误

11.18 Re-entrant (可重入函数)

  • 重入问题的条件:主控制流中和某个信号处理函数同时调用gethostbyname and gethostbyaddr,而进程只存在host变量的单个副本

注意以下几点:
+ gethostbyname, gethostbyaddr, getservbyname, and get servbyport are not re-entrant 因为 all return a pointer to 同一个 static structure.
+ inet_pton and inet_ntop are always 可重入(re-entrant).
+ Historically(历史原因), inet_ntoa is not re-entrant, but some implementations(一些实现) that support threads provide a re-entrant version that uses thread-specific data(线程特定数据).
+ ……
+ 在信号处理函数中应尽量不使用任何可重入函数

11.19 gethostbyname_r and gethostbyaddr_r()

  • There are two ways to make a nonre-entrant function such as gethostbyname re-entrant
      1. Instead of filling in and returning a static structure, the caller allocates the structure and the re-entrant function fills in the caller’s structure
      1. The re-entrant function calls malloc and dynamically allocates the memory
#include <netdb.h>

struct hostent *gethostbyname_r (const char *hostname, struct hostent *result, char *buf, int buflen, int *h_errnop) ;

struct hostent *gethostbyaddr_r (const char *addr, int len, int type, struct hostent *result, char *buf, int buflen, int *h_errnop) ;

Both return: non-null pointer if OK, NULL on error

11.20 Obsolete IPv6 Address Lookup()

11.21 Other Networking Information(其他网络相关信息)

这里写图片描述

11.22 Summary

12. IPv4与IPv6的互操作性

IPv4客户与IPv6客户

  • IPv4客户到IPv6服务器
    • IP地址将被映射成IPV6地址
    • 通信都使用IPv4载送的数据报
  • IPv4监听套接字只能接收来自IPv4客户的外来连接

13. 守护进程和inetd超级服务器

守护进程:后台启动且不与任何控制终端关联

13.2 syslogd守护进程

  • 读配置文件,/etc/syslog.conf
  • A Unix domain socket is created and bound to the pathname /var/run/log (/dev/log on some systems).—>往syslogd绑定的路径名发送消息就是发送日志消息
  • A UDP socket is created and bound to port 514 (the syslog service).—->往127.0.0.1:514发送消息就是发送日志消息
  • The pathname /dev/klog is opened. Any error messages from within the kernel(内核) appear as input on this device.(看起来像是从这个设备输入的)

13.3 13.2 syslogd Function

#include <syslog.h>

void syslog(int priority, const char *message, ... );
  • priority:级别(level)和设施(facility:标识消息发送进程类型的facility)的组合
  • 增设了格式控制符 m%,将被替换成当前errno值对应的出错消息
  • 举例:syslog(LOG_INFO|LOG_LOCAL2, "rename(%s, %s): %m", file1, file2);
#include <syslog.h>
void openlog(const char *ident, int options, int facility);//首次调用syslog前调用
void closelog(void);//在进程不需要发送日志消息时调用
  • ident:通常是程序名
  • facility:指定syslog()的默认facility

13.4 daemon_init()

  • 通过调用它,能把一个普通进程编程守护进程

13.5 inetd Daemon

  • inetd进程先使用daemon_init函数的技巧把自己变成守护进程,接着处理自己的配置文件/etc/inetd.conf

13.6 daemon_inetd Function

设置由inetd启动的程序步骤:
+ 1.在/etc/services文件中服务名和端口
+ 2.添加程序路径:/etc/inetd.conf,问题是找不到这个文件
+ 3.给inet发送sIGHUP信号
+ 4.日志存放文件:/var/adm/messages(根据/etc/syslog.conf),这两个文件也找不到

13.7 Summary

14. 高级IO函数

14.2 套接字超时

14. 3 recv()和send()

#include <sys/socket.h>

ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);

ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);

Both return: number of bytes read or written if OK, –1 on error
  • flag :
    • DONTROUTE:绕过路由表
    • MSG_DONTWAIT:把单个IO指定为非阻塞
    • MSG_OOB:With send, this flag specifies that out-of-band data(带外数据) is being sent
    • MSG_PEEK:This flag lets us look at the data that is available to be read, without having the system discard the data after the recv or recvfrom returns(读,但是完后不丢弃)
    • MSG_WAITALL:可以这样用
      • #define readn(fd, ptr, n) recv(fd, ptr, n, MSG_WAITALL)

14.4 rreadv()和writev()函数

#include <sys/uio.h>

ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);

ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);

Both return: number of bytes read or written, –1 on error
  • filedes;可用于任何描述符
  • iov:指向iovec结构体的一个数组,allows up to 1,024, while HP-UX has a limit of 2,100.
struct iovec {
  void   *iov_base;   /* starting address of buffer */
  size_t  iov_len;    /* size of buffer */
};
  • writev()可以避免Nagle算法

14.5 recvmsg()和sendmsg()函数

#include <sys/socket.h>

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

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

Both return: number of bytes read or written if OK, –1 on error
  • msg:
struct msghdr {
  void         *msg_name;        /* protocol address,指向一个套接字的地址结构 */
  socklen_t     msg_namelen;     /* size of protocol address */
  struct iovec *msg_iov;         /* scatter/gather array,指定输出缓冲区数组*/
  int           msg_iovlen;      /* # elements in msg_iov */
  void         *msg_control;     /* ancillary data (cmsghdr struct),指定可选辅助数据 */
  socklen_t     msg_controllen;  /* length of ancillary data */
  int           msg_flags;       /* flags returned by recvmsg() */
};

.

14.6 辅助数据(control information)

  • 以伪代码的形式使用宏
struct msghdr     msg;
struct cmsghdr    *cmsgptr;
/* fill in msg structure */
/* call recvmsg() */
for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL;
     cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
    if (cmsgptr->cmsg_level == ... &&
        cmsgptr->cmsg_type == ... ) {
        u_char *ptr;
        ptr = CMSG_DATA(cmsgptr);
        /* process data pointed to by ptr */
    }
}

14.7 排队的数据量

获悉已排队的数据量:
- 1,使用非阻塞IO
- 2,使用MSG_PEEK标志
- 3,ioctl的第三个参数

14.8 套接字和标准IO

使用标准IO容易引起缓冲问题,可以用setvbuf迫使变为行缓冲

14.9 高级轮询技术

  • 1./dev/poll
  • 2.kqueue接口
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
int kqueue(void);
int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout) ;
void EV_SET(struct kevent *kev, uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_t data, void *udata);

尽管总的来说应该避免编写不可移植的代码,然而对于一个任务繁重的网络应用程序来说,使用各种可能得方式为他在特定的主机上进行优化也相当普遍

14.10 T/TCP:事物目的TCP

  • 有些系统可能不支持T/TCP,可用MSG_EOF检查
  • 客户端需要开启级别为IPPROTO的套接字选项TCP_NOPUSH

14.11 小结

猜你喜欢

转载自blog.csdn.net/qq_36337149/article/details/80421262
今日推荐