Work Better Than Yesterday!

zhangge's stupid and messy life


Home| Life| Technique Concentrate On One Thing.

TCP/IP网络编程学习笔记

06 Jun 2013

这个手记记录我学习Unix下的TCP/IP网络编程的一些知识点和重要的理解,还包含了一些遇到的错误以及解决方法。这个日志会一直更新。

第一章

1.事实上,只要一个程序员设计了两个使用TCP/IP通信的程序,这个程序员就已经发明了一种新的应用协议。
2.TELNET协议可以用来访问不同于标准的远程登录服务的其他服务。
3.所有的协议设计的目的都是寻找一个适用于多种应用的基本抽象。

第二章

1.服务器要处理的安全问题:鉴别,授权,数据安全,保密,保护。
2.在设计客户应用软件时,最好让它包含一些允许用户全部指明目的机器和目的协议端口号的参数。

第三章

1.一个进程包括一段地址空间和至少一个执行的线程。线程最重要的信息是一个指令指针,他指明进程正在执行的地址。
2.当多个线程并发执行一段代码时,每个线程拥有自己的过程激活记录运行时栈(run-time stack)。
3.select调用:它允许一个程序询问操作系统哪个IO设备已准备就绪。这里IO设备包括TCP。当某个输入源就绪后,select调用立即返回,程序就可以从这个输入源中读取数据了。
4.对一个单处理器的体系结构来说,单个CPU在任一时刻只能执行一个线程。

第四章

1.Unix IO的六个基本操作API:open, close, read, write, lseek, ioctl。
2.TCP/IP协议加入unix的时候,扩展了I/O操作;首先是扩展了文件描述符集,加入了socket的描述符,其次read/write可以给socket描述符写数据。

第五章

1.套接字API提供了许多综合的功能,这些功能支持使用众多可能的协议进行网络通信。套接字调用把所有TCP/IP协议看作一个单一的协议族。这些调用允许程序员指明所要求的服务而不是指明某个特定的协议的名字。
2.socket()本质是分配了一个数据结构,里面还没有信息或者信息不全。
3.如果服务器将套接字配置为等待传入连接,则称为被动(passive)套接字;反之,客户用来发起连接的套接字称为主动(active)套接字。
4.TCP/IP协议族表示为PF_INET,地址族表示为AF_INET,两个符号常量都是2。
5.为使程序可移植和可维护,TCP/IP代码不能在声明中使用sockaddr结构。这种结构只能用于覆盖,而且代码只能引用该结构中的sa_family字段。应该使用的结构体sockaddr_in。
6.主要系统调用:客户端:socket, connect, send, recv, close. 服务端:socket,bind,listen,accept,recv,send,close。
其他:recvmsg, recvfrom, sendto, shutdown, getpeername, getsockopt, setsockopt, htons, ntohs, htonl, ntohl。

第六章

1.允许用户在调用客户软件时指明服务器地址,可以使客户软件更具一般性,并且改变服务器位置成为可能。
2.inet_addr把点分十进制转换成点分二进制。gethostbyname把域名转换成IP地址。返回是hostent结构体。
3.getservbyname由服务名字和协议查找端口,返回servent结构体。
4.getprotobyname由名字查找协议,返回protoent结构体。这个结构体包含了协议的符号常量值。
5.TCP客户端算法:确定服务器IP和port,分配socket,指定本地IP和port,连接到服务器,使用应用级协议与服务器通信,关闭连接。
6.客户可以允许TCP自动选择本地端口,connect调用的一个副效应就是所选择的本地端口能满足自动分配端口。
7.对于本地多接口多IP问题:因为选取正确的本地IP要求应用程序与IP选录软件交互,TCP客户软件往往将本地端点地址放置不填,而允许TCP/IP软件自动选取正确的本地IP地址和未使用的本地协议端口号。
8.由于TCP并不保持记录的边界,所以从TCP连接中进行接收的任何程序都必须准备一次只接收几个字节的数据。即使在发送应用程序一次发送一大块数据时,此规则也成立。
9.shutdown系统调用可以指定方向来关闭连接,例如结束发送请求后不在连接,就调用shutdown(2,1)。
10.UDP客户端算法:和TCP的区别在于没有调用connect连接。所以对于connect和shutdown的调用都没有用。

第七章

1.例子库使用

extern int errno;
strerror(errno);

则必须

#include <errno.h>

否则报Segmentation fault错误。
2.对于linux下的TIME和ECHO服务,可以安装openbsd-inet来启动。
3.daytime和time服务不同,daytime服务意在为人所用,而time服务意在为那些存储或维护时间的程序所使用。
即daytime服务返回的是时间字符串,而time服务返回的是时间戳,从1900.1.1开始,这是国际标准时间!
但是UNIX的时间是从1970.1.1开始的,所以适当修改一下,减去一个差值就可以。
4.书上的例程是daytime用TCP实现,time用UDP实现。我写的时候,弄错了一个地方,就是sizeof和strlen弄混了,所以一直时间错误,这两个的区别,我已经记录在C语言的学习记录哪里了。
5.对于htonl,htons,ntohl,ntohs这写字节顺序转换函数,由于不同机器的字节顺序不一样,有些是大端,有些是小端的,因此TCP/IP协议统一规定了网络字节顺序,其实就是大端字节顺序,因此不管本地是大端还是小端(一般这个对用户来说是透明的),在发送之前都要转换成网络字节顺序,收到以后都要从网络字节顺序转换成本地字节顺序。
注意的是,是字节顺序,不是位顺序,也就是说,一个字节里面的8位顺序是对的,只是每个字节的排序反了过来。还有,要明白,这字节顺序一般是对数而言的,特别是整型数,如果你在网络上传输一个数,那么要先把这个数转换成网络字节顺序再一个字节一个字节地传。对方接收以后如果知道是整型数,就转转成本地字节顺序,其他都按照字节来接收。因此一般的字符串,字节顺序是不变的。

第八章

1.双工传输:在任何时候,单个TCP连接都允许同时双向传送数据,且互不影响。
2.当为套接字指明本地端点时,服务器使用INADDR_ANY以取代某个特定的IP地址,这就允许套接字接收发给该机器的人一个IP地址的数据报。
3.循环连接的服务器算法:创建套接字并绑定端口(socket,bind),设置端口为被动模式(listen),接受一个连接(accept),读取客户请求并响应(read,write),关闭连接(close)。
4.循环无连接的服务器算法:创建套接字并绑定端口,重复读取客户的请求并响应。
5.并发无连接的服务器算法:主线程创建套接字并绑定端口,反复调用recvfrom接收下一个请求并创建从线程处理相应。从线程调用sendto响应请求,退出。
6.并发连接的服务器算法:主线程创建套接字并绑定端口,设置被动模式,反复调用accept直到有一个请求就创建一个从线程处理,从线程用该连接与客户端交互,关闭退出。
7.异步的单线程并发算法:创建套接字并绑定端口,将该套接字加到一个表,该表是可以进行IO的描述符,使用select在已有的套接字等待IO,如果最初的套接字准备就绪,使用accept获得下一个连接,并把该连接加入到表中,如果是其套接字准备就绪,就进行相应,不断循环。

第九章

1.recvfrom读取数据到缓存,返回实际接收的字符数,如果接收字符数大于buffer区,则丢弃。
2.io操作函数,可以用扩展的UNIX I/O,read/write,fprintf等,也可以用TCP/IP的recv和send调用。

第十章

1.循环的面向连接的服务器就是一个死循环的程序,使用TCP连接,每次accept一次请求,做完处理以后就关闭套接字。
2.TCP服务端程序有一定的脆弱性,这个暂时不是很理解,因为这与TCP的实现有关,网络方面有点忘记了。

第十一章
1.这个并发的面向连接的服务器就是用tcp连接,使用fork来创建子进程,然后用子进程处理请求,注意的是fork完全复制了描述符,在子进程要关闭主socket,在父进程要关闭从socket。
2.第二个问题是用fork复制的进程会引入一个不完全终止的进程问题。linux解决这个问题是子进程退出的时候发一个信号给父进程,然后父进程调用wait3函数,在Unix初级编程那里学过wait调用是阻塞等待子进程退出,然后获得子进程退出的exit值。
3.僵尸进程意思是,进程退出后,还有一些资源没有释放,例如pid等,要父进程调用wait才能释放,这就是僵尸进程。
4.这里有几个问题,第一,父进程接收信号时,accept被中断,然后收到一个errno,然后退出循环。这个问题可以处理accept的返回-1。第二,如果多个子进程同时退出是,父进程接收多个信号,但是信号不缓存,所以只能处理一个信号,其他丢失了。因此使用waitpid调用。书上使用wait3的原因,它包含了waitpid的功能,还可以指向子进程的资源结构rusage,并且可以设置wait3不阻塞。

第十二章
1.使用线程的时候,在连接目标文件要使用-l pthread,这点书上没有讲,还好Unix编程那书上是有的。
2.对于线程传参数时的类型是void *,书上要传sock描述符,然后,把int强转,这样有警告,而且我也觉得数据处理不好,于是使用中间变量int *tmpfd来传,每次创建线程前malloc,完了以后就free就可以拉。这点和unix书上竟然是一样的处理方式,:-)。
3.线程特征:动态创建,并发执行,抢占式调用(有一个sched_yield函数,和java里面的yield一样,是放弃cpu),私有局部变量,共享全局变量,共享文件描述符,协调和同步函数。
4.线程优点一:上下文切换开销小,效率提高。上下文切换是指操作系统将CPU从一个运行线程调度到另一个线程所需执行的指令。切换时需要保存原来线程的状态,并读取新线程的状态,因为同一个线程中的线程共享一个存储器的地址空间,进程内的线程切换就意味这操作系统不必改变虚拟存储器映射,所以比不同进程间的线程切换快。
5.第二个优点:共享存储器。这样就方便构造监控系统了。
6.线程缺点:对于共享变量的互斥问题,对于调用系统函数返回指向静态数据项的指针问题。解决办法:1.互斥量,TCPmtechod.c就是。2信号量,其实差不多,它是互斥量的扩展,允许N个线程同时进入临界区。3.条件变量,这个有点麻烦,以后要用的时候再看。
7.一些函数如exit会影响到整个进程,如果一个线程调用exit,进程就退出了。所以如果线程要退出就调用pthread_exit。
8.代码中包含一个监控系统线程,而且所有线程都是使用了独立线程来防止僵尸线程的,详细看TCPmtechod.c代码。

第十三章
1.select原理:1)获得所需要的文件描述符列表;2)将此列表传给select;3)select挂起直到任何一个文件描述符有数据到达;4)select设置一个变量中的若干位,用来通知你哪一个描述符已经有输入的数据了。详细的使用看代码:TCPmechod.c
2.select调用参数:1.需要监听的最大fd值+1,2.读入数据的fd集合,3.写出数据的fd集合,4.异常操作的fd集合,5.超时设置。如果在设定的时间内都没有数据到达,select直接返回,不阻塞。
3.如果为一个请求准备相应所需的开销中,I/O占了主导地位,则在此应用中,服务器可以使用异步I/O来提供客户间的表面上的并发性。使用select/poll调用都可以。
4.若并发服务器处理每个请求仅需很少的时间,通常他就按顺序方式执行,而执行由数据的到达驱动。只有在工作量太大,以致CPU不能顺序执行时,分时机制才取而代之。也就是说,现在写的echo服务有点不适应,反而time服务用select实现更好,除非echo一行字符就连接一次。

第十四章
1.一个多协议服务器由一个单执行线程构成,这个线程既可以在TCP也可以在UDP之上使用异步I/O来处理通信。
2.一个多协议服务器的设计允许设计者创建一个单一的过程,此过程响应某个给定的服务的请求,响应过程的调用,而不必关心这些请求是来自UDP还是TCP的。

第十五章
1.错误select : Interrupted system call是由于子进程退出发了一个信号给父进程,然后select中断掉,我没有处理errno==EINTR,所以报错,这个在第十一章的时候提到过,但是没有记住,是在Unix那本书上讲的。
2.书上的程序有问题,当运行echo子进程是,再运行daytime进程,能获得服务,但是再次运行daytime就不能获得服务了,因为父进程阻塞等待echo子进程结束,说明wait3和waitpid都没有立即返回,看来还是要用线程比较好一点嘛。
3.书上好多中方法实现多协议多服务的设计方法,我总结一下自己认为比较好的:打开多个协议服务的socket,然后使用select调用,当有一个socket有数据的时候,就启动一个线程处理请求。子进程可以执行execve来执行服务代码,inet就是这样子干的。如果子线程执行execve,不知道会不会覆盖进程。
4.在多服务服务器中,系统调用execve使得有可能将处理每个服务的代码与管理客户端发过来的初始请求的代码分隔开。
5.按照书上为inetd添加一个服务不成功,加到openbsd-inetd也不行,算了,不管拉。

第十六章
1.这一章讲了服务器并发性的统一高效管理,讲得很抽象,很多都是经验之谈,我读得不仔细,程序员当然看代码最直接了。以后有需要用到的时候再仔细去看吧。
2.并发的时候使用预分配,就像java的线程池一样:当使用预分配时,服务器在启动就创建若干个并发的从线程/进程。预分配避免了在每次请求到达时创建进程的开销,因而降低了服务器的时延,同时允许在处理一个请求时,与另一个请求相关联的I/O活动也在重叠进行。
3.更好的是,想tomcat服务器一样,开始不要预分配,在第一次使用以后不消掉就行:当使用延迟的从线程/进程分配时,服务器将开始循环地处理每个请求。仅当处理要花大块时间时,服务器才创建一个并发的从线程/进程来处理该请求。这种时延允许主服务器在创建一个进程或切换环境前,先检查由於差错并处理一些短请求。
4.预分配和时延分配基于同一原理:通过把服务器的并发等级从当前活跃的请求数目中分离出来,设计人员可获得灵活性并提高服务器的效率。

第十七章
1.这章讲的是客户端的并发,就是多线程或者多子进程,在客户端使用并发的最主要优点是在于异步性,允许客户同时处理多个请求,且不严格规定其执行顺序。
2.实现有两种方法,一,客户分两个或多个执行线程,每个处理一个功能。二,客户只有一个线程,使用select异步地处理多个输入和输出事件。
3.书上使用的是单线程,基本过程是这样:打开多个socket,然后加入到描述符集合,最后使用select调用,当有一个请求时就处理一个socket。我感觉用处不太好,还是多线程好,而且书上的例子没有很好的体现,如果有界面什么的就非常好,所以我没有去抄书上的例子了。

最后送上一张TCP/IP的大图
alt TCPIP


Sunday don't come easily! Subscribe to RSS Feed