Windows网络与通信程序设计的目录
第1章 计算机网络基础
11网络的概念和网络的组成
12计算机网络参考模型
121协议层次
122TCP/IP参考模型
123应用层(Application Layer)
124传输层(Transport Layer)
125网络层(Network Layer)
126链路层(Link Layer)
127物理层(Physical Layer)
13网络程序寻址方式
131 MAC地址
132 IP地址
133子网寻址
134端口号
135网络地址转换(NAT)
14网络应用程序设计基础
141网络程序体系结构
142网络程序通信实体
143网络程序开发环境
第2章 Winsock编程接口
21 Winsock库
211 Winsock库的装入和释放
212封装CInitSock类
22 Winsock的寻址方式和字节顺序
221 Winsock寻址
222字节顺序
223获取地址信息
23 Winsock编程详解
231 Winsock编程流程
232典型过程图
233 TCP服务器和客户端程序举例
234 UDP编程
24网络对时程序实例
241时间协议(Time Protocol)
242 TCP/IP实现代码
第3章 Windows套接字I/O模型
31套接字模式
311阻塞模式
312非阻塞模式
32选择(select)模型
321 select函数
322应用举例
33 WSAAsyncSelect模型
331消息通知和WSAAsyncSelect函数
332应用举例
34 WSAEventSelect模型
341 WSAEventSelect函数
342应用举例
343基于WSAEventSelect模型的服务器设计
35重叠(Overlapped)I/O模型
351重叠I/O函数
352事件通知方式
353基于重叠I/O模型的服务器设计
第4章 IOCP与可伸缩网络程序
41完成端口I/O模型
411什么是完成端口(completion port)对象
412使用IOCP的方法
413示例程序
414恰当地关闭IOCP
42 Microsoft扩展函数
421 GetAcceptExSockaddrs函数
422 TransmitFile函数
423 TransmitPackets函数
424 ConnectEx函数
425 DisconnectEx函数
43可伸缩服务器设计注意事项
431内存资源管理
432接受连接的方法
433恶意客户连接问题
434包重新排序问题
44可伸缩服务器系统设计实例
441 CIOCPServer类的总体结构
442数据结构定义和内存池方案
443自定义帮助函数
444开启服务和停止服务
445 I/O处理线程
446用户接口和测试程序
第5章 互联网广播和IP多播
51套接字选项和I/O控制命令
511套接字选项
512 I/O控制命令
52广播通信
53 IP多播(Multicasting)
531多播地址
532组管理协议(IGMP)
533使用IP多播
54基于IP多播的组讨论会实例
541定义组讨论会协议
542线程通信机制
543封装CGroupTalk类
544程序界面
第6章 原始套接字
第7章 Winsock服务提供者接口(SPI)
第8章 Windows网络驱动接口标准(NDIS)和协议驱动的开发
第9章 网络扫描与检测技术
第10章 点对点(P2P)网络通信技术
第11章 核心层网络封包截获技术
第12章 Windows网络防火墙开发技术
第13章 IP帮助函数
第14章 Email协议及其编程
……
作者:美团技术团队
链接:https://zhuanlanzhihucom/p/23488863
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。
那么NIO的本质是什么样的呢?它是怎样与事件模型结合来解放线程、提高系统吞吐的呢?
本文会从传统的阻塞I/O和线程池模型面临的问题讲起,然后对比几种常见I/O模型,一步步分析NIO怎么利用事件模型处理I/O,解决线程池瓶颈处理海量连接,包括利用面向事件的方式编写服务端/客户端程序。最后延展到一些高级主题,如Reactor与Proactor模型的对比、Selector的唤醒、Buffer的选择等。
注:本文的代码都是伪代码,主要是为了示意,不可用于生产环境。
传统BIO模型分析
让我们先回忆一下传统的服务器端同步阻塞I/O处理(也就是BIO,Blocking I/O)的经典编程模型:
{
ExecutorService executor = ExcutorsnewFixedThreadPollExecutor(100);//线程池
ServerSocket serverSocket = new ServerSocket();
serverSocketbind(8088);
while(!ThreadcurrentThreadisInturrupted()){//主线程死循环等待新连接到来
Socket socket = serverSocketaccept();
executorsubmit(new ConnectIOnHandler(socket));//为新的连接创建新的线程
}
class ConnectIOnHandler extends Thread{
private Socket socket;
public ConnectIOnHandler(Socket socket){
thissocket = socket;
}
public void run(){
while(!ThreadcurrentThreadisInturrupted()&&!socketisClosed()){死循环处理读写事件
String someThing = socketread()//读取数据
if(someThing!=null){
//处理数据
socketwrite()//写数据
}
}
}
}
这是一个经典的每连接每线程的模型,之所以使用多线程,主要原因在于socketaccept()、socketread()、socketwrite()三个主要函数都是同步阻塞的,当一个连接在处理I/O的时候,系统是阻塞的,如果是单线程的话必然就挂死在那里;但CPU是被释放出来的,开启多线程,就可以让CPU去处理更多的事情。其实这也是所有使用多线程的本质:
利用多核。
当I/O阻塞系统,但CPU空闲的时候,可以利用多线程使用CPU资源。
现在的多线程一般都使用线程池,可以让线程的创建和回收成本相对较低。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的I/O并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。
不过,这个模型最本质的问题在于,严重依赖于线程。但线程是很"贵"的资源,主要表现在:
线程的创建和销毁成本很高,在Linux这样的操作系统中,线程本质上就是一个进程。创建和销毁都是重量级的系统函数。
线程本身占用较大内存,像Java的线程栈,一般至少分配512K~1M的空间,如果系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半。
线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现往往是系统load偏高、CPU sy使用率特别高(超过20%以上),导致系统几乎陷入不可用的状态。
容易造成锯齿状的系统负载。因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。
所以,当面对十万甚至百万级连接的时候,传统的BIO模型是无能为力的。随着移动端应用的兴起和各种网络游戏的盛行,百万级长连接日趋普遍,此时,必然需要一种更高效的I/O处理模型。
NIO是怎么工作的
很多刚接触NIO的人,第一眼看到的就是Java相对晦涩的API,比如:Channel,Selector,Socket什么的;然后就是一坨上百行的代码来演示NIO的服务端Demo……瞬间头大有没有?
我们不管这些,抛开现象看本质,先分析下NIO是怎么工作的。
常见I/O模型对比
所有的系统I/O都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。
需要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在"干活",而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。
下图是几种常见I/O模型的对比:
以socketread()为例子:
传统的BIO里面socketread(),如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据。
对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。
最新的AIO(Async I/O)里面会更进一步:不但等待就绪是非阻塞的,就连数据从网卡到内存的过程也是异步的。
换句话说,BIO里用户最关心“我要读”,NIO里用户最关心"我可以读了",在AIO模型里用户更需要关注的是“读完了”。
NIO一个重要的特点是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。
如何结合事件模型使用NIO同步非阻塞特性
回忆BIO模型,之所以需要多线程,是因为在进行I/O操作的时候,一是没有办法知道到底能不能写、能不能读,只能"傻等",即使通过各种估算,算出来操作系统没有能力进行读写,也没法在socketread()和socketwrite()函数中返回,这两个函数无法进行有效的中断。所以除了多开线程另起炉灶,没有好的办法利用CPU。
NIO的读写函数可以立刻返回,这就给了我们不开线程利用CPU的最好机会:如果一个连接不能读写(socketread()返回0或者socketwrite()返回0),我们可以把这件事记下来,记录的方式通常是在Selector上注册标记位,然后切换到其它就绪的连接(channel)继续进行读写。
下面具体看下如何利用事件模型单线程处理所有I/O请求:
NIO的主要事件有几个:读就绪、写就绪、有新连接到来。
我们首先需要注册当这几个事件到来的时候所对应的处理器。然后在合适的时机告诉事件选择器:我对这个事件感兴趣。对于写操作,就是写不出去的时候对写事件感兴趣;对于读操作,就是完成连接和系统没有办法承载新读入的数据的时;对于accept,一般是服务器刚启动的时候;而对于connect,一般是connect失败需要重连或者直接异步调用connect的时候。
其次,用一个死循环选择就绪的事件,会执行系统调用(Linux 26之前是select、poll,26之后是epoll,Windows是IOCP),还会阻塞的等待新事件的到来。新事件到来的时候,会在selector上注册标记位,标示可读、可写或者有连接到来。
注意,select是阻塞的,无论是通过操作系统的通知(epoll)还是不停的轮询(select,poll),这个函数是阻塞的。所以你可以放心大胆地在一个while(true)里面调用这个函数而不用担心CPU空转。
所以我们的程序大概的模样是:
interface ChannelHandler{void channelReadable(Channel channel);
void channelWritable(Channel channel);
}
class Channel{
Socket socket;
Event event;//读,写或者连接
}
//IO线程主循环:
class IoThread extends Thread{
public void run(){
Channel channel;
while(channel=Selectorselect()){//选择就绪的事件和对应的连接
if(channelevent==accept){
registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器
}
if(channelevent==write){
getChannelHandler(channel)channelWritable(channel);//如果可以写,则执行写事件
}
if(channelevent==read){
getChannelHandler(channel)channelReadable(channel);//如果可以读,则执行读事件
}
}
}
Map<Channel,ChannelHandler> handlerMap;//所有channel的对应事件处理器
}
这个程序很简短,也是最简单的Reactor模式:注册所有感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。
优化线程模型
由上面的示例我们大概可以总结出NIO是怎么解决掉线程的瓶颈并处理海量连接的:
NIO由原来的阻塞读写(占用线程)变成了单线程轮询事件,找到可以进行读写的网络描述符进行读写。除了事件的轮询是阻塞的(没有可干的事情必须要阻塞),剩余的I/O操作都是纯CPU操作,没有必要开启多线程。
并且由于线程的节约,连接数大的时候因为线程切换带来的问题也随之解决,进而为处理海量连接提供了可能。
单线程处理I/O的效率确实非常高,没有线程切换,只是拼命的读、写、选择事件。但现在的服务器,一般都是多核处理器,如果能够利用多核心进行I/O,无疑对效率会有更大的提高。
仔细分析一下我们需要的线程,其实主要包括以下几种:
事件分发器,单线程选择就绪的事件。
I/O处理器,包括connect、read、write等,这种纯CPU操作,一般开启CPU核心个线程就可以。
业务线程,在处理完I/O后,业务一般还会有自己的业务逻辑,有的还会有其他的阻塞I/O,如DB操作,RPC等。只要有阻塞,就需要单独的线程。
Java的Selector对于Linux系统来说,有一个致命限制:同一个channel的select不能被并发的调用。因此,如果有多个I/O线程,必须保证:一个socket只能属于一个IoThread,而一个IoThread可以管理多个socket。
另外连接的处理和读写的处理通常可以选择分开,这样对于海量连接的注册和读写就可以分发。虽然read()和write()是比较高效无阻塞的函数,但毕竟会占用CPU,如果面对更高的并发则无能为力。
NIO在客户端的魔力
通过上面的分析,可以看出NIO在服务端对于解放线程,优化I/O和处理海量连接方面,确实有自己的用武之地。那么在客户端上,NIO又有什么使用场景呢
常见的客户端BIO+连接池模型,可以建立n个连接,然后当某一个连接被I/O占用的时候,可以使用其他连接来提高性能。
但多线程的模型面临和服务端相同的问题:如果指望增加连接数来提高性能,则连接数又受制于线程数、线程很贵、无法建立很多线程,则性能遇到瓶颈。
每连接顺序请求的Redis
对于Redis来说,由于服务端是全局串行的,能够保证同一连接的所有请求与返回顺序一致。这样可以使用单线程+队列,把请求数据缓冲。然后pipeline发送,返回future,然后channel可读时,直接在队列中把future取回来,done()就可以了。
伪代码如下:
class RedisClient Implements ChannelHandler{private BlockingQueue CmdQueue;
private EventLoop eventLoop;
private Channel channel;
class Cmd{
String cmd;
Future result;
}
public Future get(String key){
Cmd cmd= new Cmd(key);
queueoffer(cmd);
eventLoopsubmit(new Runnable(){
List list = new ArrayList();
queuedrainTo(list);
if(channelisWritable()){
channelwriteAndFlush(list);
}
});
}
public void ChannelReadFinish(Channel channel,Buffer Buffer){
List result = handleBuffer();//处理数据
//从cmdQueue取出future,并设值,futuredone();
}
public void ChannelWritable(Channel channel){
channelflush();
}
}
这样做,能够充分的利用pipeline来提高I/O能力,同时获取异步处理能力。
多连接短连接的HttpClient
类似于竞对抓取的项目,往往需要建立无数的HTTP短连接,然后抓取,然后销毁,当需要单机抓取上千网站线程数又受制的时候,怎么保证性能呢
何不尝试NIO,单线程进行连接、写、读操作?如果连接、读、写操作系统没有能力处理,简单的注册一个事件,等待下次循环就好了。
如何存储不同的请求/响应呢?由于http是无状态没有版本的协议,又没有办法使用队列,好像办法不多。比较笨的办法是对于不同的socket,直接存储socket的引用作为map的key。
常见的RPC框架,如Thrift,Dubbo
这种框架内部一般维护了请求的协议和请求号,可以维护一个以请求号为key,结果的result为future的map,结合NIO+长连接,获取非常不错的性能。
NIO高级主题
Proactor与Reactor
一般情况下,I/O 复用机制需要事件分发器(event dispatcher)。 事件分发器的作用,即将那些读写事件源分发给各读写事件的处理者,就像送快递的在楼下喊: 谁谁谁的快递到了, 快来拿吧!开发人员在开始的时候需要在分发器那里注册感兴趣的事件,并提供相应的处理者(event handler),或者是回调函数;事件分发器在适当的时候,会将请求的事件分发给这些handler或者回调函数。
涉及到事件分发器的两种模式称为:Reactor和Proactor。 Reactor模式是基于同步I/O的,而Proactor模式是和异步I/O相关的。在Reactor模式中,事件分发器等待某个事件或者可应用或个操作的状态发生(比如文件描述符可读写,或者是socket可读写),事件分发器就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。
而在Proactor模式中,事件处理者(或者代由事件分发器发起)直接发起一个异步读写操作(相当于请求),而实际的工作是由操作系统来完成的。发起时,需要提供的参数包括用于存放读到数据的缓存区、读的数据大小或用于存放外发数据的缓存区,以及这个请求完后的回调函数等信息。事件分发器得知了这个请求,它默默等待这个请求的完成,然后转发完成事件给相应的事件处理者或者回调。举例来说,在Windows上事件处理者投递了一个异步IO操作(称为overlapped技术),事件分发器等IO Complete事件完成。这种异步模式的典型实现是基于操作系统底层异步API的,所以我们可称之为“系统级别”的或者“真正意义上”的异步,因为具体的读写是由操作系统代劳的。
举个例子,将有助于理解Reactor与Proactor二者的差异,以读操作为例(写操作类似)。
在Reactor中实现读
注册读就绪事件和相应的事件处理器。
事件分发器等待事件。
事件到来,激活分发器,分发器调用事件对应的处理器。
事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。
在Proactor中实现读:
处理器发起异步读操作(注意:操作系统必须支持异步IO)。在这种情况下,处理器无视IO就绪事件,它关注的是完成事件。
事件分发器等待操作完成事件。
在分发器等待过程中,操作系统利用并行的内核线程执行实际的读操作,并将结果数据存入用户自定义缓冲区,最后通知事件分发器读操作完成。
事件分发器呼唤处理器。
事件处理器处理用户自定义缓冲区中的数据,然后启动一个新的异步操作,并将控制权返回事件分发器。
可以看出,两个模式的相同点,都是对某个I/O事件的事件通知(即告诉某个模块,这个I/O操作可以进行或已经完成)。在结构上,两者也有相同点:事件分发器负责提交IO操作(异步)、查询设备是否可操作(同步),然后当条件满足时,就回调handler;不同点在于,异步情况下(Proactor),当回调handler时,表示I/O操作已经完成;同步情况下(Reactor),回调handler时,表示I/O设备可以进行某个操作(can read 或 can write)。
下面,我们将尝试应对为Proactor和Reactor模式建立可移植框架的挑战。在改进方案中,我们将Reactor原来位于事件处理器内的Read/Write操作移至分发器(不妨将这个思路称为“模拟异步”),以此寻求将Reactor多路同步I/O转化为模拟异步I/O。以读操作为例子,改进过程如下:
注册读就绪事件和相应的事件处理器。并为分发器提供数据缓冲区地址,需要读取数据量等信息。
分发器等待事件(如在select()上等待)。
事件到来,激活分发器。分发器执行一个非阻塞读操作(它有完成这个操作所需的全部信息),最后调用对应处理器。
事件处理器处理用户自定义缓冲区的数据,注册新的事件(当然同样要给出数据缓冲区地址,需要读取的数据量等信息),最后将控制权返还分发器。
如我们所见,通过对多路I/O模式功能结构的改造,可将Reactor转化为Proactor模式。改造前后,模型实际完成的工作量没有增加,只不过参与者间对工作职责稍加调换。没有工作量的改变,自然不会造成性能的削弱。对如下各步骤的比较,可以证明工作量的恒定:
标准/典型的Reactor:
步骤1:等待事件到来(Reactor负责)。
步骤2:将读就绪事件分发给用户定义的处理器(Reactor负责)。
步骤3:读数据(用户处理器负责)。
步骤4:处理数据(用户处理器负责)。
改进实现的模拟Proactor:
步骤1:等待事件到来(Proactor负责)。
步骤2:得到读就绪事件,执行读数据(现在由Proactor负责)。
步骤3:将读完成事件分发给用户处理器(Proactor负责)。
步骤4:处理数据(用户处理器负责)。
对于不提供异步I/O API的操作系统来说,这种办法可以隐藏Socket API的交互细节,从而对外暴露一个完整的异步接口。借此,我们就可以进一步构建完全可移植的,平台无关的,有通用对外接口的解决方案。
代码示例如下:
interface ChannelHandler{void channelReadComplate(Channel channel,byte[] data);
void channelWritable(Channel channel);
}
class Channel{
Socket socket;
Event event;//读,写或者连接
}
//IO线程主循环:
class IoThread extends Thread{
public void run(){
Channel channel;
while(channel=Selectorselect()){//选择就绪的事件和对应的连接
if(channelevent==accept){
registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器
Selectorinterested(read);
}
if(channelevent==write){
getChannelHandler(channel)channelWritable(channel);//如果可以写,则执行写事件
}
if(channelevent==read){
byte[] data = channelread();
if(channelread()==0)//没有读到数据,表示本次数据读完了
{
getChannelHandler(channel)channelReadComplate(channel,data;//处理读完成事件
}
if(过载保护){
Selectorinterested(read);
}
}
}
}
Map<Channel,ChannelHandler> handlerMap;//所有channel的对应事件处理器
}
Selectorwakeup()
主要作用
解除阻塞在Selectorselect()/select(long)上的线程,立即返回。
两次成功的select之间多次调用wakeup等价于一次调用。
如果当前没有阻塞在select上,则本次wakeup调用将作用于下一次select——“记忆”作用。
为什么要唤醒?
注册了新的channel或者事件。
channel关闭,取消注册。
优先级更高的事件触发(如定时器事件),希望及时处理。
原理
Linux上利用pipe调用创建一个管道,Windows上则是一个loopback的tcp连接。这是因为win32的管道无法加入select的fd set,将管道或者TCP连接加入select fd set。
wakeup往管道或者连接写入一个字节,阻塞的select因为有I/O事件就绪,立即返回。可见,wakeup的调用开销不可忽视。
Buffer的选择
通常情况下,操作系统的一次写操作分为两步:
将数据从用户空间拷贝到系统空间。
从系统空间往网卡写。同理,读操作也分为两步:
① 将数据从网卡拷贝到系统空间;
② 将数据从系统空间拷贝到用户空间。
对于NIO来说,缓存的使用可以使用DirectByteBuffer和HeapByteBuffer。如果使用了DirectByteBuffer,一般来说可以减少一次系统空间到用户空间的拷贝。但Buffer创建和销毁的成本更高,更不宜维护,通常会用内存池来提高性能。
如果数据量比较小的中小应用情况下,可以考虑使用heapBuffer;反之可以用directBuffer。
NIO存在的问题
使用NIO != 高性能,当连接数<1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势。
NIO并没有完全屏蔽平台差异,它仍然是基于各个操作系统的I/O系统实现的,差异仍然存在。使用NIO做网络编程构建事件驱动模型并不容易,陷阱重重。
推荐大家使用成熟的NIO框架,如Netty,MINA等。解决了很多NIO的陷阱,并屏蔽了操作系统的差异,有较好的性能和编程模型。
老司机带你分析SpringMVC框架设计原理与实现
链接:https://panbaiducom/s/1cksL0_VmSMdkIXWFSOx19g
密码:57w4Netty粘包分包现象及解决方案实战,防socket攻击
链接:https://panbaiducom/s/1kTF2oqHOqvrPJrKa7TpXOQ
密码:dk9n大型企业级高并发下数据库水平切分之读写分离技巧详解
链接:https://panbaiducom/s/1OrXSGCCboqgVX2vgfC7Z7Q
密码:ri8q分布式事务出现场景及解决方案详细剖析
链接:https://panbaiducom/s/1BBf6cePibN0xawFEY7A6ZA
密码:380p以上都是小编收集了大神的灵药,喜欢的拿走吧!喜欢小编就轻轻关注一下吧!
Windows网络与通信程序设计(第2版) 王艳平 这本书写的非常好,我有本王艳平写的 windows程序设计,写得很好,我自己不做网络开发,就没有买网络的那本书!不过推荐你看看,真的很不错!
本书将编程方法、网络协议和应用实例有机结合起来,详细阐明Windows网络编程的各方面内容。本书首先介绍Windows平台上进行网络编程的基础知识,包括网络硬件、术语、协议、Winsock编程接口和各种I/O方法等;然后通过具体实例详细讲述当前流行的高性能可伸缩服务器设计、IP多播和Internet广播、P2P程序设计、原始套接字、SPI、协议驱动的开发和原始以太数据的发送、ARP欺骗技术、LAN和WAN上的扫描和侦测技术、个人防火墙与网络封包截获技术等;最后讲述IP帮助函数和E-mail的开发方法。 本书结构紧凑,内容由浅入
第1章 计算机网络基础 1
11 网络的概念和网络的组成 1
12 计算机网络参考模型 2
121 协议层次 2
122 TCP/IP参考模型 2
123 应用层(Application Layer) 3
124 传输层(Transport Layer) 3
125 网络层(Network Layer) 3
126 链路层(Link Layer) 4
127 物理层(Physical Layer) 4
13 网络程序寻址方式 4
131 MAC地址 4
132 IP地址 5
133 子网寻址 6
134 端口号 8
135 网络地址转换(NAT) 8
14 网络应用程序设计基础 10
141 网络程序体系结构 10
142 网络程序通信实体 11
143 网络程序开发环境 12
第2章 Winsock编程接口 13
21 Winsock库 13
211 Winsock库的装入和释放 13
212 封装CInitSock类 14
22 Winsock的寻址方式和字节顺序 14
221 Winsock寻址 14
222 字节顺序 16
223 获取地址信息 17
23 Winsock编程详解 20
231 Winsock编程流程 20
232 典型过程图 23
233 TCP服务器和客户端程序举例 24
234 UDP编程 26
24 网络对时程序实例 28
241 时间协议(Time Protocol) 28
242 TCP/IP实现代码 29
第3章 Windows套接字I/O模型 31
31 套接字模式 31
311 阻塞模式 31
312 非阻塞模式 31
32 选择(select)模型 32
321 select函数 32
322 应用举例 33
33 WSAAsyncSelect模型 36
331 消息通知和WSAAsyncSelect函数 36
332 应用举例 37
34 WSAEventSelect模型 40
341 WSAEventSelect函数 40
342 应用举例 42
343 基于WSAEventSelect模型的服务器设计 44
35 重叠(Overlapped)I/O模型 53
351 重叠I/O函数 53
352 事件通知方式 56
353 基于重叠I/O模型的服务器设计 56
第4章 IOCP与可伸缩网络程序 67
41 完成端口I/O模型 67
411 什么是完成端口(completion port)对象 67
412 使用IOCP的方法 67
413 示例程序 69
414 恰当地关闭IOCP 72
42 Microsoft扩展函数 72
421 GetAcceptExSockaddrs函数 73
422 TransmitFile函数 73
423 TransmitPackets函数 74
424 ConnectEx函数 75
425 DisconnectEx函数 76
43 可伸缩服务器设计注意事项 76
431 内存资源管理 76
432 接受连接的方法 77
433 恶意客户连接问题 77
434 包重新排序问题 78
44 可伸缩服务器系统设计实例 78
441 CIOCPServer类的总体结构 78
442 数据结构定义和内存池方案 82
443 自定义帮助函数 85
444 开启服务和停止服务 88
445 I/O处理线程 93
446 用户接口和测试程序 99
第5章 互联网广播和IP多播 100
51 套接字选项和I/O控制命令 100
511 套接字选项 100
512 I/O控制命令 102
52 广播通信 103
53 IP多播(Multicasting) 105
531 多播地址 105
532 组管理协议(IGMP) 105
533 使用IP多播 106
54 基于IP多播的组讨论会实例 110
541 定义组讨论会协议 110
542 线程通信机制 111
543 封装CGroupTalk类 111
544 程序界面 117
第6章 原始套接字 121
61 使用原始套接字 121
62 ICMP编程 121
621 ICMP与校验和的计算 121
622 Ping程序实例 124
623 路由跟踪 126
63 使用IP头包含选项 129
631 IP数据报格式 129
632 UDP数据报格式 131
633 原始UDP封包发送实例 133
64 网络嗅探器开发实例 134
641 嗅探器设计原理 135
642 网络嗅探器的具体实现 136
643 侦听局域网内的密码 138
65 TCP通信开发实例 140
651 创建一个原始套接字,并设置IP头选项 140
652 构造IP头和TCP头 140
653 发送原始套接字数据报 142
654 接收数据 146
第7章 Winsock服务提供者接口(SPI) 147
71 SPI概述 147
72 Winsock协议目录 148
721 协议特性 149
722 使用Winsock API函数枚举协议 150
723 使用Winsock SPI函数枚举协议 151
73 分层服务提供者(LSP) 153
731 运行原理 153
732 安装LSP 154
733 移除LSP 158
734 编写LSP 159
735 LSP实例 161
74 基于SPI的数据报过滤实例 165
75 基于Winsock的网络聊天室开发 171
751 服务端 171
752 客户端 171
753 聊天室程序的设计说明 172
754 核心代码分析 172
第8章 Windows网络驱动接口标准(NDIS)和协议驱动的开发 176
81 核心层网络驱动 176
811 Windows 2000及其后产品的网络体系结构 176
812 NDIS网络驱动程序 177
813 网络驱动开发环境 178
82 WDM驱动开发基础 181
821 UNICODE字符串 181
822 设备对象 181
823 驱动程序的基本结构 183
824 I/O请求包(I/O request packet,IRP)和I/O堆栈 183
825 完整驱动程序示例 186
826 扩展派遣接口 188
827 应用举例(进程诊测实例) 191
83 开发NDIS网络驱动预备知识 198
831 中断请求级别(Interrupt Request Level,IRQL) 198
832 旋转锁(Spin Lock) 198
833 双链表 199
834 封包结构 199
84 NDIS协议驱动 200
841 注册协议驱动 200
842 打开下层协议驱动的适配器 201
843 协议驱动的封包管理 202
844 在协议驱动中接收数据 203
845 从协议驱动发送封包 204
85 NDIS协议驱动开发实例 204
851 总体设计 204
852 NDIS协议驱动的初始化、注册和卸载 206
853 下层NIC的绑定和解除绑定 209
854 发送数据 217
855 接收数据 219
856 用户IOCTL处理 225
第9章 网络扫描与检测技术 233
91 网络扫描基础知识 233
911 以太网数据帧 233
912 ARP 234
913 ARP格式 236
914 SendARP函数 237
92 原始以太封包的发送 238
921 安装协议驱动 238
922 协议驱动用户接口 238
923 发送以太封包的测试程序 244
93 局域网计算机扫描 245
931 管理原始ARP封包 246
932 ARP扫描示例 249
94 互联网计算机扫描 253
941 端口扫描原理 253
942 半开端口扫描实现 254
95 ARP欺骗原理与实现 259
951 IP欺骗的用途和实现原理 259
952 IP地址冲突 260
953 ARP欺骗示例 261
第10章 点对点(P2P)网络通信技术 264
101 P2P穿越概述 264
102 一般概念 265
1021 NAT术语 265
1022 中转 265
1023 反向连接 266
103 UDP打洞 267
1031 中心服务器 267
1032 建立点对点会话 267
1033 公共NAT后面的节点 267
1034 不同NAT后面的节点 268
1035 多级NAT后面的节点 269
1036 UDP空闲超时 270
104 TCP打洞 271
1041 套接字和TCP端口重用 271
1042 打开点对点的TCP流 271
1043 应用程序看到的行为 272
1044 同步TCP打开 273
105 Internet点对点通信实例 273
1051 总体设计 273
1052 定义P2P通信协议 274
1053 客户方程序 275
1054 服务器方程序 287
1055 测试程序 291
第11章 核心层网络封包截获技术 294
111 Windows网络数据和封包过滤概述 294
1111 Windows网络系统体系结构图 294
1112 用户模式下的网络数据过滤 295
1113 内核模式下的网络数据过滤 296
112 中间层网络驱动PassThru 296
1121 PassThru NDIS中间层驱动简介 296
1122 编译和安装PassThru驱动 297
113 扩展PassThru NDIS IM驱动——添加IOCTL接口 297
1131 扩展之后的PassThru驱动(PassThruEx)概况 297
1132 添加基本的DeviceIoControl接口 298
1133 添加绑定枚举功能 302
1134 添加ADAPT结构的引用计数 307
1135 适配器句柄的打开/关闭函数 308
1136 句柄事件通知 315
1137 查询和设置适配器的OID信息 315
114 扩展PassThru NDIS IM驱动——添加过滤规则 323
1141 需要考虑的事项 323
1142 过滤相关的数据结构 324
1143 过滤列表 326
1144 网络活动状态 327
1145 IOCTL控制代码 328
1146 过滤数据 331
115 核心层过滤实例 339
第12章 Windows网络防火墙开发技术 342
121 防火墙技术概述 342
122 金羽(Phoenix)个人防火墙浅析 343
1221 金羽(Phoenix)个人防火墙简介 343
1222 金羽(Phoenix)个人防火墙总体设计 344
1223 金羽(Phoenix)个人防火墙总体结构 345
123 开发前的准备 345
1231 常量的定义 346
1232 访问规则 348
1233 会话结构 348
1234 文件结构 349
1235 UNICODE支持 355
124 应用层DLL模块 356
1241 DLL工程框架 356
1242 共享数据和IO控制 362
1243 访问控制列表ACL(Access List) 364
1244 查找应用程序访问权限的过程 367
1245 类的接口——检查函数 370
125 核心层SYS模块 373
126 主模块工程 375
1261 I/O控制类 375
1262 主应用程序类 377
1263 主对话框中的属性页 380
1264 主窗口类 381
127 防火墙页面 383
1271 网络访问监视页面 383
1272 应用层过滤规则页面 387
1273 核心层过滤规则页面 397
1274 系统设置页面 403
第13章 IP帮助函数 406
131 IP配置信息 406
1311 获取网络配置信息 406
1312 管理网络接口 408
1313 管理IP地址 412
132 获取网络状态信息 415
1321 获取TCP连接表 415
1322 获取UDP监听表 418
1323 获取IP统计数据 420
133 路由管理 427
1331 获取路由表 427
1332 管理特定路由 431
1333 修改默认网关的例子 432
134 ARP表管理 433
1341 获取ARP表 433
1342 添加ARP入口 434
1343 删除ARP入口 434
1344 打印ARP表的例子 434
135 进程网络活动监视实例 438
1351 获取通信的进程终端 438
1352 Netstate源程序代码 439
第14章 Email协议及其编程 444
141 概述 444
142 电子邮件介绍 445
1421 电子邮件Internet的地址 445
1422 Internet邮件系统 445
1423 电子邮件信头的结构及分析 446
143 SMTP原理 448
1431 SMTP原理分析 448
1432 SMTP工作机制 449
1433 SMTP命令码和工作原理 449
1434 SMTP通信模型 450
1435 SMTP的命令和应答 451
144 POP3协议原理 452
1441 POP3协议简介 452
1442 POP3工作原理 453
1443 POP3命令原始码 454
1444 POP3会话实例 459
145 实例分析与程序设计 460
1451 总界面设计 460
1452 SMTP客户端设计 461
1453 POP3客户端设计 473
0条评论