原先曾以Socket编程为入口开始自己的新的学习,毕竟未曾致用,时至今日已比较生疏。借着阅读《UNIX网络编程(卷一)》(简称UNPv1)的机会,正好复习一番,而且希望将新的感受记录下来。一些技术细节翻阅原书即可,点到为止,不在这篇博文详述。
作者推荐在网络编程中使用snprintf()代替sprintf()、用fget()、strncat()和strncpy()分别代替gets()、strcat()和strcpy();同时提到了更好的替代函数strlcat()和strlcpy()。具体原因是前者的调用会使缓冲区溢出,而这是黑客网络入侵的一种方式。snprintf()需要缓冲区长度作为显式参数,超过缓冲区尾端的字符都将被抛弃。其余函数情形类似。
----第1章 简介,1.5 一个简单的时间获取服务器程序
再看TCP状态转换图,发现以前忽略了一些细节:除了三路握手、关闭连接的一些典型状态,还有由于双方同时发送SYN造成的同时打开;同时发送FIN造成的同时关闭;以及TIME_WAIT状态存在的理由:最后的ACK可能丢失,使这次连接老的分组自然消逝。
----第2章 传输层:TCP、UDP和SCTP,2.6 TCP连接的建立和终止
SCTP是相对而言比较新的协议,同时以前从未接触过。面向连接按序递送的消息、所连接端点之间多个流、单个端点支持多个IP 地址是目前所了解的SCTP的特征,四路握手、拆连亦与TCP不同。
----第2章 传输层:TCP、UDP和SCTP,2.8 SCTP关联的建立和终止
虽然和TCP一样都有应用进程缓冲区(TCP可以是任意大小),但UDP的套接字发送缓冲区实际上是不存在的,通常过程是把应用进程缓冲区内容复制到某一个内核缓冲区,发送后即丢弃。
----第2章 传输层:TCP、UDP和SCTP,2.11 缓冲区大小及限制
关于开发包裹函数readn()、writen()的必要性在于,不同于文件读写,使用read()和write()时,内核提供的套节字缓冲区在某个时刻可能不足以放下所应输入或输出的n个字符,这时便要重复调用read()或write()以达到目的,在未结束前完成n个字符的输入或输出。
----第3章 套接字编程简介,3.9 readn、writen和readline函数
listen()的第二个参数backlog涉及到内核对于监听套接字维护的两个队列的机制。这两个队列存放的是ESTABLISHED和SYN_RCVD状态的排队的套接字之和,最初的设计是,这个和等于backlog。然而目前的实现并不明确,而且在不同的UNIX实现中,实际已排队连接的最大数目一般比backlog稍大一些。顺便提一下,在对这两个队列有所了解之后,就不难明白accept()如何获取一个ESTABLISHED的连接并赋予它一个套接字结构的了。
----第4章 基本TCP套接字编程,4.5 listen函数
select()中间的三个参数readset、writeset和exceptest会在返回时把代表某一位就绪的描述符置为1,未就绪置为0,因此每次调用select()时应该先把关心的位置为1。
----第6章 I/O复用:select和poll函数,6.3 select函数
对于线程私有数据相关机制和使用,UNP比APUE介绍的要全面一些。每个进程都有一个key结构,它由系统维护,存放某个索引号的线程私有数据是否在被使用的标志和析构函数,而私有数据本身由存放某一个线程信息的Thread中对应的指针访问(这是笔者在阅读了P543至544相关内容后自己的理解)。相关函数的使用方法比较典型,记录在下面。my_read和readline的关系是,readline调用my_read,先读入需要的全部数据进入自己的缓冲区,每次返回一个,循环至readline通过调用读取完毕。
使用线程私有数据的readline函数 /* threads/readline.c */ #include "unpthread.h" static pthread_key_t r1_key; static pthread_once_t r1_once = PTHREAD_ONCE_INIT; static void readline_destructor(void *ptr) { free(ptr); } static void readline_once(void) { pthread_key_creat(&r1_key, readline_destructor); } typedef struct { int r1_cnt; char *r1_bufptr; char r1_buf[MAXLINE]; } Rline; static ssize_t my_read(Rline *tsd, int fd, char *ptr) { if (tsd->r1_cnt <= 0) { again: if ( (tsd->r1_cnt = read(fd, tsd->r1_buf, MAXLINE)) <0) { if (errno == EINTR) goto again; return(-1); } else if (tsd->r1_cnt == 0) return(0); tsd->r1_bufptr = tsd->r1_buf; } tsd->r1_cnt--; *ptr = *tsd->r1_bufptr++; return(1); } ssize_t readline(int fd, void *vptr, size_t maxlen) { size_t n,rc; char c,*ptr; Rline *tsd; pthread_once(&r1_once, readline_once); if ( (tsd = pthread_getspecific(r1_key)) == NULL) { tsd = calloc(1, sizeof(Rline)); pthread_setspecific(r1_key,tsd); } ptr = vptr; for (n=1; n < maxlen; n++) { if ( (rc = my_read(tsd, fd, &c)) == 1) { *ptr++ = c; if (c=='\n') break; } else if (rc == 0) { *ptr = 0; return(n - 1); } else return(-1); } *ptr = 0; return(n); }
----第26章 线程,26.5 线程特定数据
编写SCTP程序需要注意几个地方:开启内核支持(2.4及以下不支持)、安装运行库、编译时需要-lsctp选项。
tcp_connect()利用getaddrinfo()屏蔽了很多细节操作,屏蔽了IPv4和IPv6在填充地址结构的差别,确实很有价值。
(549~560涉及到非阻塞I/O,未读)
目前手上有其他工作要做,UNP的第三部分高级套接字编程就不再精读,全书作为以后查阅时参考的手册。