网路通信----笔记

正版 Visual C++网络通信开发入门与编程实践-------- 李媛媛

编辑推荐


超长时间多媒体有声讲解视频
  全书以通信协议层的技术为主线
  按网络体系结构从应用层到数据链路层逐一讲解
  实例完美融合知识点、技巧、行业知识与成功经验

内容提要


本书从介绍通信基础知识和VC++基本编程模型开始,通过众多的小实例来贯穿讲解晦涩的基础知识;然后按照通信协议层展开,将通信协议层和实际应用结合,让读者在学习“基础”后学习中、高级应用,最终提高实际应用水平和独立编程技能;本书最后部分的综合案例,可以让读者对前面学习的内容融会贯通,以便深刻理解与实践应用。
  本书的特点是:以通信协议层的技术为主线,在此基础上讲解各技术的应用范围,再通过众多小、中、大型实例来全面而生动地讲解VC++的知识,既能让读者全面地学习VC++技术,又能让读者联系实际,从而摆脱单纯讲解软件功能的枯燥学习模式。本书在讲解VC++的过程中还穿插大量提示和技巧,并对复杂和容易忽略的问题进行单独说明。书中的实例制作深入浅出,步骤详细清晰,可以帮助读者轻松、快速地学习VC++,能够保证对VC++不了解的读者也可以轻松学习。同时对实例进行细致的选择,使本书将知识点、技巧、行业知识和成功经验完美地融合到实例中,也让中级读者感觉物有所值。
  本书所配光盘中包含多媒体视频教学和实例源文件。
  本书适合VC++初、中级自学用户及VC++设计爱好者,同时也可作为计算机技能中级培训教材。

目录


第1章 Visual C++网络通信基础
1.1 计算机网络的组成及体系结构
1.1.1 网络边缘
1.1.2 网络核心
1.1.3 计算机网络的分层体系结构
1.2 初识Windows Sockets编程规范
1.2.1 Windows Sockets的相关概念
1.2.2 Winsock技术特点
1.2.3 Winsock编程原理

2.2.2 服务方式

UNIX系统的I/O命令集,是从Maltics和早期系统中的命令演变出来的,其模式为打开一读/写一关闭(open-write-read- close)。在一个用户进程进行I/O操作时,它首先调用"打开"获得对指定文件或设备的使用权,并返回称为文件描述符的整型数,以描述用户在打开的文 件或设备上进行I/O操作的进程。然后这个用户进程多次调用"读/写"以传输数据。当所有的传输操作完成后,用户进程关闭调用,通知操作系统已经完成了对 某对象的使用。
    TCP/IP协议被集成到UNIX内核中时,相当于在UNIX系统引入了一种新型的I/O操作。UNIX用户进程与网络协议的交互作用比用户进程与传统的 I/O设备相互作用复杂得多。首先,进行网络操作的两个进程在不同机器上,如何建立它们之间的联系?其次,网络协议存在多种,如何建立一种通用机制以支持 多种协议?这些都是网络应用编程界面所要解决的问题。
在UNIX系统中,网络应用编程界面有两类:UNIX BSD的套接字(socket)和UNIX System V的TLI。由于Sun公司采用了支持TCP/IP的UNIX BSD操作系统,使TCP/IP的应用有更大的发展,其网络应用编程界面──套接字(socket)在网络软件中被广泛应用,至今已引进微机操作系统 DOS和Windows系统中,成为开发网络应用软件的强有力工具,本章将要详细讨论这个问题。
2.2 套接字编程基本概念
在开始使用套接字编程之前,首先必须建立以下概念。
2.2.1 网间进程通信
进程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信 提供了相应设施,如UNIX BSD中的管道(pipe)、命名管道(named pipe)和软中断信号(signal),UNIX system V的消息(message)、共享存储区(shared memory)和信号量(semaphore)等,但都仅限于用在本机进程之间通信。网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程 通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(process ID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。例如,主机A赋于某进程号5,在B机中也可以存在5号进程,因此,"5号进 程"这句话就没有意义了。
其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。
为了解决上述问题,TCP/IP协议引入了下列几个概念。
端口
网络中可以被命名和寻址的通信端口,是操作系统可分配的一种资源。
按照OSI七层协议的描述,传输层与网络层在功能上的最大区别是传输层提供进程通信能力。从这个意义上讲,网络通信的最终地址就不仅仅是主机地址了,还包 括可以描述进程的某种标识符。为此,TCP/IP协议提出了协议端口(protocol port,简称端口)的概念,用于标识通信的进程。
端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序(即进程)通过系统调用与某端口建立连接(binding)后,传输层传给该端口 的数据都被相应进程所接收,相应进程发给传输层的数据都通过该端口输出。在TCP/IP协议的实现中,端口操作类似于一般的I/O操作,进程获取一个端 口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问之。
类似于文件描述符,每个端口都拥有一个叫端口号(port number)的整数型标识符,用于区别不同端口。由于TCP/IP传输层的两个协议TCP和UDP是完全独立的两个软件模块,因此各自的端口号也相互独 立,如TCP有一个255号端口,UDP也可以有一个255号端口,二者并不冲突。
端口号的分配是一个重要问题。有两种基本分配方式:第一种叫全局分配,这是一种集中控制方式,由一个公认的中央机构根据用户需要进行统一分配,并将结果公 布于众。第二种是本地分配,又称动态连接,即进程需要访问传输层服务时,向本地操作系统提出申请,操作系统返回一个本地唯一的端口号,进程再通过合适的系 统调用将自己与该端口号联系起来(绑扎)。TCP/IP端口号的分配中综合了上述两种方式。TCP/IP将端口号分为两部分,少量的作为保留端口,以全局 方式分配给服务进程。因此,每一个标准服务器都拥有一个全局公认的端口(即周知口,well-known port),即使在不同机器上,其端口号也相同。剩余的为自由端口,以本地方式进行分配。TCP和UDP均规定,小于256的端口号才能作保留端口。
地址
网络通信中通信的两个进程分别在不同的机器上。在互连网络中,两台机器可能位于不同的网络,这些网络通过网络互连设备(网关,网桥,路由器等)连接。因此需要三级寻址:
1. 某一主机可与多个网络相连,必须指定一特定网络地址;
2. 网络上每一台主机应有其唯一的地址;
3. 每一主机上的每一进程应有在该主机上的唯一标识符。
通常主机地址由网络ID和主机ID组成,在TCP/IP协议中用32位整数值表示;TCP和UDP均使用16位端口号标识用户进程。
网络字节顺序
不同的计算机存放多字节值的顺序不同,有的机器在起始地址存放低位字节(低价先存),有的存高位字节(高价先存)。为保证数据的正确性,在网络协议中须指定网络字节顺序。TCP/IP协议使用16位整数和32位整数的高价先存格式,它们均含在协议头文件中。
连接
两个进程间的通信链路称为连接。连接在内部表现为一些缓冲区和一组协议机制,在外部表现出比无连接高的可靠性。
半相关
综上所述,网络中用一个三元组可以在全局唯一标志一个进程:
(协议,本地地址,本地端口号)
这样一个三元组,叫做一个半相关(half-association),它指定连接的每半部分。
全相关
一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议。也就是说,不可能通信的一端用TCP协议,而另一端用UDP协议。因此一个完整的网间通信需要一个五元组来标识:
(协议,本地地址,本地端口号,远地地址,远地端口号)
这样一个五元组,叫做一个相关(association),即两个协议相同的半相关才能组合成一个合适的相关,或完全指定组成一连接。

在网络分层结构中,各层之间是严格单向依赖的,各层次的分工和协作集中体现在相邻层之间的界面上。"服务"是描述相邻层之间关系的抽象概念,即网络中各层 向紧邻上层提供的一组操作。下层是服务提供者,上层是请求服务的用户。服务的表现形式是原语(primitive),如系统调用或库函数。系统调用是操作 系统内核向网络应用程序或高层协议提供的服务原语。网络中的n层总要向n+1层提供比n-1层更完备的服务,否则n层就没有存在的价值。
在OSI的术语中,网络层及其以下各层又称为通信子网,只提供点到点通信,没有程序或进程的概念。而传输层实现的是"端到端"通信,引进网间进程通信概念,同时也要解决差错控制,流量控制,数据排序(报文排序),连接管理等问题,为此提供不同的服务方式:
面向连接(虚电路)或无连接
面向连接服务是电话系统服务模式的抽象,即每一次完整的数据传输都要经过建立连接,使用连接,终止连接的过程。在数据传输过程中,各数据分组不携带目的地 址,而使用连接号(connect ID)。本质上,连接是一个管道,收发数据不但顺序一致,而且内容相同。TCP协议提供面向连接的虚电路。
无连接服务是邮政系统服务的抽象,每个分组都携带完整的目的地址,各分组在系统中独立传送。无连接服务不能保证分组的先后顺序,不进行分组出错的恢复与重传,不保证传输的可靠性。UDP协议提供无连接的数据报服务。
下面给出这两种服务的类型及应用中的例子:
服务类型
服 务
例 子
面向连接
可靠的报文流
可靠的字节流
不可靠的连接
文件传输(FTP)
远程登录(Telnet)
数字话音
无连接
不可靠的数据报
有确认的数据报
请求-应答
电子邮件(E-mail)
电子邮件中的挂号信
网络数据库查询
顺序
在网络传输中,两个连续报文在端-端通信中可能经过不同路径,这样到达目的地时的顺序可能会与发送时不同。"顺序"是指接收数据顺序与发送数据顺序相同。TCP协议提供这项服务。
差错控制
保证应用程序接收的数据无差错的一种机制。检查差错的方法一般是采用检验"检查和(Checksum)"的方法。而保证传送无差错的方法是双方采用确认应答技术。TCP协议提供这项服务。
流控制
在数据传输过程中控制数据传输速率的一种机制,以保证数据不被丢失。TCP协议提供这项服务。
字节流
字节流方式指的是仅把传输中的报文看作是一个字节序列,不提供数据流的任何边界。TCP协议提供字节流服务。
报文
接收方要保存发送方的报文边界。UDP协议提供报文服务。
全双工/半双工
端-端间数据同时以两个方向/一个方向传送。
缓存/带外数据
在字节流服务中,由于没有报文边界,用户进程在某一时刻可以读或写任意数量的字节。为保证传输正确或采用有流控制的协议时,都要进行缓存。但对某些特殊的需求,如交互式应用程序,又会要求取消这种缓存。
在 数据传送过程中,希望不通过常规传输方式传送给用户以便及时处理的某一类信息,如UNIX系统的中断键(Delete或Control-c)、终端流控制 符(Control-s和Control-q),称为带外数据。逻辑上看,好象用户进程使用了一个独立的通道传输这些数据。该通道与每对连接的流相联系。 由于Berkeley Software Distribution中对带外数据的实现与RFC 1122中规定的Host Agreement不一致,为了将互操作中的问题减到最小,应用程序编写者除非与现有服务互操作时要求带外数据外,最好不使用它。
2.2.3 客户/服务器模式
在TCP/IP 网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式(Client/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。客户/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软 硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步 的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户/ 服务器模式的TCP/IP。
客户/服务器模式在操作过程中采取的是主动请求方式:
首先服务器方要先启动,并根据请求提供相应服务:
1. 打开一通信通道并告知本地主机,它愿意在某一公认地址上(周知口,如FTP为21)接收客户请求;
2. 等待客户请求到达该端口;
3. 接收到重复服务请求,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。
4. 返回第二步,等待另一客户请求。
5. 关闭服务器
客户方:
1. 打开一通信通道,并连接到服务器所在主机的特定端口;
2. 向服务器发服务请求报文,等待并接收应答;继续提出请求......
3. 请求结束后关闭通信通道并终止。
从上面所描述过程可知:
1. 客户与服务器进程的作用是非对称的,因此编码不同。
2. 服务进程一般是先于客户请求而启动的。只要系统运行,该服务进程一直存在,直到正常或强迫终止。
2.2.4 套接字类型
TCP/IP的socket提供下列三种类型套接字。
流式套接字(SOCK_STREAM)
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复地发送,且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。文件传送协议(FTP)即使用流式套接字。
数据报式套接字(SOCK_DGRAM)
提供了一个无连接服务。数据包以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。网络文件系统(NFS)使用数据报式套接字。
原始式套接字(SOCK_RAW)
该接口允许对较低层协议,如IP、ICMP直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备。
2.3 基本套接字系统调用
为了更好地说明套接字编程原理,下面给出几个基本套接字系统调用说明。
2.3.1 创建套接字──socket()
应用 程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段,其调用格式如下:
SOCKET PASCAL FAR socket(int af, int type, int protocol);
该 调用要接收三个参数:af、type、protocol。参数af指定通信发生的区域,UNIX系统支持的地址族有:AF_UNIX、AF_INET、 AF_NS等,而DOS、WINDOWS中仅支持AF_INET,它是网际网区域。因此,地址族与协议族相同。参数type 描述要建立的套接字的类型。参数protocol说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。根据 这三个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号。因此,socket()系统调用实际上指定了相关五元组中的"协议"这一 元。
1.2.4 实例—Willsock实现基于TCP的客户端/服务器通信

C/S模式
客户机/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户机/服务器模式的TCP/IP。

客户机/服务器模式在操作过程中采取的是主动请求的方式。

基于TCP(面向连接)socket编程

服务端进程通过bind方法将其套接字告知系统,以使其他的套接字能找到它。它可通过套接字的“侦听(listen)”来“接收(accept)”发过来的信息。客户端的进程同服务端套接字建立连接然后交换信息。需要的信息都可以从该通道向任一端进行发送。

面向连接的TCP通信过程如下:
clip_image002

服务器:

创建端点 (socket())

绑定地址(bind())

指定队列(listen())

等待连接 (accept())

传输数据 (read()/write())

客户端:

创建端点 (socket())

链接服务器 (connect())

传输数据(read()/write())

基于UDP(面向无连接)socket编程

用无连接协议,双方的套接字都需要用bind方法来告知系统。这是因为每方的信息都会单独处理,所以每次服务端发信息过来时,客户端都需要找到它,反之亦然。每次调用bind方法,都绑定了一个新的端口。当然,如果端口已经被使用了,则是不能被绑定的。如果你指定的端口为0,则系统会把当前可用的端口自动给你一个。由于发送信息的额外任务,进程不会使用read/write方法,而是使用recvfrom/sendto方法。这两个方法的参数一个是要写入的套接字,另一个则是远程计算机上服务的地址。

clip_image003

服务端:

创建端点(socket())

绑定地址 (bind())

传输数据(sendto()/recvfrom())

客户端:

创建端点 (socket())

绑定地址(bind()) (connect方法可选择调用)

连接服务端(connect())

传输数据(sendto()/recvfrom())

多线程的设计

VC中利用多线程技术实现线程之间的通信》这篇文章比较适合线程的了解,Win32 提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。MFC中使用线程要注意:

         1、尽量少的使用全局变量、static变量做共享数据,尽量使用参数传递对象。

         2、在MFC中请慎用线程。因为MFC的框架假定你的消息处理都是在主线程中完成的。首先窗口句柄是属于线程的,如果拥有窗口句柄的线程退出了,如果另一个线程处理这个窗口句柄,系统就会出现问题。而MFC为了避免这种情况的发生,使你在子线程中调用消息(窗口)处理函数时,就会不停的出Assert错误,烦都烦死你。典型的例子就时CSocket,因为CSocket是使用了一个隐藏窗口实现了假阻塞,所以不可避免的使用了消息处理函数,如果你在子线程中使用CSocket,你就可能看到assert的弹出了。

         3、不要在不同的线程中同时注册COM组件。

常见问题的解决

1、关闭套接字

我们在利用IOCP(完成端口)进行程序设计的时候,经常要关闭一些不满足条件的套接字。假如我们直接采用closesocket方法进行关闭的话,绑定到IO端口的此套接字的未发送的数据就会丢失,这种情况是我们不愿意发生的。下面介绍一种合理关闭此套接字的方法:
  首先,利用setsockopt(MSDN)函数设定套接字的选项,我们把此套接字设定为:假如有数据未发送,当数据发送完后再关闭此套接字。
  代码如下:
LINGER lingerStruct;
lingerStruct.l_onoff = 1;
lingerStruct.l_linger = 0;
setsockopt(Socket, SOL_SOCKET, SO_LINGER,
(char *)&lingerStruct, sizeof(lingerStruct) );
// Now close the socket handle. This will do an abortive or graceful close, as requested.
CancelIo((HANDLE) Socket);
closesocket(Socket);
clientSocket = INVALID_SOCKET;
  当在完成端口的数据被发送出去之后,套接字就会被关闭,这样我们就完成了一个套接字的关闭。

        <参考 http://www.examda.com/ncre2/cpp/fudao/20090107/091252332.html>

2、解决 Socket API错误代码:WSAECONNRESET (10054)

出现原因:使用UDP SOCKET时(利用事件触发方式),如果发送端在发送数据时(WSASendTo),接收端没还有创建,那么发送端将会收到一个事件通知,此时调用WSARecv()函数时将会产生调用错误(WSAECONNRESET ),从这以后,这个发送端这个SOCKET无法接受到数据。解决办法:

         a.头文件中加入下面代码:
               #include <Winsock2.h>
               #pragma comment(lib,"ws2_32.lib")
               #define IOC_VENDOR 0x18000000
               #define _WSAIOW(x,y) (IOC_IN|(x)|(y))
               #define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12)
         b.在创建socket之后加入下面代码:
               DWORD    dwBytesReturned = 0;
               BOOL        bNewBehavior = FALSE;
               DWORD    status;
               status = WSAIoctl(m_hSock, SIO_UDP_CONNRESET,
                           &bNewBehavior,
                           sizeof (bNewBehavior),
                           NULL, 0, &dwBytesReturned,
                           NULL, NULL);
      <参考 http://blog.csdn.net/wupangzi/archive/2009/07/27/4384081.aspxhttp://hi.baidu.com/jetqu2003/blog/item/397700031435e9703812bbcc.html>

1.3 利用MFC网络编程
1.3.1 CAsyncSocket类的基本组成
1.3.2 CSocket类的基本组成
1.3.3 Winlnet类的基本组成
1.3.4 实例——基于CSocket的网络聊天室
第2章 认识Windows编程模型
2.1 Windows初级编程模型
2.1.1 匈牙利命名法
2.1.2 Visual C++使用入门
2.1.3 最简单的Windows应用程序
2.2 Windows应用程序剖析
2.2.1 真正的Windows应用程序
2.2.2 Windows程序分析
2.2.3 重要的消息事件处理
2.2.4 Windows控件的应用
2.3 Windows高级编程
2.3.1 利用Windows菜单中的位图资源
2.3.2 利用资源制作菜单
2.4 小结
第3章 网络基本应用在VC++中的实现
3.1 获取网卡的MAC地址
3.1.1 原理
3.1.2 实现程序
3.2 获取网络中计算机的IP地址和计算机名

深入DNS域名解析服务原理

DNS分为Client和Server,Client扮演发问的角色,也就是问Server一个Domain Name,而Server必须要回答此Domain Name的真正IP地址。而当地的DNS先会查自己的资料库。如果自己的资料库没有,则会往该DNS上所设的的DNS询问,依此得到答案之后,将收到的答案存起来,并回答客户。

DNS服务器会根据不同的授权区(Zone),记录所属该网域下的各名称资料,这个资料包括网域下的次网域名称及主机名称。

  在每一个名称服务器中都有一个快取缓存区(Cache),这个快取缓存区的主要目的是将该名称服务器所查询出来的名称及相对的IP地址记录在快取缓存区中,这样当下一次还有另外一个客户端到次服务器上去查询相同的名称 时,服务器就不用在到别台主机上去寻找,而直接可以从缓存区中找到该笔名称记录资料,传回给客户端,加速客户端对名称查询的速度。例如:

  当DNS客户端向指定的DNS服务器查询网际网路上的某一台主机名称 DNS服务器会在该资料库中找寻用户所指定的名称 如果没有,该服务器会先在自己的快取缓存区中查询有无该笔纪录,如果找到该笔名称记录后,会从DNS服务器直接将所对应到的IP地址传回给客户端 ,如果名称服务器在资料记录查不到且快取缓存区中也没有时,服务器首先会才会向别的名称服务器查询所要的名称。例如:

DNS客户端向指定的DNS服务器查询网际网路上某台主机名称,当DNS服务器在该资料记录找不到用户所指定的名称时,会转向该服务器的快取缓存区找寻是否有该资料 ,当快取缓存区也找不到时,会向最接近的名称服务器去要求帮忙找寻该名称的IP地址 ,在另一台服务器上也有相同的动作的查询,当查询到后会回复原本要求查询的服务器,该DNS服务器在接收到另一台DNS服务器查询的结果后,先将所查询到的主机名称及对应IP地址记录到快取缓存区中 ,最后在将所查询到的结果回复给客户端 。

  范例

  我们举例说明,假设我们要查询网际网路上的一个名称为www.test.com.cn,从此名称我们知道此部主机在中国CN,而且要找的组织名称test.com.cn此网域下的www主机,以下为名称解析过程的每一步骤。

  《Step 1》在DNS的客户端(Reslover)键入查询主机的指令,如:

c:\ping www.test.com.cn

pinging www.test.com.cn 【192.72.80.36】with 32bytes of data

reply from 192.72.80.36 bytes time <10ms ttl 253

  《Step 2》而被指定的DNS服务器先行查询是否属于该网域下的主机名称,如果查出改主机名称并不属于该网域范围,之后会再查询快取缓存区的纪录资料,查是否有此机名称。

  《Step 3》查询后发现缓存区中没有此纪录资料,会取得一台根网域的其中一台服务器,发出说要找www.test.com.cn的Request。

  《Step 4》在根网域中,向Root Name Server询问,Root Name Server记录了各Top Domain分别是由哪些DNS Server负责,所以他会响应最接近的Name Server为控制CN网域的DNS伺服主机。

  《Step 5》Root Name Server已告诉Local DNS Server哪部Name Server负责.cn这个Domain,然后Local DNS再向负责发出找寻www.test.com.cn的名称Request。

  《Step 6》在.cn这个网域中,被指定的DNS服务器在本机上没有找到此名称的的纪录,所以会响应原本发出查询要求的DNS服务器说最近的服务器在哪里?他会回应最近的主机为控制com.cn网域的DNS伺服主机。

  《Step 7》原本被查询的DNS服务器主机,收到继续查询的IP位置后,会再向com.cn的网域的DNS Server发出寻找www.test.com.cn名称搜寻的要求。

  《Step 8》com.cn的网域中,被指定的DNS Server在本机上没有找到此名称的记录,所以会回复查询要求的DNS Server告诉他最接近的服务器在哪里?他就回应最接近为控制test.com.cn的网域的DNS主机。

  《Step 9》原本被查询的DNS Server,在接收到应继续查询的位置,在向test.com.cn网域的DNS Server发出寻找www.test.com.cn的要求,最后会在test.com.cn的网域的DNS Server找到www.test.com.cn此主机的IP。

  《Step 10》所以原本发出查询要求的DNS服务器,再接收到查询结果的IP位置后,响应回给原查询名称的DNS客户端。

  两种真正DNS的查询模式

  有两种询问原理,分为Recursive和Interactive两种。前者是由DNS代理去问,问的方法是用Interactive方式,后者是由本机直接做Interactive式的询问。由上例可以看出,我们一般查询名称的过程中,实际上这两种查询模式都是交互存在着的。

  递归式(Recursive):DNS客户端向DNS Server的查询模式,这种方式是将要查询的封包送出去问,就等待正确名称的正确响应,这种方式只处理响应回来的封包是否是正确响应或是说是找不到该名称的错误讯息。

  交谈式(Interactive):DNS Server间的查询模式,由Client端或是DNS Server上所发出去问,这种方式送封包出去问,所响应回来的资料不一定是最后正确的名称位置,但也不是如上所说的响应回来是错误讯息,他响应回来告诉你最接近的IP位置,然后再到此最接近的IP上去寻找所要解析的名称,反复动作直到找到正确位置。

3.2.1 原理
3.2.2 实现程序
3.3 超链接程序的原理与实现
3.3.1 原理
3.3.2 实现程序

超级链接程序原理与实现MFC网络编程 2010-12-21 21:02:49 阅读 评论 字号:大中小 订阅 .

(一)原理:要实现超级链接程序,首先创建一个CHyperLink类,而该类是从静态文本框类Static继承而来的,因此具备了CStatic的所有属性,同时在该类的基础上,扩展一些功能,比如设定颜色、设定鼠标形状等,然后通过对操作函数的调用链接到相应的URL

(二)类声明部分的代码:

class CHyperLink : public CStatic

{

// Construction

public:

CHyperLink();

// Attributes

public:

// Operations

public:

void SetURL(CString strURL);//设定URL

CString GetURL()const;//获得URL

void SetColours(COLORREF crLinkColour,COLORREF crVisitedColour,COLORREF crHoverColour=-1);//设定颜色

COLORREF GetLinkColour()const;//获得连接颜色

COLORREF GetVisitedColour() const;//获得被访问后的颜色

COLORREF GetHoverColour()const;//获得鼠标移动上以后的颜色

void SetVisited(BOOL bVisited=TRUE);//设定是否被访问过

BOOL GetVisited()const;//获得是否被访问过

void SetLinkCursor(HCURSOR hCursor);//设定鼠标形状

HCURSOR GetLinkCursor()const;//获得鼠标形状

void SetUnderline(BOOL bUnderline=TRUE);//设定是否有下划线

BOOL GetUnderline()const;//获得是否有下划线

void SetAutoSize(BOOL bAutoSize=TRUE);//设定是否是自动改变大小

BOOL GetAutoSize() const;

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CHyperLink)

public:

virtual BOOL PreTranslateMessage(MSG* pMsg);

protected:

virtual void PreSubclassWindow();

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CHyperLink();

protected:

HINSTANCE GotoURL(LPCTSTR url,int showcmd);//连接到URL

void ReportError(int nError);//打印错误

LONG GetRegKey(HKEY key,LPCTSTR subkey,LPTSTR retdata);//获得注册表信息

void PositionWindow();//调整位置

void SetDefaultCursor();//设定默认的鼠标形状

protected:

COLORREF m_crLinkColour,m_crVisitedColour; //超级链接颜色

COLORREF m_crHoverColour;//鼠标停留颜色

BOOL m_bOverControl;//是否鼠标移到控件上

BOOL m_bVisited;//是否被访问

BOOL m_bUnderline;//是否有下画线

BOOL m_bAdjustToFit;//是否自动调整控件大小

CString m_strURL;//URL

CFont m_Font;//设定字体

HCURSOR m_hLinkCursor;//光标

CToolTipCtrl m_ToolTip;//提示文字

// Generated message map functions

//{{AFX_MSG(CHyperLink)

afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);

afx_msg void OnMouseMove(UINT nFlags, CPoint point);

afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);

afx_msg void OnClicked();

afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

(三)类实现部分的代码:

BOOL CHyperLink::PreTranslateMessage(MSG* pMsg) //重载该函数,可以处理键盘和鼠标消息

{

// TODO: Add your specialized code here and/or call the base class

m_ToolTip.RelayEvent(pMsg);

return CStatic::PreTranslateMessage(pMsg);

}

void CHyperLink::PreSubclassWindow() //定制控件

{

// 获得鼠标单击事件

DWORD dwStyle = GetStyle();

::SetWindowLong(GetSafeHwnd(), GWL_STYLE, dwStyle | SS_NOTIFY);

// 如果URL为空,设定为窗体名称

if (m_strURL.IsEmpty())

GetWindowText(m_strURL);

// 同时检查窗体标题是否为空,如果为空则设定为URL

CString strWndText;

GetWindowText(strWndText);

if (strWndText.IsEmpty()) {

ASSERT(!m_strURL.IsEmpty());

SetWindowText(m_strURL);

}

// 创建字体

LOGFONT lf;

GetFont()->GetLogFont(&lf);

lf.lfUnderline = m_bUnderline;

m_Font.CreateFontIndirect(&lf);

SetFont(&m_Font);

PositionWindow(); // 调整窗体大小

SetDefaultCursor(); // 设定默认鼠标形状

//创建提示信息

CRect rect;

GetClientRect(rect);

m_ToolTip.Create(this);

m_ToolTip.AddTool(this, m_strURL, rect, TOOLTIP_ID);

CStatic::PreSubclassWindow();

}

HBRUSH CHyperLink::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

{

HBRUSH hbr = CStatic::OnCtlColor(pDC, pWnd, nCtlColor);

// TODO: Change any attributes of the DC here

// TODO: Return a different brush if the default is not desired

return hbr;

}

void CHyperLink::OnMouseMove(UINT nFlags, CPoint point) //鼠标移动事件

{

// TODO: Add your message handler code here and/or call default

CStatic::OnMouseMove(nFlags, point);

if(m_bOverControl) //判断是否鼠标在控件上方

{

CRect rect;

GetClientRect(rect);

if(!rect.PtInRect(point))

{

m_bOverControl=FALSE;

ReleaseCapture();

RedrawWindow();

return;

}

}

else

{

m_bOverControl=TRUE;

RedrawWindow();

SetCapture();

}

}

BOOL CHyperLink::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)

{

// TODO: Add your message handler code here and/or call default

if (m_hLinkCursor)

{

::SetCursor(m_hLinkCursor);

return TRUE;

}

return FALSE;

}

void CHyperLink::OnClicked()

{

int result = (int)GotoURL(m_strURL, SW_SHOW);

m_bVisited = (result > HINSTANCE_ERROR);

if (!m_bVisited) {

MessageBeep(MB_ICONEXCLAMATION); // Unable to follow link

ReportError(result);

} else

SetVisited(); // Repaint to show visited colour

}

void CHyperLink::PositionWindow()//调整窗口位置事件

{

if(!::IsWindow(GetSafeHwnd()) || m_bAdjustToFit)

return ;

CRect rect;//得到当前窗口的位置

GetWindowRect(rect);

CWnd* pParent=GetParent();

if(pParent)

pParent->ScreenToClient(rect);

CString strWndText; //得到窗口文本的大小

GetWindowText(strWndText);

CDC* pDC=GetDC();

CFont* pOldFont=pDC->SelectObject(&m_Font);

CSize Extent=pDC->GetTextExtent(strWndText);

pDC->SelectObject(pOldFont);

ReleaseDC(pDC);

DWORD dwStyle=GetStyle();//通过窗口的风格获得文本的环境

if(dwStyle & SS_CENTERIMAGE)

rect.DeflateRect(0,(rect.Height()-Extent.cy)/2);

else

rect.bottom=rect.top+Extent.cy;

if(dwStyle & SS_CENTER)

rect.DeflateRect((rect.Width()-Extent.cx)/2,0);

else if(dwStyle & SS_RIGHT)

rect.left=rect.right-Extent.cx;

else

rect.right=rect.left+Extent.cx;

SetWindowPos(NULL,rect.left,rect.top,rect.Width(),rect.Height(),SWP_NOZORDER);

}

//链接到目标地址

HINSTANCE CHyperLink::GotoURL(LPCTSTR url, int showcmd)

{

TCHAR key[MAX_PATH + MAX_PATH];

// 调用函数ShellExecute()

HINSTANCE result = ShellExecute(NULL, _T("open"), url, NULL,NULL, showcmd);

// 如果错误,则检查注册表获得.htm文件的注册键值

if ((UINT)result <= HINSTANCE_ERROR) {

if (GetRegKey(HKEY_CLASSES_ROOT, _T(".htm"), key) == ERROR_SUCCESS) {

lstrcat(key, _T("\\shell\\open\\command"));

if (GetRegKey(HKEY_CLASSES_ROOT,key,key) == ERROR_SUCCESS) {

TCHAR *pos;

pos = _tcsstr(key, _T("\"%1\""));

if (pos == NULL) { // 没有发现

pos = strstr(key, _T("%1")); // 检查%1

if (pos == NULL) // 没有参数

pos = key+lstrlen(key)-1;

else

*pos = '\0'; // 删除参数

}

else

*pos = '\0'; // 删除参数

lstrcat(pos, _T(" "));

lstrcat(pos, url);

result = (HINSTANCE) WinExec(key,showcmd);

}

}

}

return result;

}

HBRUSH CHyperLink::CtlColor(CDC* pDC, UINT nCtlColor)

{

ASSERT(nCtlColor == CTLCOLOR_STATIC);

if (m_bOverControl)

pDC->SetTextColor(m_crHoverColour);

else if (m_bVisited)

pDC->SetTextColor(m_crVisitedColour);

else

pDC->SetTextColor(m_crLinkColour);

// transparent text.

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)GetStockObject(NULL_BRUSH);

}

BOOL CHyperLink::GetAutoSize() const

{

return m_bAdjustToFit;

}

COLORREF CHyperLink::GetHoverColour() const//获得鼠标移动上以后的颜色

{

return m_crHoverColour;

}

COLORREF CHyperLink::GetLinkColour() const//获得连接颜色

{

return m_crLinkColour;

}

HCURSOR CHyperLink::GetLinkCursor() const//获得鼠标形状

{

return m_hLinkCursor;

}

LONG CHyperLink::GetRegKey(HKEY key, LPCTSTR subkey, LPTSTR retdata)//获得注册表信息

{

HKEY hkey;

LONG retval = RegOpenKeyEx(key, subkey, 0, KEY_QUERY_VALUE, &hkey);

if (retval == ERROR_SUCCESS) {

long datasize = MAX_PATH;

TCHAR data[MAX_PATH];

RegQueryValue(hkey, NULL, data, &datasize);

lstrcpy(retdata,data);

RegCloseKey(hkey);

}

return retval;

}

BOOL CHyperLink::GetUnderline() const//获得是否有下划线

{

return m_bUnderline;

}

CString CHyperLink::GetURL() const//获得URL

{

return m_strURL;

}

BOOL CHyperLink::GetVisited() const//获得是否被访问过

{

return m_bVisited;

}

COLORREF CHyperLink::GetVisitedColour() const//获得被访问后的颜色

{

return m_crVisitedColour;

}

void CHyperLink::ReportError(int nError)//打印错误

{

CString str;

switch (nError) {

case 0: str = "The operating system is out\nof memory or resources."; break;

case SE_ERR_PNF: str = "The specified path was not found."; break;

case SE_ERR_FNF: str = "The specified file was not found."; break;

case ERROR_BAD_FORMAT: str = "The .EXE file is invalid\n(non-Win32 .EXE or error in .EXE image)."; break;

case SE_ERR_ACCESSDENIED: str = "The operating system denied\naccess to the specified file."; break;

case SE_ERR_ASSOCINCOMPLETE: str = "The filename association is\nincomplete or invalid."; break;

case SE_ERR_DDEBUSY: str = "The DDE transaction could not\nbe completed because other DDE transactions\nwere being processed."; break;

case SE_ERR_DDEFAIL: str = "The DDE transaction failed."; break;

case SE_ERR_DDETIMEOUT: str = "The DDE transaction could not\nbe completed because the request timed out."; break;

case SE_ERR_DLLNOTFOUND: str = "The specified dynamic-link library was not found."; break;

case SE_ERR_NOASSOC: str = "There is no application associated\nwith the given filename extension."; break;

case SE_ERR_OOM: str = "There was not enough memory to complete the operation."; break;

case SE_ERR_SHARE: str = "A sharing violation occurred. ";

default: str.Format("Unknown Error (%d) occurred.", nError); break;

}

str = "Unable to open hyperlink:\n\n" + str;

AfxMessageBox(str, MB_ICONEXCLAMATION | MB_OK);

}

void CHyperLink::SetAutoSize(BOOL bAutoSize /* = TRUE */)//设定是否自动改变大小

{

m_bAdjustToFit = bAutoSize;

if (::IsWindow(GetSafeHwnd()))

PositionWindow();

}

//设定颜色

void CHyperLink::SetColours(COLORREF crLinkColour, COLORREF crVisitedColour,

COLORREF crHoverColour /* = -1 */)

{

m_crLinkColour = crLinkColour;

m_crVisitedColour = crVisitedColour;

if (crHoverColour == -1)

m_crHoverColour = ::GetSysColor(COLOR_HIGHLIGHT);

else

m_crHoverColour = crHoverColour;

if (::IsWindow(m_hWnd))

Invalidate();

}

void CHyperLink::SetDefaultCursor()//设定默认的鼠标形状

{

if (m_hLinkCursor == NULL) // No cursor handle - load our own

{

// Get the windows directory

CString strWndDir;

GetWindowsDirectory(strWndDir.GetBuffer(MAX_PATH), MAX_PATH);

strWndDir.ReleaseBuffer();

strWndDir += _T("\\winhlp32.exe");

// This retrieves cursor #106 from winhlp32.exe, which is a hand pointer

HMODULE hModule = LoadLibrary(strWndDir);

if (hModule) {

HCURSOR hHandCursor = ::LoadCursor(hModule, MAKEINTRESOURCE(106));

if (hHandCursor)

m_hLinkCursor = CopyCursor(hHandCursor);

}

FreeLibrary(hModule);

}

}

void CHyperLink::SetLinkCursor(HCURSOR hCursor)//设定鼠标形状

{

m_hLinkCursor = hCursor;

if (m_hLinkCursor == NULL)

SetDefaultCursor();

}

//设置下划线

void CHyperLink::SetUnderline(BOOL bUnderline /* = TRUE */)

{

m_bUnderline = bUnderline;

if (::IsWindow(GetSafeHwnd()))

{

LOGFONT lf;

GetFont()->GetLogFont(&lf);

lf.lfUnderline = m_bUnderline;

m_Font.DeleteObject();

m_Font.CreateFontIndirect(&lf);

SetFont(&m_Font);

Invalidate();

}

}

//设定URL

void CHyperLink::SetURL(CString strURL)

{

m_strURL = strURL;

if (::IsWindow(GetSafeHwnd())) {

PositionWindow();

m_ToolTip.UpdateTipText(strURL, this, TOOLTIP_ID);

}

}

void CHyperLink::SetVisited(BOOL bVisited /* = TRUE */) //设定是否被访问过

{

m_bVisited = bVisited;

if (::IsWindow(GetSafeHwnd()))

Invalidate();

}

3.4 获取域名和网卡类型的原理和实现
3.4.1 原理
3.4.2 实现程序
3.5 小结
第4章 串口通信及其实例
4.1 串行通信原理
4.1.1 串行通信基本概念
4.1.2 单工、半双工和全双工定义
4.1.3 串行通信协议
4.1.4 串行通信方式
4.2 MSComm控件
4.2.1 VC++中的MSComm控件
4.2.2 实例——-MSCscorIlIn多串口通信
4.3 Windows APl串口通信编程
4.3.1 Windows串口通信API函数
4.3.2 VC++中的CserialPort类
4.3.3 实例——串口的多线程通信
4.4小结
第5章 应用层协议及编程实例
5.1 应用层协议体系结构
5.1.1 应用层协议原理
5.1.2 网络应用程序的体系结构
5.2 HTTP协议
5.2.1 HTTP协议

http协议的主要特点

HTTP协议的主要特点可概括如下:

1.支持客户/服务器模式。

2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。

3.灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type(Content-Type是HTTP包中用来表示内容类型的标识)加以标记。

4.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

1.连接(Connection):一个传输层的实际环流,它是建立在两个相互通讯的应用程序之间。  2.消息(Message):HTTP通讯的基本单位,包括一个结构化的八元组序列并通过连接传输。  3.请求(Request):一个从客户端到服务器的请求信息包括应用于资源的方法、资源的标

HTTP协议的几个重要概念

1.连接(Connection):一个传输层的实际环流,它是建立在两个相互通讯的应用程序之间。
2.消息(Message):HTTP通讯的基本单位,包括一个结构化的八元组序列并通过连接传输。
3.请求(Request):一个从客户端到服务器的请求信息包括应用于资源的方法、资源的标识符和协议的版本号
4.响应(Response):一个从服务器返回的信息包括HTTP协议的版本号、请求的状态(例如“成功”或“没找到”)和文档的MIME类型。
5.资源(Resource):由URI标识的网络数据对象或服务。
6.实体(Entity):数据资源或来自服务资源的回映的一种特殊表示方法,它可能被包围在一个请求或响应信息中。一个实体包括实体头信息和实体的本身内容。
7.客户机(Client):一个为发送请求目的而建立连接的应用程序。
8.用户代理(Useragent):初始化一个请求的客户机。它们是浏览器、编辑器或其它用户工具。
9.服务器(Server):一个接受连接并对请求返回信息的应用程序。
10.源服务器(Originserver):是一个给定资源可以在其上驻留或被创建的服务器。
11.代理(Proxy):一个中间程序,它可以充当一个服务器,也可以充当一个客户机,为其它客户机建立请求。请求是通过可能的翻译在内部或经过传递到其它的服务器中。一个代理在发送请求信息之前,必须解释并且如果可能重写它。
  代理经常作为通过防火墙的客户机端的门户,代理还可以作为一个帮助应用来通过协议处理没有被用户代理完成的请求。
12.网关(Gateway):一个作为其它服务器中间媒介的服务器。与代理不同的是,网关接受请求就好象对被请求的资源来说它就是源服务器;发出请求的客户机并没有意识到它在同网关打交道。
  网关经常作为通过防火墙的服务器端的门户,网关还可以作为一个协议翻译器以便存取那些存储在非HTTP系统中的资源。
13.通道(Tunnel):是作为两个连接中继的中介程序。一旦激活,通道便被认为不属于HTTP通讯,尽管通道可能是被一个HTTP请求初始化的。当被中继的连接两端关闭时,通道便消失。当一个门户(Portal)必须存在或中介(Intermediary)不能解释中继的通讯时通道被经常使用。
14.缓存(Cache):反应信息的局域存储。

HTTP协议基础

HTTP(HyperTextTransferProtocol)是超文本传输协议的缩写,它用于传送WWW方式的数据,关于HTTP协议的详细内容请参考RFC2616。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求,请求头包含请求的方法、URI、协议版本、以及包含请求修饰符、客户信息和内容的类似于MIME的消息结构。服务器以一个状态行作为响应,相应的内容包括消息协议的版本,成功或者错误编码加上包含服务器信息、实体元信息以及可能的实体内容。
通常HTTP消息包括客户机向服务器的请求消息和服务器向客户机的响应消息。这两种类型的消息由一个起始行,一个或者多个头域,一个只是头域结束的空行和可选的消息体组成。HTTP的头域包括通用头,请求头,响应头和实体头四个部分。每个头域由一个域名,冒号(:)和域值三部分组成。域名是大小写无关的,域值前可以添加任何数量的空格符,头域可以被扩展为多行,在每行开始处,使用至少一个空格或制表符。
通用头域
通用头域包含请求和响应消息都支持的头域,通用头域包含Cache-Control、Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via。对通用头域的扩展要求通讯双方都支持此扩展,如果存在不支持的通用头域,一般将会作为实体头域处理。下面简单介绍几个在UPnP消息中使用的通用头域。
Cache-Control头域
Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令包括no-cache、no-store、max-age、max-stale、min-fresh、only-if-cached,响应消息中的指令包括public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age。各个消息中的指令含义如下:
Public指示响应可被任何缓存区缓存。
Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。
no-cache指示请求或响应消息不能缓存
no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
Date头域
Date头域表示消息发送的时间,时间的描述格式由rfc822定义。例如,Date:Mon,31Dec200104:25:57GMT。Date描述的时间表示世界标准时,换算成本地时间,需要知道用户所在的时区。
Pragma头域
Pragma头域用来包含实现特定的指令,最常用的是Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。

请求消息
请求消息的第一行为下面的格式:
MethodSPRequest-URISPHTTP-VersionCRLFMethod表示对于Request-URI完成的方法,这个字段是大小写敏感的,包括OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE。方法GET和HEAD应该被所有的通用WEB服务器支持,其他所有方法的实现是可选的。GET方法取回由Request-URI标识的信息。HEAD方法也是取回由Request-URI标识的信息,只是可以在响应时,不返回消息体。POST方法可以请求服务器接收包含在请求中的实体信息,可以用于提交表单,向新闻组、BBS、邮件群组和数据库发送消息。
SP表示空格。Request-URI遵循URI格式,在此字段为星号(*)时,说明请求并不用于某个特定的资源地址,而是用于服务器本身。HTTP-Version表示支持的HTTP版本,例如为HTTP/1.1。CRLF表示换行回车符。请求头域允许客户端向服务器传递关于请求或者关于客户机的附加信息。请求头域可能包含下列字段Accept、Accept-Charset、Accept-Encoding、Accept-Language、Authorization、From、Host、If-Modified-Since、If-Match、If-None-Match、If-Range、If-Range、If-Unmodified-Since、Max-Forwards、Proxy-Authorization、Range、Referer、User-Agent。对请求头域的扩展要求通讯双方都支持,如果存在不支持的请求头域,一般将会作为实体头域处理。
典型的请求消息:
GEThttp://class/download.microtool.de:80/somedata.exe
Host:download.microtool.de
Accept:*/*
Pragma:no-cache
Cache-Control:no-cache
Referer:http://class/download.microtool.de/
User-Agent:Mozilla/4.04[en](Win95;I;Nav)
Range:bytes=554554-
上例第一行表示HTTP客户端(可能是浏览器、下载程序)通过GET方法获得指定URL下的文件。棕色的部分表示请求头域的信息,绿色的部分表示通用头部分。
Host头域
Host头域指定请求资源的Intenet主机和端口号,必须表示请求url的原始服务器或网关的位置。HTTP/1.1请求必须包含主机头域,否则系统会以400状态码返回。
Referer头域
Referer头域允许客户端指定请求uri的源资源地址,这可以允许服务器生成回退链表,可用来登陆、优化cache等。他也允许废除的或错误的连接由于维护的目的被追踪。如果请求的uri没有自己的uri地址,Referer不能被发送。如果指定的是部分uri地址,则此地址应该是一个相对地址。
Range头域
Range头域可以请求实体的一个或者多个子范围。例如,
表示头500个字节:bytes=0-499
表示第二个500字节:bytes=500-999
表示最后500个字节:bytes=-500
表示500字节以后的范围:bytes=500-
第一个和最后一个字节:bytes=0-0,-1
同时指定几个范围:bytes=500-600,601-999
但是服务器可以忽略此请求头,如果无条件GET包含Range请求头,响应会以状态码206(PartialContent)返回而不是以200(OK)。
User-Agent头域
User-Agent头域的内容包含发出请求的用户信息。
响应消息
响应消息的第一行为下面的格式:
HTTP-VersionSPStatus-CodeSPReason-PhraseCRLF
HTTP-Version表示支持的HTTP版本,例如为HTTP/1.1。Status-Code是一个三个数字的结果代码。Reason-Phrase给Status-Code提供一个简单的文本描述。Status-Code主要用于机器自动识别,Reason-Phrase主要用于帮助用户理解。Status-Code的第一个数字定义响应的类别,后两个数字没有分类的作用。第一个数字可能取5个不同的值:
1xx:信息响应类,表示接收到请求并且继续处理
2xx:处理成功响应类,表示动作被成功接收、理解和接受
3xx:重定向响应类,为了完成指定的动作,必须接受进一步处理
4xx:客户端错误,客户请求包含语法错误或者是不能正确执行
5xx:服务端错误,服务器不能正确执行一个正确的请求
响应头域允许服务器传递不能放在状态行的附加信息,这些域主要描述服务器的信息和Request-URI进一步的信息。响应头域包含Age、Location、Proxy-Authenticate、Public、Retry-After、Server、Vary、Warning、WWW-Authenticate。对响应头域的扩展要求通讯双方都支持,如果存在不支持的响应头域,一般将会作为实体头域处理。
典型的响应消息:
HTTP/1.0200OK
Date:Mon,31Dec200104:25:57GMT
Server:Apache/1.3.14(Unix)
Content-type:text/html
Last-modified:Tue,17Apr200106:46:28GMT
Etag:"a030f020ac7c01:1e9f"
Content-length:39725426
Content-range:bytes554554-40279979/40279980
上例第一行表示HTTP服务端响应一个GET方法。棕色的部分表示响应头域的信息,绿色的部分表示通用头部分,红色的部分表示实体头域的信息。
Location响应头
Location响应头用于重定向接收者到一个新URI地址。
Server响应头
Server响应头包含处理请求的原始服务器的软件信息。此域能包含多个产品标识和注释,产品标识一般按照重要性排序。
实体
请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成。实体头域包含关于实体的原信息,实体头包括Allow、Content-Base、Content-Encoding、Content-Language、Content-Length、Content-Location、Content-MD5、Content-Range、Content-Type、Etag、Expires、Last-Modified、extension-header。extension-header允许客户端定义新的实体头,但是这些域可能无法未接受方识别。实体可以是一个经过编码的字节流,它的编码方式由Content-Encoding或Content-Type定义,它的长度由Content-Length或Content-Range定义。
Content-Type实体头
Content-Type实体头用于向接收方指示实体的介质类型,指定HEAD方法送到接收方的实体介质类型,或GET方法发送的请求介质类型Content-Range实体头
Content-Range实体头用于指定整个实体中的一部分的插入位置,他也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。一般格式:
Content-Range:bytes-unitSPfirst-byte-pos-last-byte-pos/entity-legth
例如,传送头500个字节次字段的形式:Content-Range:bytes0-499/1234如果一个http消息包含此节(例如,对范围请求的响应或对一系列范围的重叠请求),Content-Range表示传送的范围,Content-Length表示实际传送的字节数。
Last-modified实体头
Last-modified实体头指定服务器上保存内容的最后修订时间。

一、HTTP协议是什么

我们在浏览器的地址栏里输入的网站地址叫做URL(UniformResourceLocator,统一资源定位符)。就像每家每户都有一个门牌地址一样,每个网页也都有一个Internet地址。当你在浏览器的地址框中输入一个URL或是单击一个超级链接时,URL就确定了要浏览的地址。浏览器通过超文本传输协议(HTTP),将Web服务器上站点的网页代码提取出来,并翻译成漂亮的网页。因此,在我们认识HTTP之前,有必要先弄清楚URL的组成,例如:http://www.microsoft.com/china/index.htm。它的含义如下:

1.http://:代表超文本传输协议,通知microsoft.com服务器显示Web页,通常不用输入;

2.www:代表一个Web(万维网)服务器;

3.Microsoft.com/:这是装有网页的服务器的域名,或站点服务器的名称;

4.China/:为该服务器上的子目录,就好像我们的文件夹;

5.Index.htm:index.htm是文件夹中的一个HTML文件(网页)。

我们知道,Internet的基本协议是TCP/IP协议,然而在TCP/IP模型最上层的是应用层(Applicationlayer),它包含所有高层的协议。高层协议有:文件传输协议FTP、电子邮件传输协议SMTP、域名系统服务DNS、网络新闻传输协议NNTP和HTTP协议等。

HTTP协议(HypertextTransferProtocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效,使网络传输减少。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等。这就是你为什么在浏览器中看到的网页地址都是以“http://”开头的原因。

自WWW诞生以来,一个多姿多彩的资讯和虚拟的世界便出现在我们眼前,可是我们怎么能够更加容易地找到我们需要的资讯呢?当决定使用超文本作为WWW文档的标准格式后,于是在1990年,科学家们立即制定了能够快速查找这些超文本文档的协议,即HTTP协议。经过几年的使用与发展,得到不断的完善和扩展,目前在WWW中使用的是HTTP/1.0的第六版。

二、HTTP是怎样工作的

既然我们明白了URL的构成,那么HTTP是怎么工作呢?我们接下来就要讨论这个问题。

由于HTTP协议是基于请求/响应范式的(相当于客户机/服务器)。一个客户机与服务器建立连接后,发送一个请求给服务器,请求方式的格式为:统一资源标识符(URL)、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容。服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。

许多HTTP通讯是由一个用户代理初始化的并且包括一个申请在源服务器上资源的请求。最简单的情况可能是在用户代理和服务器之间通过一个单独的连接来完成。在Internet上,HTTP通讯通常发生在TCP/IP连接之上。缺省端口是TCP80,但其它的端口也是可用的。但这并不预示着HTTP协议在Internet或其它网络的其它协议之上才能完成。HTTP只预示着一个可靠的传输。

这个过程就好像我们打电话订货一样,我们可以打电话给商家,告诉他我们需要什么规格的商品,然后商家再告诉我们什么商品有货,什么商品缺货。这些,我们是通过电话线用电话联系(HTTP是通过TCP/IP),当然我们也可以通过传真,只要商家那边也有传真。

以上简要介绍了HTTP协议的宏观运作方式,下面介绍一下HTTP协议的内部操作过程。

在WWW中,“客户”与“服务器”是一个相对的概念,只存在于一个特定的连接期间,即在某个连接中的客户在另一个连接中可能作为服务器。基于HTTP协议的客户/服务器模式的信息交换过程,它分四个过程:建立连接、发送请求信息、发送响应信息、关闭连接。这就好像上面的例子,我们电话订货的全过程。

其实简单说就是任何服务器除了包括HTML文件以外,还有一个HTTP驻留程序,用于响应用户请求。你的浏览器是HTTP客户,向服务器发送请求,当浏览器中输入了一个开始文件或点击了一个超级链接时,浏览器就向服务器发送了HTTP请求,此请求被送往由IP地址指定的URL。驻留程序接收到请求,在进行必要的操作后回送所要求的文件。在这一过程中,在网络上发送和接收的数据已经被分成一个或多个数据包(packet),每个数据包包括:要传送的数据;控制信息,即告诉网络怎样处理数据包。TCP/IP决定了每个数据包的格式。如果事先不告诉你,你可能不会知道信息被分成用于传输和再重新组合起来的许多小块。

也就是说商家除了拥有商品之外,它也有一个职员在接听你的电话,当你打电话的时候,你的声音转换成各种复杂的数据,通过电话线传输到对方的电话机,对方的电话机又把各种复杂的数据转换成声音,使得对方商家的职员能够明白你的请求。这个过程你不需要明白声音是怎么转换成复杂的数据的。

应用分析 HTTP网页访问应用分析

一、HTTP协议简介

1.什么是HTTP协议

HTTP,全称Hyper Text Transfer Protocol,中文名为超文本传输协议。

HTTP是一种用于从Web服务器端传送超文本标记语言(HTML-Hyper Text Markup Language)文件到客户端浏览器的传送协议,它是Internet上最常见的协议之一。我们通常访问的网页,就是通过HTTP协议进行传送的。

2.HTTP协议的工作原理

HTTP用名字标识某一资源时(即在浏览器中输入网址),遵循统一资源名(URN-Uniform Resource Name)的规则,当前网络中最常用的URN是统一资源定位符(URL-Uniform Resource Locator),当客户端在浏览器中输入一个URL或单击一个URL超链接时,就确定了要访问的地址。

以http://www.colasoft.com.cn/resource/index.html为例介绍URL的组成:

1) http://:表示使用超文本传输协议,通知Web服务器显示Web页,客户端可不输入;

2) www:代表1个Web服务器;

3) colasoft.com.cn/:Web服务器的域名,或站点服务器的名称;

4) resource/:Web服务器上的子目录,类似机器中的文件夹;

5) index.html:Web服务器上resource子目录中的一个网页文件,即Web服务器传送给客户端浏览器的文件。

HTTP使用TCP协议的80端口进行可靠数据传输,一个HTTP会话由客户端开始发起,包括以下步骤:

1) 客户端在浏览器中标识希望获取信息的URL;

2) 发起HTTP连接请求,启动客户端(UA)和一个初始WWW服务器或代理服务器之间的一个HTTP会话;

3) WWW服务器或代理服务器根据客户端的URL请求将内容传送给客户端。

3.HTTP协议的工作方式

宏观工作方式:

1) 客户端(UA)直接连接到Web服务器的通讯路径如图1所示,客户端与Web服务器之间的通讯不需要任何的中介服务器,这是最简单的情况。

(图1 客户端与Web服务器直接连接)

2) 客户端(UA)通过中介服务器连接到Web服务器的通讯路径如图2所示,客户端与Web服务器之间的通讯通过中介服务器进行转发,中介服务器可能有1个,也可能有多个。

clip_image004

(图2 客户端通过中介服务器与Web服务器连接)

3) 客户端(UA)到中介服务器的通讯路径如图3所示,客户端将请求发送给中介服务器1,中介服务器1将其发送中介服务器2,中介服务器2再发给Web服务器,最后客户端收到的内容由中介服务器1发送给它,而不是Web服务器。

clip_image005

(图3 客户端与中介服务器通讯过程)

内部操作过程:

如图4所示,它分为四个步骤:建立连接、发出请求信息、发出响应信息、关闭连接。

clip_image006

(图4 HTTP协议内部操作过程)

4.HTTP协议的报文格式服务器对HTTP的处理方式
HTTP协议的这种请求/回应的模式,使得服务器只能根据客户程序的请求发送回信息,这样的好处是客户具备很大的自由度,可以任意访问服务器上的信息。因此就存在多个客户同时访问一个服务器的问题。
  在Unix下,由一个守护进程来监视来自客户程序的请求,当守护进程接受到一个请求时,就建立一个新的进程对请求进行处理。通常服务器能创建足够多的新进程来回应客户的请求,然而如果同时发送请求的客户太多,那么服务器就有可能出现超载的情况,创建进程的速度跟不上众多客户发送请求的速度,这样就造成了服务器对外表现反应迟缓。此外,为了提高用户使用浏览器时的性能,现代浏览器还支持并发的访问方式,浏览一个网页时同时建立多个连接,以迅速获得一个网页上的多个图标,这样能更快速完成整个网页的传输。但是对服务器来讲,更增加了瞬间负载。
  显然,造成这个问题的关键是服务器对HTTP协议的处理方式,一次请求就要建立一个连接,在网页上充满了多个较小的图象文件的时候,那么服务器和客户程序之间的大部分工作是用于建立连接,而真正用于传递数据的工作却很轻松。因此,更好的利用现有连接,减少建立连接的消耗,就需要能在一次连接中回应多个请求。在HTTP1.1中提供了这种持续连接的方式,而下一代HTTP协议:HTTP-NG更增加了有关会话控制、丰富的内容协商等方式的支持,来提供更高效率的连接。
  除了针对每次请求都建立一个新进程的处理方式之外,HTTP守护进程也能使用其他的方式处理多个请求,例如使用多线程,或者使用异步方式在不同请求之间进行切换,就能在一个进程内处理多个请求。虽然比起建立新进程来讲,这样消耗的处理器资源略微减少,但是并不能从根本上消除并发访问带来的处理器资源不足的问题。一般使用线程和异步方式的程序较为复杂,不能很容易扩充对新特性的支持,并有可能因为程序内部要自己进行同步等原因也会造成资源消耗。使用这些方式,虽然对处理静态的网页有好处,但对于执行CGI程序,仍然要创建子进程进行处理。因此,大部分运行在Unix上的守护程序仍然使用多进程的方式,这种方式简单却有效。
  即使对于使用多进程方式进行处理的Web服务器,也有不同的处理方式。Unix系统中提供了超级服务器进程inetd,因此简单的Web服务器可以使用inetd来启动真正的Web服务器。然而,inetd效率不高,使用inetd的服务器不能用作高负载的服务器系统,因此高负载的Web服务器,本身来监听客户连接请求,并负责启动子进程真正处理客户的请求。
  如果选择的服务器程序的确需要使用inetd来启动,可以选择与inetd功能相同,但效率更高的超级服务器进程tcpserver,它可以比inetd更高效的启动服务进程。

客户端发送的HTTP报文,我们称为请求链;中介服务器或Web服务器发送的HTTP报文,称为响应链。两种报文都遵循以下格式:

l 一般开始行,即请求报文的请求行和应答报文的状态行;

l 总头;

l 报文头;

l 一个空行;

l 报文体。 

clip_image007

HTTP协议三--断点续传

 

断点续传是我们现在经常接触的概念,那么HTTP协议是如何支持断点续传的呢。我们先从一个例子来看看。
下面是一个断点续传的例子:(使用NetVampire得到)
I01-7-1219:19:23-------------------------Attempt1-------------------------
P01-7-1219:19:24Connectingto127.0.0.3...
P01-7-1219:19:24Connectedto127.0.0.3[127.0.0.3]
S01-7-1219:19:24GET/VS0515AI.EXEHTTP/1.1
S01-7-1219:19:24Connection:close
S01-7-1219:19:24Host:127.0.0.3
S01-7-1219:19:24Accept:*/*
S01-7-1219:19:24Pragma:no-cache
S01-7-1219:19:24Cache-Control:no-cache
S01-7-1219:19:24Referer:http://127.0.0.3/
S01-7-1219:19:24User-Agent:Mozilla/4.04[en](Win95;I;Nav)
S01-7-1219:19:24
R01-7-1219:19:24HTTP/1.1200OK
R01-7-1219:19:24Server:ZeroHttpServer/1.0
R01-7-1219:19:24Date:Thu,12Jul200111:19:24GMT
R01-7-1219:19:24Cache-Control:no-cache
R01-7-1219:19:24Last-Modified:Tue,30Jan200113:11:30GMT
R01-7-1219:19:24Content-Type:application/octet-stream
R01-7-1219:19:24Content-Length:15143086
R01-7-1219:19:24Connection:close
R01-7-1219:19:24
P01-7-1219:19:25Datatransferstarted
I01-7-1219:19:32JobStoppedbyuser
I01-7-1219:19:33Received5275648bytesin0:00:07(691435bytes/s)
I01-7-1219:19:40-------------------------Attempt2-------------------------
P01-7-1219:19:40Connectingto127.0.0.3...
P01-7-1219:19:40Connectedto127.0.0.3[127.0.0.3]
S01-7-1219:19:40GET/VS0515AI.EXEHTTP/1.1
S01-7-1219:19:40Connection:close
S01-7-1219:19:40Host:127.0.0.3
S01-7-1219:19:40Accept:*/*
S01-7-1219:19:40Pragma:no-cache
S01-7-1219:19:40Cache-Control:no-cache
S01-7-1219:19:40Referer:http://127.0.0.3/
S01-7-1219:19:40User-Agent:Mozilla/4.04[en](Win95;I;Nav)
S01-7-1219:19:40Range:bytes=5275648-
S01-7-1219:19:40
R01-7-1219:19:40HTTP/1.1206PartialContent
R01-7-1219:19:40Server:ZeroHttpServer/1.0
R01-7-1219:19:40Date:Thu,12Jul200111:19:40GMT
R01-7-1219:19:40Cache-Control:no-cache
R01-7-1219:19:40Last-Modified:Tue,30Jan200113:11:30GMT
R01-7-1219:19:40Content-Type:application/octet-stream
R01-7-1219:19:40Content-Range:bytes5275648-15143085/15143086
R01-7-1219:19:40Content-Length:9867438
R01-7-1219:19:40Connection:close
R01-7-1219:19:40
P01-7-1219:19:40Datatransferstarted
I01-7-1219:19:41JobStoppedbyuser
I01-7-1219:19:41Received1124756bytesin0:00:01(969617bytes/s)
第一次是普通的传输;第二次由于没有传完全,就发出了Range这个头部,从5275648字节开始传输(默认是按字节算),回应使用206状态值,表示现在开始部分传输,回复Content-Length头部,表示传输的部分,用字节记,然后就与普通传输没有区别了。
通过上面的例子,你应该了解HTTP断点续传的原理了吧。

二、分析HTTP通讯

1.分析HTTP访问的具体流程

我们使用科来网络分析系统5.0捕获并分析一个HTTP通讯过程,客户端主机名为“wangym”,客户端浏览器是IE6.0,请求的域名是“www.colasoft.com.cn”。

在客户端上打开科来网络分析系统5.0。为避免数据干扰,可以设定一个过滤器,只捕获本机的数据通讯。设定好后开始数据捕获,同时在本机的浏览器中输入www.colasoft.com.cn,待网页全部打开后,停止捕获。

注意:此文里提到的HTTP访问均指标准80端口的通信,对于非80端口的HTTP访问,用户可在“工程->高级分析模块->HTTP分析模块->常规设置->端口”处进行更改,系统默认为80,当HTTP服务有多个端口时,多个端口之间用分号分隔,如80;8080。

1) HTTP请求

图5所示的是科来网络分析系统5.0对上面访问www.colasoft.com.cn的操作的HTTP请求报文跟踪。

clip_image009

(图5 HTTP GET请求操作)

从图5中的数据包列表可知,上述操作中HTTP请求的原始信息如下:

1) 第1个数据包是DNS查询数据包,本机通过DNS查询获得www.colasoft.com.cn对应的IP地址。

2) 第2个数据包是DNS回应数据包,DNS服务器查询到域名www.colasoft.com.cn对应的IP是64.246.27.237,并将查询结果传送给客户端。

3) 3、4、5数据包是TCP连接的三次握手数据包,连接的双方是本机与域名www.colasoft.com.cn对应的IP地址64.246.27.237。

4) 第6个数据包是客户端发起的HTTP GET请求,向Web服务器处请求获得内容,第7帧的解码包含了GET请求的各参数信息。

上述HTTP访问的HTTP请求方法是GET,而GET仅仅是HTTP众多方法中的一种,HTTP通过不同的方法实现不同的功能,下表列出了HTTP常见的请求方法。

方 法

描 述

GET

向Web服务器请求一个文件

POST

向Web服务器发送数据让Web服务器进行处理

PUT

向Web服务器发送数据并存储在Web服务器内部

HEAD

检查一个对象是否存在

DELETE

从Web服务器上删除一个文件

CONNECT

对通道提供支持

TRACE

跟踪到服务器的路径

OPTIONS

查询Web服务器的性能

(表1 HTTP常见请求方法)

每个HTTP请求都包含两个部分:

1) HTTP请求行,大多情况下为GET或POST;

2) HTTP请求中的可选消息头,这些消息头会由于使用的HTTP客户端浏览器或客户端浏览器配置选项的不同而不同。

具体分析图5中第6个数据包的HTTP请求解码,可以得到如下信息:

1) HTTP请求:请求的方法是GET,“/”表示请求Web服务器的根目录,“HTTP/1.1”表示的是URI(Uniform Resource Identifier,统一资源标识符)及其版本;

2) Accept:指定客户端能够接收的内容类型,内容类型中的先后次序表示客户端接收的先后次序。这里可以看到客户端能够接收的类型有gif、bitmap、jpeg等等。

3) Accept-Language:指定优先选择的语言是中文;

4) Accept-Encoding:指定内容编码类型为gzip或deflate;

5) User-Agent:包含HTTP客户端运行的浏览器类型;

6) Host:包含的主机信息为www.colasoft.com.cn。

7) Connection:指定的连接类型为Keep-Alive。

注意:在传送一个网页时,Web服务器会同时打开多个TCP连接,如每一张图片都单独使用一个TCP连接进行传送。

超文本传输协议HTTP(二)

用于支持WWW浏览的网络协议为HTTP,这是一种最基本的客户机/服务器的访问协议。浏览器向服务器发送请求,而服务器回应相应的网页。HTTP协议从1990年开始出现,发展到当前的HTTP1.1标准,已经有了相当多的扩展,然而其最基本的实现是非常简单的,服务器需要进行的额外处理相当少,这也是为什么Web服务器软件如此众多的原因之一。
请求方法
通常,HTTP协议使用端口80来提供客户访问,因此也可以使用其他的网络软件,如telnet,模拟客户向服务器发送请求,来查看HTTP的传输方式。
$telnetwebserver80
Trying192.168.0.1...
Connectedtowebserver.
Escapecharacteris'^]'.
GET/index.html
  当telnet显示了Connect等信息建立了连接之后,服务器就等待使用者输入请求,而不进行任何提示。上例中,使用者输入GET/index.html指令,则服务器立即将相应的网页返回,然后关闭连接。
  客户程序向服务器发送的请求可以有不同的类型,这样服务器可以根据不同的请求类型进行不同的处理。在HTTP1.0中,定义了三种最基本的请求类型,GET、POST和HEAD,这些请求方法的实现方式均与上例相同,客户程序用大写指令将请求发送给服务器,后面跟随具体的数据。
GET请求最为常见,它后面跟随一个网页的位置,服务器接受请求并返回其请求的页面。除了页面位置作参数之外,请求还可以跟随协议的版本如HTTP/1.0等作为参数,以发送给服务器更多的信息。
POST请求要求服务器接收大量的信息,除了POST后面跟随的参数之外,浏览器还会在后面持续发送数据,让服务器进行处理。通常,POST方法是和CGI程序分不开的,服务器应该启动一个CGI程序来处理POST发送来的数据。
HEAD请求在客户程序和服务器之间进行交流,而不会返回具体的文档。当使用GET和POST方法时,服务器最后都将结果文档返回给客户程序,浏览器将刷新显示。而HEAD请求则不同,它仅仅交流一些内部数据,这些数据不会影响浏览的过程。因此HEAD方法通常不单独使用,而是和其他的请求方法一起起到辅助作用。一些搜寻引擎使用的自动搜索机器人使用这个方法来获得网页的标志信息,或者进行安全认证时,使用这个方法来传递认证信息。
  除了这三种最常见的访问方法之外,在HTTP1.1中还定义了更多的访问方法类型,如PUT,用于将网页放置到正确位置,DELETE用于删除相关文档等。这些方法并不常用,因而大部分Web服务器软件并没有实现他们。然而对于特定场合他们还是非常有用的,例如使用软件编辑网页时,网页编辑器可以使用这些方法,管理不同的网页。
  如果服务器不支持客户发送的请求方法,服务器将返回错误并立即关闭连接。

应用分析 - HTTP网页访问应用分析(3)

2) HTTP响应

Web服务器在收到HTTP请求后,会向HTTP客户端发送一个应答响应。

图6所示的是科来网络分析系统5.0对上面访问www.colasoft.com.cn的操作的HTTP响应报文跟踪。

clip_image010

(图6 HTTP响应)

图6中第8个数据包即是Web服务器返回给客户端的HTTP响应数据包,详细查看其解码,可以得到如下信息:

1) HTTP响应:“HTTP/1.1”表示的是URI(Uniform Resource Identifier,统一资源标识符)及其版本,“200 OK”是HTTP响应的状态码,表示客户端请示的页面存在,且状态正常。

2) Date:显示当前的时间。

3) Server:显示支持当前请求页面的Web服务器的类型。

4) X-Powered-By:显示当前请求页面的脚本类型。

5) Set-Cookie:显示此HTTP连接的Cookie信息。

6) Keep-Alive:显示此HTTP连接的Keep-Alive时间。

7) Connection:显示此HTTP连接的类型为Keep-Alive。

8) Transfer-Encoding:显示此HTTP连接的传输编码。

9) Content-Type:显示此HTTP连接的内容类型。

10) Line1-N:Web服务器传送给客户端浏览器的HTML代码。

在以太网中,数据包的大小在64-1518字节之间,如果客户端请求的页面大于1518字节,则会将请求的页面分段传送给客户端,客户端浏览器接收到Web服务器传送给自己的HTML代码后,便开始读取数据并将其显示为网页。

HTTP的不同状态码表示HTTP响应的不同类型,主要包括:

代码

描述

1xx

信息

2xx

成功

3xx

重定向

4xx

客户端错误

5xx

服务器端错误

(表2 HTTP响应状态码)

3) HTTP访问流程

通过上面对访问www.colasoft.com.cn的报文进行跟踪分析,我们可以总结出HTTP的工作流程图如图7所示。

注意:HTTP访问可以使用域名,也可直接使用IP地址,在使用IP进行访问时,将不会产生图5中第1和第2个所表示的DNS数据包,故此HTTP流程图里未包括DNS部分,而直接从TCP的三次握手开始。

clip_image012

(图7 HTTP访问流程图)

图7表示HTTP的访问流程如下:

客户端向服务器发送一个TCP连接的SYN请求(1),服务器在收到此请求后使用一个SYN/ACK的数据包对其进行响应(2),而客户端在收到此响应后再次向其发送一个ACK数据包进行确认(3),此时,TCP连接成功建立。在连接建立后,客户端立即使用请求方法(通常为GET或POST)向服务器请求数据(4),一般情况下这时服务器会向客户端回应其相应的HTTP报头和数据(5),但在某些情况下(脚本比较复杂,需耗费大量时间执行)开始的时候只能返回HTTP的报头,而数据(6、7、N)可能会在相隔一段时间后再单独地分组进行传输,当数据传输完后,客户端发送FIN数据包关闭连接。

对应图7中的标识,1-2的时间表示客户端和服务器之间路由所用的时间,4-5的时间为服务器的响应时间、5-N(此时5只返回了HTTP报头)所用的时间为服务器上脚本程序所用的时间。科来网络分析系统5.0中,对于每个数据包都可查看其绝对时间和相对时间(设定某个数据包为基准),在遇到访问网页速度慢的情况时,捕获HTTP的访问并查看相应的时间,即可确定访问速度慢的原因并排查故障。

附件:

1、建立连接协议(三次握手)
(1)客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的报文1。
(2) 服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN标志。因此它表示对刚才客户端SYN报文的回应;同时又标志SYN给客户端,询问客户端是否准备好进行数据通讯。
(3) 客户必须再次回应服务段一个ACK报文,这是报文段3。
2、连接终止协议(四次挥手)
  由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
 (1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送(报文段4)。
 (2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
 (3) 服务器关闭客户端的连接,发送一个FIN给客户端(报文段6)。
 (4) 客户段发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。
CLOSED: 这个没什么好说的了,表示初始状态。
LISTEN: 这个也是非常容易理解的一个状态,表示服务器端的某个SOCKET处于监听状态,可以接受连接了。
SYN_RCVD: 这个状态表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。
SYN_SENT: 这个状态与SYN_RCVD遥想呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
ESTABLISHED:这个容易理解了,表示连接已经建立了。
FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。
FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。
TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSING: 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。
LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。
最后有2个问题的回答,我自己分析后的结论(不一定保证100%正确)
1、 为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
2、 为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为:虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到 ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于 LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的 ACK报文。

三、总结

以上简单介绍了HTTP协议,并使用科来网络分析分析系统5.0跟踪分析了访问一个网页的具体流程。据此,用户在遇到网页访问故障时,即可结合上述的HTTP相关知识,使用网络检测分析软件(这儿是科来网络分析系统5.0)对HTTP访问的报文进行跟踪分析,以完成对此类故障的快速排查

什么是长连接、短连接?

2009-07-30 11:38

什么是长连接,什么是短连接?
贴个经典的,看完了就应该没啥问题了 :
TCP/IP通信程序设计的丰富多样性
刚接触TCP/IP通信设计的人根据范例可以很快编出一个通信程 序,据此一些人可能会认为TCP/IP编程很简单。其实不然, TCP/IP编程具有较为丰富的内容。其编程的丰富性主要体现在 通信方式和报文格式的多样性上。
一。通信方式
主要有以下三大类:
(一)SERVER/CLIENT方式
1.一个Client方连接一个Server方,或称点对点(peer to peer):
2.多个Client方连接一个Server方,这也是通常的并发服务器方式。
3.一个Client方连接多个Server方,这种方式很少见,主要
用于一个客户向多个服务器发送请求情况。
(二)连接方式
1.长连接
Client方与Server方先建立通讯连接,连接建立后不断开, 然后再进行报文发送和接收。这种方式下由于通讯连接一直 存在,可以用下面命令查看连接是否建立:
netstat –f inet|grep 端口号(如5678)。
此种方式常用于点对点通讯。
2.短连接
Client方与Server每进行一次报文收发交易时才进行通讯连 接,交易完毕后立即断开连接。此种方式常用于一点对多点 通讯,比如多个Client连接一个Server.
(三)发送接收方式
1.异步
报文发送和接收是分开的,相互独立的,互不影响。这种方 式又分两种情况:
(1)异步双工:接收和发送在同一个程序中,有两个不同的 子进程分别负责发送和接收
(2)异步单工:接收和发送是用两个不同的程序来完成。
2.同步
报文发送和接收是同步进行,既报文发送后等待接收返回报文。 同步方式一般需要考虑超时问题,即报文发上去后不能无限等 待,需要设定超时时间,超过该时间发送方不再等待读返回报 文,直接通知超时返回。
实际通信方式是这三类通信方式的组合。比如一般书上提供的 TCP/IP范例程序大都是同步短连接的SERVER/CLIENT程序。有的 组合是基本不用的,比较常用的有价值的组合是以下几种:
同步短连接Server/Client
同步长连接Server/Client
异步短连接Server/Client
异步长连接双工Server/Client
异步长连接单工Server/Client
其中异步长连接双工是最为复杂的一种通信方式,有时候经 常会出现在不同银行或不同城市之间的两套系统之间的通信。 比如金卡工程。由于这几种通信方式比较固定,所以可以预 先编制这几种通信方式的模板程序。
二.报文格式
通信报文格式多样性更多,相应地就必须设计对应的读写报文的接 收和发送报文函数。
(一)阻塞与非阻塞方式 
1.非阻塞方式
读函数不停地进行读动作,如果没有报文接收到,等待一段时间后 超时返回,这种情况一般需要指定超时时间。
2.阻塞方式
如果没有报文接收到,则读函数一直处于等待状态,直到有报文到达。
(二)循环读写方式
1.一次直接读写报文
在一次接收或发送报文动作中一次性不加分别地全部读取或全部发送报文字节。
2.不指定长度循环读写
这一般发生在短连接进程中,受网络路由等限制,一次较长的报文可能在网络传输过程中被分解成了好几个包。一次读取可能不能全部读完一次报文,这就需要循环读报文,直到读完为止。
3.带长度报文头循环读写
这种情况一般是在长连接进程中,由于在长连接中没有条件能够判断循环读写什么时候结束,所以必须要加长度报文头。读函数先是读取报文头的长度,再根据这个长度去读报文.实际情况中,报头的码制格式还经常不一样,如果是非ASCII码的报文头,还必须转换成ASCII,常见的报文头码制有:
(1)n个字节的ASCII码
(2)n个字节的BCD码
(3)n个字节的网络整型码
以上是几种比较典型的读写报文方式,可以与通信方式模板一起预先提供一些典型的API读写函数。当然在实际问题中,可能还必须编写与对方报文格式配套的读写API.

HTTP1.1状态代码及其含义

2009-08-24 16:59

100 Continue 初始的请求已经接受,客户应当继续发送请求的其余部分。(HTTP 1.1新)

101 Switching Protocols 服务器将遵从客户的请求转换到另外一种协议(HTTP 1.1新)

200 OK 一切正常,对GET和POST请求的应答文档跟在后面。

201 Created 服务器已经创建了文档,Location头给出了它的URL。

202 Accepted 已经接受请求,但处理尚未完成。

203 Non-Authoritative Information 文档已经正常地返回,但一些应答头可能不正确,因为使用的是文档的拷贝(HTTP 1.1新)。

204 No Content 没有新文档,浏览器应该继续显示原来的文档。如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的。

205 Reset Content 没有新的内容,但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容(HTTP 1.1新)。
206 Partial Content 客户发送了一个带有Range头的GET请求,服务器完成了它(HTTP 1.1新)。
300 Multiple Choices 客户请求的文档可以在多个位置找到,这些位置已经在返回的文档内列出。如果服务器要提出优先选择,则应该在Location应答头指明。
301 Moved Permanently 客户请求的文档在其他地方,新的URL在Location头中给出,浏览器应该自动地访问新的URL。

302 Found 类似于301,但新的URL应该被视为临时性的替代,而不是永久性的。注意,在HTTP1.0中对应的状态信息是“Moved Temporatily”。
出现该状态代码时,浏览器能够自动访问新的URL,因此它是一个很有用的状态代码。

注意这个状态代码有时候可以和301替换使用。例如,如果浏览器错误地请求http://host/~user(缺少了后面的斜杠),有的服务器返回301,有的则返回302。

严格地说,我们只能假定只有当原来的请求是GET时浏览器才会自动重定向。请参见307。
303 See Other 类似于301/302,不同之处在于,如果原来的请求是POST,Location头指定的重定向目标文档应该通过GET提取(HTTP 1.1新)。

304 Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。

305 Use Proxy 客户请求的文档应该通过Location头所指明的代理服务器提取(HTTP 1.1新)。

307 Temporary Redirect 和302(Found)相同。许多浏览器会错误地响应302应答进行重定向,即使原来的请求是POST,即使它实际上只能在POST请求的应答是303时才能重定向。由于这个原因,HTTP 1.1新增了307,以便更加清除地区分几个状态代码:当出现303应答时,浏览器可以跟随重定向的GET和POST请求;如果是307应答,则浏览器只能跟随对GET请求的重定向。(HTTP 1.1新)

400 Bad Request 请求出现语法错误。

401 Unauthorized 客户试图未经授权访问受密码保护的页面。应答中会包含一个WWW-Authenticate头,浏览器据此显示用户名字/密码对话框,然后在填写合适的Authorization头后再次发出请求。

403 Forbidden 资源不可用。服务器理解客户的请求,但拒绝处理它。通常由于服务器上文件或目录的权限设置导致。

404 Not Found 无法找到指定位置的资源。这也是一个常用的应答。

405 Method Not Allowed 请求方法(GET、POST、HEAD、Delete、PUT、TRACE等)对指定的资源不适用。(HTTP 1.1新)

406 Not Acceptable 指定的资源已经找到,但它的MIME类型和客户在Accpet头中所指定的不兼容(HTTP 1.1新)。

407 Proxy Authentication Required 类似于401,表示客户必须先经过代理服务器的授权。(HTTP 1.1新)

408 Request Timeout 在服务器许可的等待时间内,客户一直没有发出任何请求。客户可以在以后重复同一请求。(HTTP 1.1新)

409 Conflict 通常和PUT请求有关。由于请求和资源的当前状态相冲突,因此请求不能成功。(HTTP 1.1新)

410 Gone 所请求的文档已经不再可用,而且服务器不知道应该重定向到哪一个地址。它和404的不同在于,返回407表示文档永久地离开了指定的位置,而404表示由于未知的原因文档不可用。(HTTP 1.1新)

411 Length Required 服务器不能处理请求,除非客户发送一个Content-Length头。(HTTP 1.1新)

412 Precondition Failed 请求头中指定的一些前提条件失败(HTTP 1.1新)。

413 Request Entity Too Large 目标文档的大小超过服务器当前愿意处理的大小。如果服务器认为自己能够稍后再处理该请求,则应该提供一个Retry-After头(HTTP 1.1新)。

414 Request URI Too Long URI太长(HTTP 1.1新)。

416 Requested Range Not Satisfiable 服务器不能满足客户在请求中指定的Range头。(HTTP 1.1新)

500 Internal Server Error 服务器遇到了意料不到的情况,不能完成客户的请求。

501 Not Implemented 服务器不支持实现请求所需要的功能。例如,客户发出了一个服务器不支持的PUT请求。

502 Bad Gateway 服务器作为网关或者代理时,为了完成请求访问下一个服务器,但该服务器返回了非法的应答。

503 Service Unavailable 服务器由于维护或者负载过重未能应答。例如,Servlet可能在数据库连接池已满的情况下返回503。服务器返回503时可以提供一个Retry-After头。

504 Gateway Timeout 由作为代理或网关的服务器使用,表示不能及时地从远程服务器获得应答。(HTTP 1.1新)

505 HTTP Version Not Supported 服务器不支持请求中所指明的HTTP版本。(HTTP 1.1新)

HTTP状态列表

响应码由三位十进制数字组成,它们出现在由HTTP服务器发送的响应的第一行。
响应码分五种类型,由它们的第一位数字表示:
1xx:信息,请求收到,继续处理
2xx:成功,行为被成功地接受、理解和采纳
3xx:重定向,为了完成请求,必须进一步执行的动作
4xx:客户端错误,请求包含语法错误或者请求无法实现
5xx:服务器错误,服务器不能实现一种明显无效的请求
下表显示每个响应码及其含义:
100 => “HTTP/1.1 100 Continue” //继续
101 => “HTTP/1.1 101 Switching Protocols” //分组交换协议
200 => “HTTP/1.1 200 OK” //OK
201 => “HTTP/1.1 201 Created” //被创建
202 => “HTTP/1.1 202 Accepted” //被采纳
203 => “HTTP/1.1 203 Non-Authoritative Information” //非授权信息
204 => “HTTP/1.1 204 No Content” //无内容
205 => “HTTP/1.1 205 Reset Content” //重置内容
206 => “HTTP/1.1 206 Partial Content” //部分内容
300 => “HTTP/1.1 300 Multiple Choices” //多选项
301 => “HTTP/1.1 301 Moved Permanently” //永久地传送
302 => “HTTP/1.1 302 Found” //找到
303 => “HTTP/1.1 303 See Other” //参见其他
304 => “HTTP/1.1 304 Not Modified” //未改动
305 => “HTTP/1.1 305 Use Proxy” //使用代理
307 => “HTTP/1.1 307 Temporary Redirect” //暂时重定向
400 => “HTTP/1.1 400 Bad Request” //错误请求
401 => “HTTP/1.1 401 Unauthorized” //未授权
402 => “HTTP/1.1 402 Payment Required” //要求付费
403 => “HTTP/1.1 403 Forbidden” //禁止
404 => “HTTP/1.1 404 Not Found” //未找到
405 => “HTTP/1.1 405 Method Not Allowed” //不允许的方法
406 => “HTTP/1.1 406 Not Acceptable” //不被采纳
407 => “HTTP/1.1 407 Proxy Authentication Required” //要求代理授权
408 => “HTTP/1.1 408 Request Time-out” //请求超时
409 => “HTTP/1.1 409 Conflict” //冲突
410 => “HTTP/1.1 410 Gone” //过期的
411 => “HTTP/1.1 411 Length Required” //要求的长度
412 => “HTTP/1.1 412 Precondition Failed” //前提不成立
413 => “HTTP/1.1 413 Request Entity Too Large” //请求实例太大
414 => “HTTP/1.1 414 Request-URI Too Large” //请求URI太大
415 => “HTTP/1.1 415 Unsupported Media Type” //不支持的媒体类型
416 => “HTTP/1.1 416 Requested range not satisfiable” //无法满足的请求范围
417 => “HTTP/1.1 417 Expectation Failed” //失败的预期
500 => “HTTP/1.1 500 Internal Server Error” //内部服务器错误
501 => “HTTP/1.1 501 Not Implemented” //未被使用
502 => “HTTP/1.1 502 Bad Gateway” //网关错误
503 => “HTTP/1.1 503 Service Unavailable” //不可用的服务
504 => “HTTP/1.1 504 Gateway Time-out” //网关超时
505 => “HTTP/1.1 505″ //HTTP版本未被支持

HTTP协议的运作方式

HTTP协议是基于请求/响应范式的。一个客户机与服务器建立连接后,发送一个请求给服务器,请求方式的格式为,统一资源标识符、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容。服务器接到请求后,给予相应的响应信息,其格式为一个状态行包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。
  许多HTTP通讯是由一个用户代理初始化的并且包括一个申请在源服务器上资源的请求。最简单的情况可能是在用户代理(UA)和源服务器(O)之间通过一个单独的连接来完成(见图2-1)。
图2-1

clip_image013
  当一个或多个中介出现在请求/响应链中时,情况就变得复杂一些。中介由三种:代理(Proxy)、网关(Gateway)和通道(Tunnel)。一个代理根据URI的绝对格式来接受请求,重写全部或部分消息,通过URI的标识把已格式化过的请求发送到服务器。网关是一个接收代理,作为一些其它服务器的上层,并且如果必须的话,可以把请求翻译给下层的服务器协议。一个通道作为不改变消息的两个连接之间的中继点。当通讯需要通过一个中介(例如:防火墙等)或者是中介不能识别消息的内容时,通道经常被使用。图2-2> clip_image014
  上面的图2-2表明了在用户代理(UA)和源服务器(O)之间有三个中介(A,B和C)。一个通过整个链的请求或响应消息必须经过四个连接段。这个区别是重要的,因为一些HTTP通讯选择可能应用于最近的连接、没有通道的邻居,应用于链的终点或应用于沿链的所有连接。尽管图2-2是线性的,每个参与者都可能从事多重的、并发的通讯。例如,B可能从许多客户机接收请求而不通过A,并且/或者不通过C把请求送到A,在同时它还可能处理A的请求。
  任何针对不作为通道的汇聚可能为处理请求启用一个内部缓存。缓存的效果是请求/响应链被缩短,条件是沿链的参与者之一具有一个缓存的响应作用于那个请求。下图说明结果链,其条件是针对一个未被UA或A加缓存的请求,B有一个经过C来自O的一个前期响应的缓存拷贝。
图2-3 clip_image015

在Internet上,HTTP通讯通常发生在TCP/IP连接之上。缺省端口是TCP80,但其它的端口也是可用的。但这并不预示着HTTP协议在Internet或其它网络的其它协议之上才能完成。HTTP只预示着一个可靠的传输。
  以上简要介绍了HTTP协议的宏观运作方式,下面介绍一下HTTP协议的内部操作过程。
  首先,简单介绍基于HTTP协议的客户/服务器模式的信息交换过程,如图2-4所示,它分四个过程,建立连接、发送请求信息、发送响应信息、关闭连接。
图2-4 clip_image016
  在WWW中,“客户”与“服务器”是一个相对的概念,只存在于一个特定的连接期间,即在某个连接中的客户在另一个连接中可能作为服务器。WWW服务器运行时,一直在TCP80端口(WWW的缺省端口)监听,等待连接的出现。
  下面,讨论HTTP协议下客户/服务器模式中信息交换的实现。  1.建立连接  连接的建立是通过申请套接字(Socket)实现的。客户打开一个套接字并把它约束在一个端口上,如果成功,就相当于建立了一个虚拟文件。以后就可以在该虚拟文件上写数据并通过网络向外传送。
2.发送请求
  打开一个连接后,客户机把请求消息送到服务器的停留端口上,完成提出请求动作。
HTTP/1.0  请求消息的格式为:
  请求消息=请求行(通用信息|请求头|实体头)CRLF[实体内容]
  请求 行=方法 请求URL HTTP版本号 CRLF
  方  法=GET|HEAD|POST|扩展方法
U R L=协议名称+宿主名+目录与文件名
  请求行中的方法描述指定资源中应该执行的动作,常用的方法有GET、HEAD和POST。不同的请求对象对应GET的结果是不同的,对应关系如下:
  对象      GET的结果
  文件      文件的内容
  程序      该程序的执行结果
  数据库查询   查询结果
HEAD——要求服务器查找某对象的元信息,而不是对象本身。
POST——从客户机向服务器传送数据,在要求服务器和CGI做进一步处理时会用到POST方法。POST主要用于发送HTML文本中FORM的内容,让CGI程序处理。
  一个请求的例子为:
GEThttp://networking.zju.edu.cn/zju/index.htmHTTP/1.0
  头信息又称为元信息,即信息的信息,利用元信息可以实现有条件的请求或应答。
  请求头——告诉服务器怎样解释本次请求,主要包括用户可以接受的数据类型、压缩方法和语言等。
  实体头——实体信息类型、长度、压缩方法、最后一次修改时间、数据有效期等。
  实体——请求或应答对象本身。
3.发送响应
  服务器在处理完客户的请求之后,要向客户机发送响应消息。
HTTP/1.0的响应消息格式如下:

  响应消息=状态行(通用信息头|响应头|实体头) CRLF 〔实体内容〕
  状态行=HTTP版本号 状态码 原因叙述
  状态码表示响应类型
1××  保留
2××  表示请求成功地接收
3××  为完成请求客户需进一步细化请求
4××  客户错误
5××  服务器错误
  响应头的信息包括:服务程序名,通知客户请求的URL需要认证,请求的资源何时能使用。
4.关闭连接
  客户和服务器双方都可以通过关闭套接字来结束TCP/IP对话

HTTPWWW的配置注意事项

 

一、HTTP的安全因素
对于HTPP要关注的两个基本安全情况之一是,一个恶意的客户能对HTTP服务器做些什么。在大多数情况
下,我们对HTTP服务器安全性的考虑同我们对其他服务器如匿名FTP服务器处理来自因特网的连接的安全性考虑
是一样的。你要确保用户的这些连接只能访问到你提供给他们访问的信息,并且不能让他们欺骗你的服务器来
获得他们不应得到的信息。
有许多种方法完成这个目标,包括:
1)仔细配置你的服务器中的安全及访问控制功能,来限制哪些用户可以访问服务器及他们能访问的区域。
2)以一个非特权用户来运行服务器。
3)使用CHROOT机制来限制服务器操作是在你的文件系统中的一个特定区域中,你以在服务器内或通过一
个外部交换程序来使用CHROOT。
4)不要将要保密信息放在服务器的机器上,这样的话,即使有人入侵到你的服务器上,由于那里没有他们
所感兴趣的东西,至少是没有他们无法从正常渠道得到的信息。5)对于你的网络中蓁机器进行安全配置以便
即使有人入侵的话,也只限于是该服务器这台机器,他们也难以进一步从你的网络中得到更多的信息。要这样
做的话首先必须不能将服务器运行在内部网络中。
HTTP服务器本身只提供有限的服务,没有许多要关心的安全问题。但在HTTP服务器你要担心的唯一功能
是:它能使用外部程序,特别是能通过CGI(公共网关接口COMMONGATEWAYINTERFACE)与用户交互,CGI是
HTTP提供用户信息如何与服务器连接并通过它传递给外部程序的一个功能。许多HTTP服务器配置成自动运行外
部程序来生成HTML页面。这些程序通常称为CGI程序,甚至它们所使用的CGI并不是程序。如果有人向HTTP服务
器发出一个数据查询要求,HTTP服务器执行一个外部程序来执行这个查询要求并生成HTML页面来作为回答。
有两个理由需要对外部程序的安全性担心的:
1)入侵者能不能欺骗外部程序去做一些它们不应做的事?
2)入侵者能不能上载他们自己的外部程序并执行它们?
你可能要在MACINTOSH、DOS和WINDOWS机器上HTTP服务器,这些机器有好的HTTP服务器但通常没有其他的
功能如保密。由于它们不能运行其他服务,没有强有力的外部程序能力。它们越简单,处理安全的能力就越
弱。

利用HTTP协议的特性进行拒绝服务攻击的一些构思

在介绍这个方法之前,让我们复习一下HTTP是怎样工作的:
  由于HTTP协议是基于请求/响应范式的(相当于客户机/服务器)。一个客户机与服务器建立连接后,发送一个请求给服务器,请求方式的格式为:统一资源标识符(URL)、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容。服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。
留意这行文字:“服务器接到请求后,给予相应的响应信息,其格式为一个状态行”,这是HTTP协议的一个重要特性,我们可以做个实验:
TELNET或任何一个能建立HTTP连接的程序连接到某服务器的80端口,手工输入:
GET/index.htmHTTP/1.0(必须确保这个工具能自动产生两个换行符,否则服务器会认为你没有输入完全!)
如果index.htm存在,你会看到类似以下的报文:
HTTP/1.1200OK
Server:Microsoft-IIS/5.0
Content-Location:http://www.******.com/index.htm
Date:Sat,20Jul200223:32:03GMT
Content-Type:text/html
Accept-Ranges:bytes
Last-Modified:Wed,03Jul200209:50:05GMT
ETag:"8e2ba27722c21:850"
Content-Length:3292
<html>
<head>
<title>青涩宝贝主题站--SGfans的世界!!!</title>
<metahttp-equiv="Content-Type"content="text/html;charset=gb2312">
<linkrel="stylesheet"href="all.css"type="text/css">
.......
这是正常的访问方法,但是如果我们胡乱输入请求呢?看:
HTTP/1.1400BadRequest
Server:Microsoft-IIS/5.0
Date:Sat,20Jul200223:37:59GMT
Content-Type:text/html
Content-Length:87
<html><head><title>Error</title></head><body>Theparameterisincorrect.</body></html>
呵呵,HTTP400-错误请求,其实就是HTTP语法错误,服务器老老实实给我们返回了。
可以得出结论:无论你输入了什么,服务器根据HTTP协议,总会返回信息
通常用得最多的DOS方法主要有SYN、Smurf、Land、TearDrop等,其中SYN的资料如下(抄来的资料~~呵呵)假设一个用户向服务器发送了SYN报文后突然死机或掉线,那么服务器在发出SYN+ACK应答报文后是无法收到客户端的ACK报文的(第三次握手无法完成),这种情况下服务器端一般会重试(再次发送SYN+ACK给客户端)并等待一段时间后丢弃这个未完成的连接,这段时间的长度我们称为SYNTimeout,一般来说这个时间是分钟的数量级(大约为30秒-2分钟);一个用户出现异常导致服务器的一个线程等待1分钟并不是什么很大的问题,但如果有一个恶意的攻击者大量模拟这种情况,服务器端将为了维护一个非常大的半连接列表而消耗非常多的资源----数以万计的半连接,即使是简单的保存并遍历也会消耗非常多的CPU时间和内存,何况还要不断对这个列表中的IP进行SYN+ACK的重试。实际上如果服务器的TCP/IP栈不够强大,最后的结果往往是堆栈溢出崩溃---即使服务器端的系统足够强大,服务器端也将忙于处理攻击者伪造的TCP连接请求而无暇理睬客户的正常请求(毕竟客户端的正常请求比率非常之小),此时从正常客户的角度看来,服务器失去响应,这种情况我们称作:服务器端受到了SYNFlood攻击(SYN洪水攻击)。
而Smurf、TearDrop等是利用ICMP报文来Flood和IP碎片攻击的。
但是以上的DOS方法的目的无非都是让服务器大量消耗资源和超时连接,那么除了超时连接,能不能用“正常连接”的方法来产生拒绝服务攻击呢?
再来看看19端口的定义:
字符产生器协议(CharacterGeneratorProtocol)
字符产生器服务器一个有用的调试工具。无论接收到的是什么,它都返回特定的数据。
基于TCP的字符产生器服务
此服务可以是一个基于TCP的服务,TCP端口19是用于此服务的。一旦连接建立,服务器会传送一个字符流。接收到的信息会被抛弃。字符流会在用户请求下中止。用户可能会非正常中止一个连接,因此此服务必须准备处理这种情况。传输的速度会由TCP流控制机制负责,用户不必关心数据太快,而用户来不及处理。
19端口在早期已经有人用来做Chargen攻击了,即Chargen_Denial_of_Service,但是!他们用的方法是在两台Chargen服务器之间产生UDP连接,让服务器处理过多信息而DOWN掉,那么,干掉一台WEB服务器的条件就必须有2个:1.有Chargen服务2.有HTTP服务
实际上,现在是无法找到这么多同时开放两个这两个服务的服务器的。
看看开头的HTTP协议特性,和Chargen比较一下,你发现了什么?哈哈~~~没错!一个是无论接收到什么报文都会回应,一个是一旦连接建立就会发送报文,看看示意图:
发送请求------------------------------------->
客户端----------------------------------------------------------服务器
<-------------------------------------------回应
如果把客户端改为Chargen,就是以下情况:
字符流--------------------------------------->
Chargen----------------------------------------------------------服务器
<-----------------------------------400BadRequest
也就是说,这两者会产生利害冲突,可以这样比喻:两个人吵架,你骂一句,他还一句,这就是循环过程,除非有一方停止或第三者干涉,否则这将是个Do...Loop循环!
搬到HTTP和Chargen来说,就是因为这两者的特性正好天生一对,那好,就从这里下手:
攻击者伪造源IP给N台Chargen发送连接请求(Connect),如有必要还可以发送(Send)个“Fuckyou”报文过去,Chargen接收到连接后就会返回每秒72字节的字符流(实际上根据网络实际情况,这个速度更快)给服务器,HTTP协议处理这个报文时当然会识别为400BadRequest而返回一条错误说明给它,接下来的情况嘛………………自己发挥想像力,别问我。
我用自己的机器(640kbpsADSL)+VBWinsock程序做测试,只用了3秒钟,程序就因为内存溢出而崩溃了主要是TextBox的问题,它接受文本的最大范围为64KB),就是说,3秒钟内Chargen就发送了大于64KB的字符流!如果用大于10台的Chargen一起发起攻击,其速度足以大量消耗服务器资源和带

5.2.2 实例——基于vC++的HTTP蓉户端程序

应用层位于TCP/IP协议栈的最上层,它是网络应用程序及其应用层协议保存的地方。应用层向使用网络的用户提供特定的、常用的应用程序,如使用最广泛的远程登录协议Telnet、文件传输协议FTP、超文本传输协议HTTP、域名系统DNS、简单网络管理协议SNMP、P2p文件共享系统和简单邮件传输协议SMTP等.网络应用程序3种主要体系结构:客户机/服务器体系结构,P2P体系结构、客户机/服务器和P2P混合的体系结构.

有些应用层协议是基于TCP协议的,如FTP和HTTP,有些应用层协议是基于UDP协议的,如SMTP等。应用层协议定义了运行在不同端系统上的应用程序进程是如何相互传递报文的,特别是应用层协议定义了:

(1)交换的报文类型,如请求报文和相应报文。

(2)各种报文类型的语法,如报文的各个字段及其详细描述.

(3)字段的语义,即包含在字段中的信息的含义

(4)进程何时、如何发送报文及对报文进行响应.

    HTTP协议即超文本传输协议(HyperText Transfer Protocol),它是一个面向无连接的简单快速C/S结构的协议,HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。

    HTTP协议是基于请求/响应范式的。一个客户机与服务器建立连接后,发送一个请求给服务器,请求方式的格式为统一资源标识符、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容。服务器接到请求后,给予相应的响应消息,其格式为一个状态行包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息,实体信息和可能的内容。

    HTTP协议的内部操作过程分为4个过程,建立连接、发送请求信息、发送响应消息、关闭连接,在WWW中,"客户"与"服务器"是一个相对的概念,只存在于一个特定的连接期间,即在某个连接中的客户在另一个连接中可能作为服务器。WWW服务器运行时,一般在TCP80端口(WWW的默认端口)监听,等待连接的出现,下面,讨论HTTP协议下客户/ 服务器模式中信息交换的实现.

(1)建立连接:连接的建立是通过申请套接字(Socket)实现的。客户打开一个套接字并把它绑定在一个端口上,这样就可以建立客户机与服务器的TCP连接

(2)发送请求:客户机与服务器建立连接后,客户机把请求信息送服务器的监听端口上,消息中含有资源在服务器上的位置,完成提出请求动作

(3)发送响应:服务器在处理完客户的请求之后,要向客户机发送响应消息,消息中含有返回状态码,表示请求是否完成;包含相应消息标题和请求的对象实体,如一个HTTP文件或者一个图片

(4)关闭连接:一旦响应消息发出,服务器将关闭TCP/IP 连接,然后结束HTTP会话,客户和服务器双方都可以通过关闭套接字来结束TCP/IP对话

客户端HTTP请求的方法:GET,HEAD,POST,PUT,DELETE,OPTIONS,TRAC

该实例直接调用类库中的CHtmlView类,CHtmlView类在文档/视图结构的上下文中提供WebBrowser控件的功能。WebBrowser控件是客户可浏览网址以及本地文件系统和网络文件夹的窗口。WebBrowser控件支持超级链接、统一资源定位符(URL)导航器比维护一张历史表.,所以在创建程序的时候MFCAppWizard,并将CHtmlView 指定为视图类.

下面是用到的函数

//刷新当前的页面

void CMywebView::OnRefresh()

{

// TODO: Add your command handler code here

Refresh();

}

//前进到下一个网页

void CMywebView::OnForward()

{

// TODO: Add your command handler code here

GoForward();

}

//停止网页的下载

void CMywebView::OnStop()

{

// TODO: Add your command handler code here

Stop();

}

//进行搜索

void CMywebView::OnSearch()

{

// TODO: Add your command handler code here

OnSearch();

}

//后退到前一个网页

void CMywebView::OnBack()

{

// TODO: Add your command handler code here

GoBack();

}

//回到主页

void CMywebView::OnHamepage()

{

// TODO: Add your command handler code here

GoHome();

}

void CMainFrame::OnGO()//该函数实现按回车键时,自动跳动指定的网页

{

CString addr;

//获得用户在地址栏输入的地址

m_wndDlgBar.GetDlgItem(IDC_ADDRESS)->GetWindowText(addr);

//浏览指定的网页

((CMywebView*)GetActiveView())->Navigate(addr);

}

clip_image018

5.3 Email协议及电子邮件

http://binyanye.blog.163.com/blog/static/1753360812010112514120844/

Email协议及电子邮件

    SMTP是简单邮件传输协议,是Internet的正式标准,最初在1982年由RFC821规定,目前它的最高版本是RFC2821.目标是向用户提供高效、可靠的邮件传输。SMTP采用C/S模式,专用电子邮件的发送,规定了发信人把电子邮件发送到收信人的电子邮箱的全过程,SMTP客户机与SMTP服务器两者在通信过程中如何交换信息。

      SMTP主要工作在两种情况下:一是电子邮件从客户机传输到服务器;二是从某一个服务器传输到令一个服务器。

      POP3协议是邮局协议版本3的缩写,从邮件传输过程可知,发送给某用户的邮件通过SMTP的传输,最终被保存于用户所在的电子邮件服务器上的邮箱里。一般情况下用户无法在邮件服务器上直接阅读邮件,用户需要把存储于邮件服务器上的邮件取到本地主机后再阅读。POP3就是一个从邮件服务器的邮箱中取邮件到本地主机的协议。

工作时,服务器在提供POP3服务的TCP端口110上侦听客户的请求,当客户主机需要从服务器上取邮件时,它向服务器发出建立一条TCP连接的请求。在连接成功后,客户与服务器之间使用POP3协议会话的过程分为3个阶段(1)认证阶段,每一个用户只有提供了正确的用户名和口令后,才有权访问自己的邮箱,在这个阶段里,可以使用USER、PASS和QUIT这3个POP3命令(2)邮件操作阶段:检索、下载或者删除邮件等操作(3)更新阶段:当客户发送了QUIT命令后,系统就进入更新阶段,POP3服务器释放在操作阶段中取得的资源,并将逻辑删除的邮件进行物理删除,然后发送消息,关闭客户与服务器之间的TCP连接,邮件处理的会话过程结束

实例:Email接收程序--POP3邮件接收端:

关键代码:

()

bool WSocket::Create(int af, int type, int protocol)//创建套接字

{

m_sock=socket(af,type,protocol);

if(m_sock==INVALID_SOCKET)

{

return false;

}

return true;

}

bool WSocket::Connect(const char *ip, unsigned short port)//与服务器连接

{

struct sockaddr_in svraddr;

svraddr.sin_family=AF_INET;

svraddr.sin_addr.s_addr=inet_addr(ip);

svraddr.sin_port=htons(port);

int ret=connect(m_sock,(struct sockaddr*)&svraddr,sizeof(svraddr));

if(ret==SOCKET_ERROR)

{

return false;

}

return true;

}

int WSocket::Send(const char *buf, int len, int flags)//套接字发送数据

{

int bytes;

int count=0;

while(count<len)

{

bytes=send(m_sock,buf+count,len-count,flags);

if(bytes==-1||bytes==0)

return -1;

count+=bytes;

}

return count;

}

int WSocket::Recv(char *buf, int len, int flags)//套接字接收数据

{

return (recv(m_sock,buf,len,flags));

}

int WSocket::Close()//关闭套接字

{

#ifdef WIN32

return (closesocket(m_sock));

#else

return (close(m_sock));

#endif

}

int WSocket::Init()//套接字初始化

{

#ifdef WIN32

WSADATA wsaData;

WORD version=MAKEWORD(2,0);

int ret=WSAStartup(version,&wsaData);//加载套接字

if(ret)

{

cerr<<"Initilize winsock error!"<<endl;

return -1;

}

#endif

return 0;

}

int WSocket::Clean()//清除套接字

{

#ifdef WIN32

return (WSACleanup());

#endif

return 0;

}

bool WSocket::DnsParse(const char* domain,char* ip)//服务器域名解析

{

struct hostent* p;

if((p=gethostbyname(domain))==NULL)

return false;

//如果是域名则转换成IP地址

sprintf(ip,

"%u.%u.%u.%u",

(unsigned char)p->h_addr_list[0][0],

(unsigned char)p->h_addr_list[0][1],

(unsigned char)p->h_addr_list[0][2],

(unsigned char)p->h_addr_list[0][3]);

return true;

}

()

//POP3接收服务器消息函数

int CPop3::Pop3Recv(char* buf, int len, int flags)

{

int rs;

int offset = 0;

do

{

if ( offset > len - 2 )

return offset;

rs = m_sock.Recv(buf + offset, len - offset, flags);

if ( rs < 0 ) /* error occur */

return -1;

offset += rs;

buf[offset] = '\0';

} while ( strstr(buf, "\r\n.\r\n") == (char*)NULL );

return offset;

}

//初始化POP3,

bool CPop3::Init(const char* username, const char* userpwd, const char* svraddr, unsigned short port)

{

//给用户名、密码和服务器地址赋值

strcpy(m_username, username);

strcpy(m_password, userpwd);

strcpy(m_svraddr, svraddr);

m_port = port;

return true;

}

//与邮件服务器建立连接

bool CPop3::Connect()

{

// 创建套接字

m_sock.Create(AF_INET, SOCK_STREAM, 0);

// 解析域名

char ipaddr[16];

if ( WSocket::DnsParse(m_svraddr, ipaddr) != true )

{

return false;

}

// 发送连接

if ( m_sock.Connect(ipaddr, m_port) != true )

{

return false;

}

// 接收服务器消息

char buf[128];

int rs = m_sock.Recv(buf, sizeof(buf), 0);

if ( rs <= 0 || strncmp(buf, "+OK", 3) != 0 )

{

return false;

}

#ifdef _DEBUG

buf[rs] = '\0';

printf("Recv POP3 Resp: %s", buf);

#endif

return true;

}

//向邮件服务器发送用户名和密码登录邮件服务器

bool CPop3::Login()

{

//发送USER命令,向服务器发送用户名

char sendbuf[128];

char recvbuf[128];

sprintf(sendbuf, "USER %s\r\n", m_username);

m_sock.Send(sendbuf, strlen(sendbuf), 0);

int rs = m_sock.Recv(recvbuf, sizeof(recvbuf), 0);

if ( rs <= 0 || strncmp(recvbuf, "+OK", 3) != 0 )

{

return false;

}

#ifdef _DEBUG

recvbuf[rs] = '\0';

printf("Recv USER Resp: %s", recvbuf);

#endif

//发送PASS命令,向服务器发送密码

sprintf(sendbuf, "PASS %s\r\n", m_password);

m_sock.Send(sendbuf, strlen(sendbuf), 0);

rs = m_sock.Recv(recvbuf, sizeof(recvbuf), 0);

if ( rs <= 0 || strncmp(recvbuf, "+OK", 3) != 0 )

{

return false;

}

#ifdef _DEBUG

recvbuf[rs] = '\0';

printf("Recv PASS Resp: %s", recvbuf);

#endif

return true;

}

//列出邮件主要信息

bool CPop3::List(int& sum)

{

//发送LIST命令,获得邮件信息

char sendbuf[128];

char recvbuf[256];

sprintf(sendbuf, "LIST \r\n");

m_sock.Send(sendbuf, strlen(sendbuf), 0);

int rs = Pop3Recv(recvbuf, sizeof(recvbuf), 0);

if ( rs <= 0 || strncmp(recvbuf, "+OK", 3) != 0 )

{

return false;

}

recvbuf[rs] = '\0';

#ifdef _DEBUG

printf("Recv LIST Resp: %s", recvbuf);

#endif

sum = GetMailSum(recvbuf);

return true;

}

//获取邮件并保存邮件

bool CPop3::Retrieve(int num)

{

int rs;

FILE* fp;

int flag = 0;

unsigned int len;

char filename[32];

char sendbuf[128];

char recvbuf[10240];

//发送RETR命令,获取邮件内容

sprintf(sendbuf, "RETR %d\r\n", num);

m_sock.Send(sendbuf, strlen(sendbuf), 0);

do {

rs = Pop3Recv(recvbuf, sizeof(recvbuf), 0);

if ( rs < 0 ) {

return false;

}

recvbuf[rs] = '\0';

// 获得邮件主题,并生成保存邮件的文件名

if ( flag == 0 )

{

GetSubject((char *)(LPCTSTR)Subject,recvbuf);

GetSubject(filename,recvbuf);

strcat(filename, ".eml");

flag = 1;

if ( (fp = fopen(filename, "wb")) == NULL )

return false;

}

#ifdef _DEBUG

printf("Recv RETR Resp: %s", recvbuf);

#endif

//获得邮件大小

len = strlen(recvbuf);

//保存邮件

if ( fwrite(recvbuf, 1, len, fp) != len ) {

fclose(fp);

return false;

}

fflush(fp);

} while ( strstr(recvbuf, "\r\n.\r\n") == (char*)NULL );

fclose(fp);

return true;

}

//与邮件服务器断开

bool CPop3::Quit()

{

char sendbuf[128];

char recvbuf[128];

//发送QUIT命令,断开与邮件服务器的连接

sprintf(sendbuf, "QUIT\r\n");

m_sock.Send(sendbuf, strlen(sendbuf), 0);

int rs = m_sock.Recv(recvbuf, sizeof(recvbuf), 0);

if ( rs <= 0 || strncmp(recvbuf, "+OK", 3) != 0 )

{

return false;

}

#ifdef _DEBUG

recvbuf[rs] = '\0';

printf("Recv QUIT Resp: %s", recvbuf);

#endif

//关闭套接字

m_sock.Close();

return true;

}

//获得邮件主题

bool CPop3::GetSubject(char* subject, const char* buf)

{

char* p = strstr(buf, "Subject: ");

if ( p == NULL )

return false;

p = p + 9;

for (int i = 0; i < 32; i++) {

if ( p[i] == '\r' || p[i] == '\n' ) {

subject[i] = '\0';

break;

}

subject[i] = p[i];

}

return true;

}

//获得邮箱邮件数量

int CPop3::GetMailSum(const char* buf)

{

int sum = 0;

char* p = strstr(buf, "\r\n");

if ( p == NULL )

return sum;

p = strstr(p + 2, "\r\n");

if ( p == NULL )

return sum;

while ( (p = strstr(p + 2, "\r\n")) != NULL )

{

sum++;

}

return sum;

}

clip_image019

5.3.1 SMTP协议

1 SMTPSimple Mail Transfer Protocal)称为简单邮件传输协议,目标是向用户提供高效、可靠的邮件传输。

SMTP的一个重要特点是它能够在传送中接力传送邮件,即邮件可以通过不同网络上的主机接力式传送。
工作在两种情况下:

一是电子邮件从客户机传输到服务器;

二是从某一个服务器传输到另一个服务器。

SMTP是个请求/响应协议,它监听25号端口,用于接收用户的邮件请求,并与远端邮件服务器建立SMTP连接。

 
SMTP工作机制 

SMTP通常有两种工作模式:发送SMTP和接收SMTP。

具体工作方式为:

发送SMTP在接到用户的邮件请求后,判断此邮件是否为本地邮件,
若是直接投送到用户的邮箱,否则向DNS查询远端邮件服务器的MX纪录,并建立与远端接收SMTP之间的一个双向传送通道,此后SMTP命令由发送SMTP发出,由接收SMTP接收,而应答则反方面传送。
一旦传送通道建立,SMTP发送者发送MAIL命令指明邮件发送者。
如果SMTP接收者可以接收邮件则返回OK应答。
SMTP发送者再发出RCPT命令确认邮件是否接收到。
如果SMTP接收者接收,则返回OK应答;
如果不能接收到,则发出拒绝接收应答(但不中止整个邮件操作),双方将如此重复多次。
当接收者收到全部邮件后会接收到特别的序列,如果接收者成功处理了邮件,则返回OK应答。 
2.POP协议简介 
POP的全称是 Post Office Protocol ,即邮局协议,用于电子邮件的接收,它使用TCP的110端口,现在常用的是第三版,所以简称为 POP3。 
 
POP3采用Client/Server工作模式。
当客户机需要服务时,客户端的软件(Outlook Express或FoxMail)将与POP3服务器建立TCP连接,此后要经过POP3协议的三种工作状态,
1、首先是认证过程,确认客户机提供的用户名和密码,
在认证通过后便转入处理状态,在此状态下用户可收取自己的邮件或做邮件
2、的删除,在完成响应的操作后客户机便发出quit命令,
3、此后便进入更新状态,将做删除标记的邮件从服务器端删除掉。到此为止整个POP过程完成。 
 
3.IMAP协议简介
 IMAP是Internet Message Access Protocol的缩写,顾名思义,主要提供的是通过Internet获取信息的一种协议。
IMAP像POP那样提供了方便的邮件下载服务,让用户能进行离线阅读,但IMAP能完成的却远远不只这些。IMAP提供的摘要浏览功能可以让你在阅读完所有的邮件到达时间、主题、发件人、大小等信息后才作出是否下载的决定。

邮件协议SMTP POP3 IMAP介绍及常用邮件服务器地址

邮件发送协议SMTP协议简介
1. 介绍
SMTP称为简单Mail传输协议(Simple Mail Transfer Protocal),目标是向用户提供高效、可靠的邮件传输。SMTP的一个重要特点是它能够在传送中接力传送邮件,即邮件可以通过不同网络上的主机接力式传送。工作在两种情况下:一是电子邮件从客户机传输到服务器;二是从某一个服务器传输到另一个服务器。 SMTP是个请求/响应协议,它监听25号端口,用于接收用户的Mail请求,并与远端Mail服务器建立SMTP连接。

2. Smtp工作机制
SMTP通常有两种工作模式:发送SMTP和接收SMTP。具体工作方式为:发送SMTP在接到用户的邮件请求后,判断此邮件是否为本地邮件,若是直接投送到用户的邮箱,否则向dns查询远端邮件服务器的MX纪录,并建立与远端接收SMTP之间的一个双向传送通道,此后SMTP命令由发送SMTP发出,由接收SMTP接收,而应答则反方面传送。一旦传送通道建立,SMTP发送者发送MAIL命令指明邮件发送者。如果SMTP接收者可以接收邮件则返回 OK应答。SMTP发送者再发出RCPT命令确认邮件是否接收到。如果SMTP接收者接收,则返回OK应答;如果不能接收到,则发出拒绝接收应答(但不中止整个邮件操作),双方将如此重复多次。当接收者收到全部邮件后会接收到特别的序列,如果接收者成功处理了邮件,则返回OK应答。

smtp的缺点:

1.smtp不能传送可执行文件或其他的二进制对象。

2.smtp限于传送7位的ASCII码。许多其他非英文国家的文字就无法转换。即使在smtp网关将EBDCDIC码(即扩充的二/十进制交换码)转换为ASCII码时也会遇到一些麻烦。

3.smtp服务器会拒绝超过一定长度的邮件。

4.某些smtp的实现并没有完全按照smp的标准。常见问题如下:a.回车,换行的删除和增加。b.超过76个字符时的处理:截断或自动换行。c.后面多余空格的删除。d.将制表符tab转换为若干个空格。

邮件读取协议POP3IMAP
POP的全称是 Post Office Protocol ,即邮局协议,用于电子邮件的接收,它使用TCP的110端口,现在常用的是第三版,所以简称为 POP3。POP3仍采用Client/Server工作模式,。当客户机需要服务时,客户端的软件(OutlookExpress或FoxMail)将与POP3服务器建立TCP连接,此后要经过POP3协议的三种工作状态,首先是认证过程,确认客户机提供的用户名和密码,在认证通过后便转入处理状态,在此状态下用户可收取自己的邮件或做邮件的删除,在完成响应的操作后客户机便发出quit命令,此后便进入更新状态,将做删除标记的邮件从服务器端删除掉。到此为止整个POP过程完成。
IMAP是Internet Message Access Protocol的缩写,它比POP3复杂的多。现在较新的版本是1996年的版本4,即IMAP4【RFC2060】,它目前还只是因特网的建议标准。顾名思义,主要提供的是通过Internet获取信息的一种协议。IMAP象POP那样提供了方便的邮件下载服务,让用户能进行离线阅读,但IMAP能完成的却远远不只这些。IMAP提供的摘要浏览功能可以让你在阅读完所有的邮件到达时间、主题、发件人、大小等信息后才作出是否下载的决定。

POP3协议的不足
POP3天生的缺陷,即当用户接收电子邮件时,所有的信件都从服务器上清除并下载到客户机。在整个收信过程中,用户无法知道邮件的具体信息,只有照单全收入硬盘后,才能慢慢浏览和删除。这使用户几乎没有对邮件接收的控制决定权。一旦碰上邮箱被轰炸,或有比较大的邮件,用户不能通过分析邮件的内容及发信人地址来决定是否下载或删除,从而造成系统资源的浪费。而IMAP协议不但可以克服POP3的缺陷,而且还提供了更强大的功能。

对IMAP的解析,IMAP提供操作的三种模式:
1、在线方式:邮件保留在Mail服务器端,客户端可以对其进行管理。其使用方式与WebMail相类似。

在在线方式下,IMAP允许用户象访问和操纵本地信息一样来访问和操纵邮件服务器上的信息。IMAP软件支持邮件在本地文件夹间和服务器文件夹间的随意拖动,以把本地硬盘上的文件存放到服务器上,或将服务器上的文件取回本地,所有的功能仅需要一次鼠标拖放的操作来实现。

在用户端可对服务器上的邮箱建立任意层次结构的文件夹,并可灵活地在文件夹间移动邮件,标出那些读过或回复过的邮件,删除对你来说无用的文件。

IMAP提供的摘要浏览功能可以让你在阅读完所有的邮件到达时间、主题、发件人、大小等信息,同时还可以享受选择性下载附件的服务。比如一封邮件里含有3个附件,而其中只有1个附件是您需要的,则可以选择只下载这1个附件。你可以充分了解后才作出是否下载,是全部下载还是仅下载一部分等决定,使用户不会因下载垃圾信息而占用宝贵的空间和浪费网费。

IMAP还提供基于服务器的邮件处理以及共享邮件信箱等功能。邮件(包括已下载邮件的副本)在手动删除前保留在服务器中,这有助于邮件档案的生成和共享。用户可在任何客户机上都可查看服务器上的邮件。这让那些漫游用户感到很方便。
同时IMAP也象POP3一样,允许用户从服务器上下载信息到他们的电脑上,这意味着他们仍然可以在离线方式下阅读邮件。

2、离线方式:邮件保留在Mail服务器端,客户端可以对其进行管理。这与POP协议一样。

3、分离方式:邮件的一部分在Mail服务器端,一部分在客户端。这与一些成熟的组件包应用(如LotusNotes/Domino)的方式类似。
在分离状态下,本地系统上的邮件状态和服务器上的邮件状态,可能和以后再连接时不一样。此时,IMAP的同步机制解决了这个问题。IMAP邮件的客户端软件能够记录用户在本地的操作,当他们连上网络后会把这些操作传送给服务器,服务器也会告诉客户端软件,当用户离线的时候服务器端发生的事件,比如有新邮件到达等,以保持服务器和客户端的同步。

在IMAP下可定义供其他拥有特别访问权利的用户使用的共享文件夹,而使用POP不能实现共享邮件信箱和共享邮件,仅能通过抄送给或用手工传送邮件。共享信箱将使以使用Internet邮件为主的工作组的工作变得更为容易。

IMAP还提供许多特别的功能比如建立子目录和通过IMAP访问Usenet。在系统管理员方面,IMAP也提供了一整套可用的特性。

IMAP的监听端口为143,消息的内在时间和日期是由服务器给出的,而不是在RFC822中信头给出的时间和日期,是消息最后到达的真实日期和时间。如果信息是被IMAP的Copy命令投递的,这应当是源信息的内在时间和日期;如果信息是被IAMP的Append命令投递的,这应当是由Append命令专门描述的时间和日期。
在IMAP协议中定义了很多的命令,可用telnet来执行,例如Authenticate、List和Close等等,此处不再详述。

实现IMAP的不足
在利用服务器磁盘资源方面,IMAP不如POP3。由于使用POP时服务器端的邮件被下载到客户机的同时会删除,因而不占用额外空间用以存放旧的邮件。而IMAP服务器将保持旧的邮件,占用了额外空间,而且需要定期地删除旧邮件。同时,由于用户查阅信息标题和决定下载哪些附件,也需要一定时间,因此链接时间也比POP方式长。
在应用方面,由于IMAP比较复杂,给开发者开发服务器和客户机的软件带来一些难题。对于ISP来说,采用IMAP意味着要花钱购买相关商业软件,同时会付出高额技术支撑费用,因而商用的实现方案还不多。

SMTP 命令简介

什么是 SMTP
SMTP (Simple Mail Transfer Protocol) : 电子邮件从客户机传输到服务器或从某一个服务器传输到另一个服务器使用的传输协议。 SMTP 是请求/响应协议,命令和响应都是基于 ASCII 文本,并以 CR 和 LF 符结束。响应包括一个表示返回状态的三位数字代码。SMTP 在 TCP 协议 25 端口监听连接请求。

什么是 ESMTP
ESMTP (Extended SMTP),顾名思义,扩展 SMTP 就是对标准 SMTP 协议进行的扩展。它与 SMTP 服务的区别仅仅是,使用 SMTP 发信不需要验证用户帐户,而用 ESMTP 发信时, 服务器会要求用户提供用户名和密码以便验证身份。验证之后的邮件发送过程与 SMTP 方式没有两样。

SMTP 命令
SMTP 命令包括:
HELO 向服务器标识用户身份。发送者能欺骗,说谎,但一般情况下服务器都能检测到。
EHLO 向服务器标识用户身份。发送者能欺骗,说谎,但一般情况下服务器都能检测到。
MAIL FROM 命令中指定的地址是发件人地址
RCPT TO 标识单个的邮件接收人;可有多个 RCPT TO;常在 MAIL 命令后面。
DATA 在单个或多个 RCPT 命令后,表示所有的邮件接收人已标识,并初始化数据传输,以 CRLF.CRLF 结束
VRFY 用于验证指定的用户/邮箱是否存在;由于安全方面的原因,服务器常禁止此命令
EXPN 验证给定的邮箱列表是否存在,扩充邮箱列表,也常被禁用
HELP 查询服务器支持什么命令
NOOP 无操作,服务器应响应 OK
RSET 重置会话,当前传输被取消
QUIT 结束会话
连接 Winmail Server 使用 SMTP 命令发送邮件
例如:安装 Winmail 的邮件服务器IP是192.168.0.1 (蓝色字体内容由客户端输入,红色字体内容是服务返回的)

telnet 192.168.0.1 25 --------------------------------------- 使用 telnet 命令连接服务器 25 端口
Trying 192.168.0.1... --------------------------------------- 正在连接服务器 25 端口
Connected to 192.168.0.1. ----------------------------------- 连接服务器 25 端口成功
220 Winmail Mail Server ESMTP ready ------------------------- 显示服务器的标识名称 (Winmail 管理工具->高级设置->系统参数->基本参数中可更改)
helo cnu.com ------------------------------------------------ 向服务器标识用户身份,发信不要认证,跳过下面几步直接发送 mail from 命令
250 Winmail Mail Server
ehlo cnu.com ------------------------------------------------ ESMTP 命令,发信需要认证。
250-Winmail Mail Server
250-PIPELINING
250-AUTH=LOGIN PLAIN
250-AUTH LOGIN PLAIN
250-SIZE 20480000
250 8BITMIME
auth login ------------------------------------------------- 进行用户身份认证
334 VXNlcm5hbWU6
Y29zdGFAYW1heGl0Lm5ldA== ----------------------------------- BASE64 加密后的用户名
334 UGFzc3dvcmQ6
MTk4MjIxNA== ----------------------------------------------- BASE64 加密后的密码
235 auth successfully -------------------------------------- 身份认证成功
(535 auth failure ------------------------------------------ 身份认证失败)
发到本系统中域名下的账户可跳过身份认证。
mail from: <test1@domain.com> ------------------------------ mail from 地址 test1@domain.com
250 ok ----------------------------------------------------- 命令执行成功
rcpt to: <test2@domain.com> -------------------------------- 递送给地址 test2@domain.com
250 ok ----------------------------------------------------- 命令执行成功
data ------------------------------------------------------- 数据传输初始化
354 go ahead ----------------------------------------------- 开始传输数据
From: test1@domain.com
To: test2@domain.com
Date: Mon, 25 Oct 2004 14:24:27 +0800
Subject: test mail
Hi, test2
This is a test mail, you don't reply it.
------------------------------------------------------------ 数据内容,包括BASE64加密后的邮件内容, 以 CRLF.CRLF 结束数据传输
250 ok message accepted for delivery ----------------------- 命令执行成功
quit ------------------------------------------------------- 结束会话
221  Winmail Mail Server
Connection closed by foreign host .------------------------- 断开连接

电子邮件是Internet上最广泛的应用之一,尽管网络上有多种邮件收发服务,但最常用的还是SMTP,SMTP就是简单邮件传输协议(Simple Mail Transfer Protocol)。传统的SMTP使用简单的协议传输7位ASCII文本字符,它还有一种扩展形式,称为ESMTP,允许扩展协商,它包括8位的传输。这样,它不仅能够传输二进制的数据,还可以传输非ASCII字符集。一会儿,我们将用SMTP命令模拟发送邮件,但现在让我们先去了解一下SMTP协议的参数吧!

参数

作用

HELO

使用标准的SMTP,向服务器标识用户身份。发送者能进行欺骗,但一般情况下服务器都能检测到

EHLO

使用ESMTP,向服务器标识用户身份。发送者能进行欺骗,但一般情况下服务器都能检测到。

STARTTLS

启用TLS

MAIL FROM

命令中指定的地址是发件人地址

RCPT TO

标识单个的邮件接收人;可有多个 RCPT TO;常在 MAIL 命令后面

DATA

在单个或多个 RCPT 命令后,表示所有的邮件接收人已标识,并初始化数据传输,以CRLF.CRLF 结束

VRFY

用于验证指定的用户/邮箱是否存在;由于安全方面的原因,服务器常禁止此命令

EXPN

验证给定的邮箱列表是否存在,扩充邮箱列表,也常被禁用

HELP

查询服务器支持什么命令

NOOP

无操作,服务器响应 250 OK

RSET

重置会话,当前传输被取消,服务器响应 250 OK

QUIT

结束会话

以上参数为常用参数,明白它们的作用,现在我们来做一个实例吧!让我们利用SMTP命令,先向邮件服务器发送一封电子邮件。Internet上有些邮件服务器都支持这种方法去模拟身份发邮件呀,大家可以试一试。

1.首先我们TELNET上邮件服务器

clip_image020

2.然后输入以下命令

clip_image021

3. 验证邮件是否收到

clip_image023

5.3.2 POP3模型及会话过程

大家一听这个POP,读起来有点像是中文中的泡泡,其实这是一个英文术语的缩写。POP的全称是 Post Office Protocol,即邮局协议,用于电子邮件的接收,它使用TCP的110端口。现在常用的是第三版 ,所以简称为 POP3。POP3仍采用Client/Server工作模式,Client被称为客户端,一般我们日常使用电脑都是作为客户端,而Server(服务器)则是网管人员进行管理的。举个形象的例子,Server(服务器)是许多小信箱的集合,就像我们所居住楼房的信箱结构,而客户端就好比是一个人拿着钥匙去信箱开锁取信一样的道理。
POP在网络模型中的层次
大家都知道网络是分层的,而这个分层就好比是一个企业里的组织结构一样。在日常使用电脑过程中,人操作着电脑,人就好比是指挥电脑对因特网操作的首席执行官。当我们打开Foxmail这个邮件软件收取邮件时,Foxmail这个软件就会调用TCP/IP参考模型中的应用层协议—POP协议。
  应用层协议建立在网络层协议之上,是专门为用户提供应用服务的,一般是可见的。如利用FTP(文件传输协议)传输一个文件请求一个和目标计算机的连接,在传输文件的过程中,用户和远程计算机交换的一部分是能看到的。而这时POP协议则会指挥下层的协议为它传送数据服务器,最后Foxmail通过一系列协议对话后成功将电子邮件保存到了Foxmail的收件箱里。TCP/IP参考模型是Internet的基础。和OSI的7层协议比较,TCP/IP参考模型中没有会话层和表示层。通常说的TCP/IP是一组协议的总称,TCP/IP实际上是一个协议族(或协议包),包括100多个相互关联的协议,其中 IP(Internet Protocol,网际协议)是网络层最主要的协议;TCP(Transmission Control Protocol,传输控制协议)和UDP(User Datagram Protocol,用户数据报协议)是传输层中最主要的协议。一般认为IP、TCP、UDP是最根本的三种协议,是其它协议的基础。
  相信读者了解TCP/IP框架之后,一定会对各层产生一定的兴趣,不过我们对于这个模型的理解也是一步步来的。在这里,我们首先只要知道相应的软件会调用应用层的相应协议,比如Foxmail会调用POP协议,而IE浏览器则会调用DNS协议先将网址解析成IP地址。在实际收取邮件的过程中,POP这个应用层的协议会指挥TCP协议,利用IP协议将一封大邮件拆分成若干个数据包在Internet上传送。
  为了便于读者理解这个过程,笔者举个例子来说明一下,比如你要和一个人远距离通话,因为距离实在太远了,你只好将你所表达的一大段分成一个个字大声喊,而对方把每个听到的字写在纸上,当写下来后就大喊一声告诉你它收到了,这样就克服了距离远听不清的弱点,这种一问一答的反馈机制就好比是TCP协议, POP服务器一般使用的是TCP的110号端口。
POP工作原理简介
  下面就让我们一起来看看电子邮件软件收取电子邮件的过程,一般我们在电子邮件软件的账号属性上设置一个POP服务器的URL(比如 pop.163.com),以及邮箱的账号和密码。这个在收信过程中都是用得到的。当我们按下电子邮件软件中的收取键后,电子邮件软件首先会调用DNS协议对POP服务器进行解析IP地址,当IP地址被解析出来后,邮件程序便开始使用TCP协议连接邮件服务器的110端口,因为POP服务器是比较忙的,所以在这个过程中我们相对要等比较长的时间。当邮件程序成功地连上POP服务器后,其先会使用USER命令将邮箱的账号传给POP服务器,然后再使用 PASS命令将邮箱的账号传给服务器,当完成这一认证过程后,邮件程序使用STAT命令请求服务器返回邮箱的统计资料,比如邮件总数和邮件大小等,然后 LIST便会列出服务器里邮件数量。然后邮件程序就会使用RETR命令接收邮件,接收一封后便使用DELE命令将邮件服务器中的邮件置为删除状态。当使用 QUIT时,邮件服务器便会将置为删除标志的邮件给删了。通俗地讲,邮件程序从服务器接收邮件,其实就是一个对话过程,POP协议就是用于电子邮件的一门语言。

POP3协议命令原始码及工作原理

 

一 简介:
1. POP适用于C/S结构的脱机模型的电子邮件协议,目前已发展到第三版,称POP3。脱机模型即不能在线
   操作,不像IMAP4(netscape支持IMAP4)
2. 当客户机与服务器连接并查询新电子邮件时,被该客户机指定的所有将被下载的邮件都将被程序下载到
   客户机,下载后,电子邮件客户机就可以删除或修改任意邮件,而无需与电子邮件服务器进一步交互。

clip_image024
3. POP3客户向POP3服务器发送命令并等待响应,POP3命令采用命令行形式,用ASCII码表示。
   服务器响应是由一个单独的命令行组成,或多个命令行组成,响应第一行以ASCII文本+OK或-ERR指出相应
   的操作状态是成功还是失败
4. 在POP3协议中有三种状态,认可状态,处理状态,和更新状态。
   当客户机与服务器建立联系时,一旦客户机提供了自己身份并成功确认,即由认可状态转入处理状态,
   在完成相应的操作后客户机发出quit命令,则进入更新状态,更新之后最后重返认可状态。如下图
 等待连接        身份确认         quit命令
—— |认可|————— |处理|——————|更新|
|__________________________________|
                  重返认可状态
5. 认可状态的命令语句
   一般情况下,大多数现有的POP3客户与服务器执行采用ASCII明文发送用户名和口令,在认可状态等
待客户连接的情况下,客户发出连接,并由命令user/pass对在网络上发送明文用户名和口令给服务器
进行身份确认。一旦确认成功,便转入处理状态。
    为了避免发送明文口令的问题,有一种新的认证方法,命令为APOP,使用APOP,口令在传输之前被加密。
当第一次与服务器连接时,POP3服务器向客户机发送一个ASCII码问候,这个问候由一串字符组成对每个客
户机是唯一的,与当时的时间有关,然后,客户机把它的纯文本口令附加到从服务器接收到的字符串之后,
然后计算出结果字符串的MD5单出函数消息摘要,客户机把用户名与MD5消息摘要作为APOP命令的参数一起发送
出去。
    目前,大多数windows上的邮件客户软件不支持APOP命令,qpopper支持。
6. POP3命令码如下:
 命令          参数       状态     描述
------------------------------------------
USER username    认可     此命令与下面的pass命令若成功,将导致状态转换
PASS password    认可     
APOP Name,Digest 认可     Digest是MD5消息摘要
------------------------------------------
STAT None        处理     请求服务器发回关于邮箱的统计资料,如邮件总数和总字节数
UIDL [Msg#]      处理     返回邮件的唯一标识符,POP3会话的每个标识符都将是唯一的
LIST [Msg#]      处理     返回邮件数量和每个邮件的大小
RETR [Msg#]      处理     返回由参数标识的邮件的全部文本
DELE [Msg#]      处理     服务器将由参数标识的邮件标记为删除,由quit命令执行
RSET None        处理     服务器将重置所有标记为删除的邮件,用于撤消DELE命令
TOP [Msg#]      处理     服务器将返回由参数标识的邮件前n行内容,n必须是正整数
NOOP None        处理     服务器返回一个肯定的响应
------------------------------------------
QUIT None        更新     
a.客户机希望结束这次会话
b.如果服务器处于‘处理’状态,那么将进入‘更新’状态以删除任何标记为删除的邮件
c.导致由处理状态到更新状态,又重返认可状态的转变
d.如果这个命令发出时服务器处于‘认可’状态,则结束会话,不进行‘更新’状态。
7. POP3协议在TCP/110端口上等待客户连接请求。
8. 若密码为明文,我如何监听?
下面的命令在服务器运行后在屏幕上显示POP3连接及命令发送的过程:
#sniffit -a -A. -p 110 -b -s 192.169.11.12
note: 192.168.11.12是客户机IP地址
         你需要事先安装sniffit这个端口监听程序
9. 考虑这种情况,若客户在收取邮件时,假定为15封信等待接收,但由于线路问题,收到第10封时断线了,
   为什么下次收时仍然从第一封开始,也即为什么前10封没有被从服务器上删除掉?
任何邮件的删除都必须在quit命令发出后对已标记为删除的邮件执行删除操作,由于中途断线,仍处于
处理状态,没有机会执行quit命令以进行状态转换。
10. pop3 session is locked by another session, please wait 10 minutes then try again.
由于非正常操作引起POP3程序内部机制锁住该次会话。
11. Foxmail与OE(outlook express)的处理机制的不同。
a. 假定服务器上有三封邮件等待客户机接收。用foxmail与OE的不同之处在于
foxmail每收一封标记删除一封,而OE则等全部接收完后再全部标记为删除最后执行quit命令。
Foxmail OE
-------------------------------
retr 1 retr 1
dele 1 retr 2
retr 2 retr 3
dele 2 dele 1
retr 3 dele 2
dele 3 dele 3
quit quit
b. Foxmail的远程邮件管理是非常优秀的管理工具,假定服务器上有三封信,对第一封,我们不想接收
   想从服务器直接删除;对第二封,想接收但不删除,对第三封,这一次不想接收,分别标记后
   最后foxmail发出的命令是
dele 1
retr 2
quit
c. 若没有foxmail,正好有几封很大的信堵住了,我不想接收,想直接删除它,或者想查看这两封是谁发的?
    直接在windows的DOS窗口下用命令行操作,如:
# telnet my.isp.net 110
user 'username'
pass 'password'
list
dele 3
dele 5
quit

5.3.3 实例——Email接收程序
5.4 FTP文件传输协议

FTP文件传输协议

     FTP(File Transfer Protocol)即文件传输协议,主要是用来在网络上进行文件传输的。要在两台主机直接直接传输文件,除了通过共享方式传输外,还有一类使用非常广泛的方式,即采用FTP方式。

     FTP的工作模式同其他的C/S模式的网络通信协议有很大的区别。通常在进行HTTP通信,只需要一个端口进行通信,即客户端只需要连接一个端口进行数据传输。但是FTP通信除了一个默认的端口21以外,还需要其他端口,通常是两个端口同时进行数据传输的。一是默认的端口,而另一个是按照一定原则由服务器或者客户端产生的非标准端口。其中默认端口主要进行控制连接,顾名思义,控制连接主要是进行命令协议的及服务器端的响应码的传输,另一个非标准端口主要是进行数据的传递,比如上传文件、下载文件、打印目录信息等。非标准端口的产生要根据用户选择的连接模式而定,如果客户选择的是PORT模式,则需要客户端提供服务器一个IP地址和一个非标准端口;如果用户采用被动模式,则服务器端要提供给客户端一个IP地址和一个非标准端口。在进行文件传输的时候,通常每传送完一个文件,又会重新建立连接模式并重新产生一个临时端口。

在用户协议解释器和服务器协议解释器之间的控制连接上传输的是FTP命令和应答信息。用户协议解释器负责发送命令和解释收到的应答,由服务器协议解释器执行命令并把执行情况以应答的形式发送给客户。

所有FTP命令和应答都在控制连接上以NVT ASCII码的形式传输,并且每一个命令都以<CR>和<LF>结尾,即是一个命令或应答占一行。

(1)FTP命令

访问控制命令:用户名:USER,口令:PASS,账号:ACCT,改变工作目录:CWD,回到上一层目录:CDUP,结构加载:SMNT,重新初始化:REIN,退出登录:QUIT

传输参数命令:数据端口:PORT,被动:PASV,表示类型:TYPE,文件结构:STRU,传输模式:MODE

FTP服务命令:RETR:用于从服务器获取指定路径内的文件复本,服务器上的文件内容和状态不受影响.

                        STOP:向服务器传送文件,如果文件存在,则覆盖源文件,否则创建文件

                        APPE:功能和STOP相似,但是如果文件在指定路径内已存在,则把数据附加到原文件尾部,如果不存在则新建文件。

                        ALLP:用于在主机上位新传送的文件分配足够的空间。参数是十进际的逻辑字节数。

                        REFR:该命令用于重命名指定的文件,它后面必须跟“rename to"来指定新的文件名。

                        RNTO:该命令用于指定新的文件名。

                        ABOR:关闭控制连接,但不关闭数据连接.

                        DEL:该命令用于删除指定路径下的文件,用户进程负责对删除的提示。

                        RMD:该命令用于删除指定目录。

                        MKD:该命令用于在指定的路径下创建新目录。

                        PWD:该命令用于返回当前工作目录。

                        LIST::该命令式服务器传送列表到客户。

                        SITE:用于获得服务器系统信息,信息因系统不同而不同。

                        NLIST:该命令式服务器传送目录表名到用户。

                        HELP:该命令与平常系统中得到的帮助相似。

                        NOOP:该命令不产生任何实际动作,它仅使服务器返回OK

(2)FTP应答

     FTP命令的应答是服务器对FTP命令执行情况的响应,它主要有两方面的功能:一是服务器对数据传输的请求和过程进行同步,这是TCP协议所要求的,TCP要求对接收到的数据都要进行确认;二是让用户了解服务器的状态,用户可以根据收到的状态信息对服务器是否正常执行了有关操作进行判断。

第6章 传输层协议及编程实例
第7章 网络层协议和数据链路层
第8章 Internet通信原理及编程实例
第9章 基于Windows API的虚拟终端实现
第10章 多线程网络文件传输的设计与实现

VC中利用多线程技术实现线程之间的通信

 作者: 刘涛

当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本实例针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,分别进行探讨,并利用多线程技术进行线程之间的通信,实现了数字的简单排序。  
一、 实现方法
1、理解线程
要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应于Visual C++中的CwinThread类对象。单独一个执行程序运行时,缺省地包含的一个主线程,主线程以函数地址的形式出现,提供程序的启动点,如main()或WinMain()函数等。

当主线程终止时,进程也随之终止。根据实际需要,应用程序可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。
  一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。
  线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进程终止。工作线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CWinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。
2、线程的管理和操作
  (一)线程的启动
  创建一个用户界面线程,首先要从类CwinThread产生一个派生类,同时必须使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。

第二步是根据需要重载该派生类的一些成员函数如:ExitInstance()、InitInstance()、OnIdle()、PreTranslateMessage()等函数。最后调用AfxBeginThread()函数的一个版本:

CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ) 启动该用户界面线程,

其中第一个参数为指向定义的用户界面线程类指针变量,

第二个参数为线程的优先级,

第三个参数为线程所对应的堆栈大小,

第四个参数为线程创建时的附加标志,缺省为正常状态,如为CREATE_SUSPENDED则线程启动后为挂起状态。
 对于工作线程来说,启动一个线程,首先需要编写一个希望与应用程序的其余部分并行运行的函数如Fun1(),接着定义一个指向CwinThread对象的指针变量*pThread,调用AfxBeginThread(Fun1,param,priority)函数,返回值赋给pThread变量的同时一并启动该线程来执行上面的Fun1()函数,其中Fun1是线程要运行的函数的名字,也既是上面所说的控制函数的名字,param是准备传送给线程函数Fun1的任意32位值,priority则是定义该线程的优先级别,它是预定义的常数,读者可参考MSDN。
  (二)线程的优先级
  以下的CwinThread类的成员函数用于线程优先级的操作:

int GetThreadPriority();
BOOL SetThradPriority()(int nPriority);
 上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;处于不同优先权层次上的线程,谁的优先权层次高,谁先运行。至于优先级设置所需的常数,自己参考MSDN就可以了,要注意的是要想设置线程的优先级,这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置,CwinThread类没有提供相应的函数,但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityClass()来实现。
 (三)线程的悬挂和恢复
CWinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中SuspendThread()用来悬挂线程,暂停线程的执行;ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。
  (四)结束线程
终止线程有三种途径,

线程可以在自身内部调用AfxEndThread()来终止自身的运行;

可以在线程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占用的堆栈;

第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。

下面以第三种方法为例,给出部分代码:

////////////////////////////////////////////////////////////////
//////CtestView message handlers
/////Set to True to end thread
Bool bend=FALSE;//定义的全局变量,用于控制线程的运行;
//The Thread Function;
UINT ThreadFunction(LPVOID pParam)//线程函数
{
while(!bend)
{
Beep(100,100);
Sleep(1000);
}
return 0;
}
/////////////////////////////////////////////////////////////
CwinThread *pThread;
HWND hWnd;
Void CtestView::OninitialUpdate()
{
hWnd=GetSafeHwnd();
pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程
pThread->m_bAutoDelete=FALSE;//线程为手动删除
Cview::OnInitialUpdate();
}
////////////////////////////////////////////////////////////////
Void CtestView::OnDestroy()
{
bend=TRUE;//改变变量,线程结束
WaitForSingleObject(pThread->m_hThread,INFINITE);//等待线程结束
delete pThread;//删除线程
Cview::OnDestroy();
}

3、线程之间的通信
  通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。
(一) 利用用户定义的消息通信
  在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息,如下所示:

#define WM_USERMSG WMUSER+100;

在需要的时候,在一个线程中调用::PostMessage((HWND)param,WM_USERMSG,0,0)或CwinThread::PostThradMessage()来向另外一个线程发送这个消息,

上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。

下面的代码是对上节代码的修改,修改后的结果是在线程结束时显示一个对话框,提示线程结束:

UINT ThreadFunction(LPVOID pParam)
{
while(!bend)
{
Beep(100,100);
Sleep(1000);
}
 ::PostMessage(hWnd,WM_USERMSG,0,0);
return 0;
}

////////WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam,
LPARAM lParam)
LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam)
{
AfxMessageBox("Thread ended.");
Retrun 0;
}
 上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。在控制函数中可以直接使用::GetMessage()这个SDK函数进行消息分检和处理,自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。
 (二)用事件对象实现通信
 在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下: ////////////////////////////////////////////////////////////////////
Cevent threadStart ,threadEnd;
UINT ThreadFunction(LPVOID pParam)
{
 ::WaitForSingleObject(threadStart.m_hObject,INFINITE);
AfxMessageBox("Thread start.");
while(!bend)
{
Beep(100,100);
Sleep(1000);
Int result=::WaitforSingleObject(threadEnd.m_hObject,0);
//等待threadEnd事件有信号,无信号时线程在这里悬停
If(result==Wait_OBJECT_0)
Bend=TRUE;
}
 ::PostMessage(hWnd,WM_USERMSG,0,0);
return 0;
}
/////////////////////////////////////////////////////////////
Void CtestView::OninitialUpdate()
{
hWnd=GetSafeHwnd();
threadStart.SetEvent();//threadStart事件有信号
pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程
pThread->m_bAutoDelete=FALSE;
Cview::OnInitialUpdate();
}
////////////////////////////////////////////////////////////////
Void CtestView::OnDestroy()
{
threadEnd.SetEvent();
WaitForSingleObject(pThread->m_hThread,INFINITE);
delete pThread;
Cview::OnDestroy();
}
  运行这个程序,当关闭程序时,才显示提示框,显示"Thread ended"。

 

VC中利用多线程技术实现线程之间的通信(2)

 

4、线程之间的同步
  前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,MFC支持的七个多线程的同步类可以分成两大类:同步对象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象(CmultiLock和CsingleLock)。本节主要介绍临界区(critical section)、互斥(mutexe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安全。
(一) 临界区
  临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。

临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。

同时启动两个线程,它们对应的函数分别为WriteThread()和ReadThread(),用以对公共数组array[]操作,下面的代码说明了如何使用临界区对象:

#include "afxmt.h"
int array[10],destarray[10];
CCriticalSection Section;
UINT WriteThread(LPVOID param)
{
Section.Lock();
for(int x=0;x<10;x++)
array[x]=x;
Section.Unlock();
}
UINT ReadThread(LPVOID param)
{
Section.Lock();
For(int x=0;x<10;x++)
Destarray[x]=array[x];
Section.Unlock();
}

上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下。
 (二)互斥
互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。

互斥与Cmutex类的对象相对应,使用互斥对象时,必须创建一个CSingleLock或CMultiLock对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下:

#include "afxmt.h"
int array[10],destarray[10];
CMutex Section;
UINT WriteThread(LPVOID param)
{
CsingleLock singlelock;
singlelock (&Section);
singlelock.Lock();
for(int x=0;x<10;x++)
array[x]=x;
singlelock.Unlock();
}
UINT ReadThread(LPVOID param)
{
CsingleLock singlelock;
singlelock (&Section);
singlelock.Lock();
For(int x=0;x<10;x++)
Destarray[x]=array[x];
singlelock.Unlock();
}

(三)信号量
信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问。

要实现计数处理,先创建一个CsingleLock或CmltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。

下面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才得以显示。/////////////////////////////////////////////////////////////////////////
Csemaphore *semaphore;
Semaphore=new Csemaphore(2,2);
HWND hWnd=GetSafeHwnd();
AfxBeginThread(threadProc1,hWnd);
AfxBeginThread(threadProc2,hWnd);
AfxBeginThread(threadProc3,hWnd);
UINT ThreadProc1(LPVOID param)
{
CsingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK);
return 0;
}
UINT ThreadProc2(LPVOID param)
{
CSingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK);
return 0;
}
UINT ThreadProc3(LPVOID param)
{
CsingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK);
return 0;
}

二、 编程步骤
1、 启动Visual C++6.0,生成一个32位的控制台程序,将该程序命名为"sequence"
2、 输入要排续的数字,声明四个子线程;
3、 输入代码,编译运行程序。

VC中利用多线程技术实现线程之间的通信(3)

 

三、 程序代码

//////////////////////////////////////////////////////////////////////////////
// sequence.cpp : Defines the entry point for the console application.
//////////////////////////////////////////////////////////////////////////////
主要用到的WINAPI线程控制函数,有关详细说明请查看MSDN;
线程建立函数:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性结构指针,可为NULL;
DWORD dwStackSize, // 线程栈大小,若为0表示使用默认值;
LPTHREAD_START_ROUTINE lpStartAddress, // 指向线程函数的指针;
LPVOID lpParameter, // 传递给线程函数的参数,可以保存一个指针值;
DWORD dwCreationFlags, // 线程建立是的初始标记,运行或挂起;
LPDWORD lpThreadId // 指向接收线程号的DWORD变量;
);
对临界资源控制的多线程控制的信号函数:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性结构指针,可为NULL;
BOOL bManualReset, // 手动清除信号标记,TRUE在WaitForSingleObject后必须手动

//调用RetEvent清除信号。若为 FALSE则在WaitForSingleObject
//后,系统自动清除事件信号;
BOOL bInitialState, // 初始状态,TRUE有信号,FALSE无信号;
LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH;
//如果遇到同名的其他信号量函数就会失败,如果遇
//到同类信号同名也要注意变化;
);
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性结构指针,可为NULL
BOOL bInitialOwner, // 当前建立互斥量是否占有该互斥量TRUE表示占有,
//这样其他线程就不能获得此互斥量也就无法进入由
//该互斥量控制的临界区。FALSE表示不占有该互斥量
LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH如果
//遇到同名的其他信号量函数就会失败,
//如果遇到同类信号同名也要注意变化;
);
//初始化临界区信号,使用前必须先初始化
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // 临界区变量指针
);
//阻塞函数
//如果等待的信号量不可用,那么线程就会挂起,直到信号可用
//线程才会被唤醒,该函数会自动修改信号,如Event,线程被唤醒之后
//Event信号会变得无信号,Mutex、Semaphore等也会变。

DWORD WaitForSingleObject(
HANDLE hHandle, // 等待对象的句柄
DWORD dwMilliseconds // 等待毫秒数,INFINITE表示无限等待
);
//如果要等待多个信号可以使用WaitForMutipleObject函数
*/
#include "stdafx.h"
#include "stdlib.h"
#include "memory.h"
HANDLE evtTerminate; //事件信号,标记是否所有子线程都执行完
/*
下面使用了三种控制方法,你可以注释其中两种,使用其中一种。
注意修改时要连带修改临界区PrintResult里的相应控制语句
*/
HANDLE evtPrint; //事件信号,标记事件是否已发生
//CRITICAL_SECTION csPrint; //临界区
//HANDLE mtxPrint; //互斥信号,如有信号表明已经有线程进入临界区并拥有此信号
static long ThreadCompleted = 0;
/*用来标记四个子线程中已完成线程的个数,当一个子线程完成时就对ThreadCompleted进行加一操作, 要使用InterlockedIncrement(long* lpAddend)和InterlockedDecrement(long* lpAddend)进行加减操作*/
//下面的结构是用于传送排序的数据给各个排序子线程
struct MySafeArray
{
long* data;
int iLength;
};
//打印每一个线程的排序结果
void PrintResult(long* Array, int iLength, const char* HeadStr = "sort");
//排序函数
unsigned long __stdcall BubbleSort(void* theArray); //冒泡排序
unsigned long __stdcall SelectSort(void* theArray); //选择排序
unsigned long __stdcall HeapSort(void* theArray); //堆排序
unsigned long __stdcall InsertSort(void* theArray); //插入排序
/*以上四个函数的声明必须适合作为一个线程函数的必要条件才可以使用CreateThread
建立一个线程。
(1)调用方法必须是__stdcall,即函数参数压栈顺序由右到左,而且由函数本身负责
栈的恢复, C和C++默认是__cdecl, 所以要显式声明是__stdcall
(2)返回值必须是unsigned long
(3)参数必须是一个32位值,如一个指针值或long类型
(4) 如果函数是类成员函数,必须声明为static函数,在CreateThread时函数指针有特殊的写法。如下(函数是类CThreadTest的成员函数中):
static unsigned long _stdcall MyThreadFun(void* pParam);
handleRet = CreateThread(NULL, 0, &CThreadTestDlg::MyThreadFun, NULL, 0, &ThreadID);
之所以要声明为static是由于,该函数必须要独立于对象实例来使用,即使没有声明实例也可以使用。*/
int QuickSort(long* Array, int iLow, int iHigh); //快速排序
int main(int argc, char* argv[])
{
long data[] = {123,34,546,754,34,74,3,56};
int iDataLen = 8;
//为了对各个子线程分别对原始数据进行排序和保存排序结果
//分别分配内存对data数组的数据进行复制
long *data1, *data2, *data3, *data4, *data5;
MySafeArray StructData1, StructData2, StructData3, StructData4;
data1 = new long[iDataLen];
memcpy(data1, data, iDataLen << 2); //把data中的数据复制到data1中
//内存复制 memcpy(目标内存指针, 源内存指针, 复制字节数), 因为long的长度
//为4字节,所以复制的字节数为iDataLen << 2, 即等于iDataLen*4
StructData1.data = data1;
StructData1.iLength = iDataLen;

data2 = new long[iDataLen];
memcpy(data2, data, iDataLen << 2);
StructData2.data = data2;
StructData2.iLength = iDataLen;

data3 = new long[iDataLen];
memcpy(data3, data, iDataLen << 2);
StructData3.data = data3;
StructData3.iLength = iDataLen;

data4 = new long[iDataLen];
memcpy(data4, data, iDataLen << 2);
StructData4.data = data4;
StructData4.iLength = iDataLen;

data5 = new long[iDataLen];
memcpy(data5, data, iDataLen << 2);
unsigned long TID1, TID2, TID3, TID4;
//对信号量进行初始化
evtTerminate = CreateEvent(NULL, FALSE, FALSE, "Terminate");
evtPrint = CreateEvent(NULL, FALSE, TRUE, "PrintResult");
//分别建立各个子线程
CreateThread(NULL, 0, &BubbleSort, &StructData1, NULL, &TID1);
CreateThread(NULL, 0, &SelectSort, &StructData2, NULL, &TID2);
CreateThread(NULL, 0, &HeapSort, &StructData3, NULL, &TID3);
CreateThread(NULL, 0, &InsertSort, &StructData4, NULL, &TID4);
//在主线程中执行行快速排序,其他排序在子线程中执行
QuickSort(data5, 0, iDataLen - 1);
PrintResult(data5, iDataLen, "Quick Sort");
WaitForSingleObject(evtTerminate, INFINITE); //等待所有的子线程结束
//所有的子线程结束后,主线程才可以结束
delete[] data1;
delete[] data2;
delete[] data3;
delete[] data4;
CloseHandle(evtPrint);
return 0;
}
/*
冒泡排序思想(升序,降序同理,后面的算法一样都是升序):从头到尾对数据进行两两比较进行交换,小的放前大的放后。这样一次下来,最大的元素就会被交换的最后,然后下一次
循环就不用对最后一个元素进行比较交换了,所以呢每一次比较交换的次数都比上一次循环的次数少一,这样N次之后数据就变得升序排列了*/
unsigned long __stdcall BubbleSort(void* theArray)
{
long* Array = ((MySafeArray*)theArray)->data;
int iLength = ((MySafeArray*)theArray)->iLength;
int i, j=0;
long swap;
for (i = iLength-1; i > 0; i--)
{
for(j = 0; j < i; j++)
{
if(Array[j] > Array[j+1]) //前比后大,交换
{
swap = Array[j];
Array[j] = Array[j+1];
Array[j+1] = swap;
}
}
}
PrintResult(Array, iLength, "Bubble Sort"); //向控制台打印排序结果
InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完
//若都执行完则设置程序结束信号量
return 0;
}
/*选择排序思想:每一次都从无序的数据中找出最小的元素,然后和前面已经有序的元素序列的后一个元素进行交换,这样整个源序列就会分成两部分,前面一部分是已经排好序的有序序列,后面一部分是无序的,用于选出最小的元素。循环N次之后,前面的有序序列加长到跟源序列一样长,后面的无序部分长度变为0,排序就完成了。*/
unsigned long __stdcall SelectSort(void* theArray)
{
long* Array = ((MySafeArray*)theArray)->data;
int iLength = ((MySafeArray*)theArray)->iLength;
long lMin, lSwap;
int i, j, iMinPos;
for(i=0; i < iLength-1; i++)
{
lMin = Array[i];
iMinPos = i;
for(j=i + 1; j <= iLength-1; j++) //从无序的元素中找出最小的元素
{
if(Array[j] < lMin)
{
iMinPos = j;
lMin = Array[j];
}
}
//把选出的元素交换拼接到有序序列的最后
lSwap = Array[i];
Array[i] = Array[iMinPos];
Array[iMinPos] = lSwap;
}
PrintResult(Array, iLength, "Select Sort"); //向控制台打印排序结果
InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
if(ThreadCompleted == 4) SetEvent(evtTerminate);//检查是否其他线程都已执行完
//若都执行完则设置程序结束信号量
return 0;
}
/*堆排序思想:堆:数据元素从1到N排列成一棵二叉树,而且这棵树的每一个子树的根都是该树中的元素的最小或最大的元素这样如果一个无序数据集合是一个堆那么,根元素就是最小或最大的元素堆排序就是不断对剩下的数据建堆,把最小或最大的元素析透出来。下面的算法,就是从最后一个元素开始,依据一个节点比父节点数值大的原则对所有元素进行调整,这样调整一次就形成一个堆,第一个元素就是最小的元素。然后再对剩下的无序数据再进行建堆,注意这时后面的无序数据元素的序数都要改变,如第一次建堆后,第二个元素就会变成堆的第一个元素。*/
unsigned long __stdcall HeapSort(void* theArray)
{
long* Array = ((MySafeArray*)theArray)->data;
int iLength = ((MySafeArray*)theArray)->iLength;
int i, j, p;
long swap;
for(i=0; i<iLength-1; i++)
{
for(j = iLength - 1; j>i; j--) //从最后倒数上去比较字节点和父节点
{
p = (j - i - 1)/2 + i; //计算父节点数组下标
//注意到树节点序数跟数组下标不是等同的,因为建堆的元素个数逐个递减
if(Array[j] < Array[p]) //如果父节点数值大则交换父节点和字节点
{
swap = Array[j];
Array[j] = Array[p];
Array[p] = swap;
}
}
}
PrintResult(Array, iLength, "Heap Sort"); //向控制台打印排序结果
InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完
//若都执行完则设置程序结束信号量
return 0;
}
/*插入排序思想:把源数据序列看成两半,前面一半是有序的,后面一半是无序的,把无序的数据从头到尾逐个逐个的插入到前面的有序数据中,使得有序的数据的个数不断增大,同时无序的数据个数就越来越少,最后所有元素都会变得有序。*/
unsigned long __stdcall InsertSort(void* theArray)
{
long* Array = ((MySafeArray*)theArray)->data;
int iLength = ((MySafeArray*)theArray)->iLength;
int i=1, j=0;
long temp;
for(i=1; i<iLength; i++)
{
temp = Array[i]; //取出序列后面无序数据的第一个元素值
for(j=i; j>0; j--) //和前面的有序数据逐个进行比较找出合适的插入位置
{
if(Array[j - 1] > temp) //如果该元素比插入值大则后移
Array[j] = Array[j - 1];
else //如果该元素比插入值小,那么该位置的后一位就是插入元素的位置
break;
}
Array[j] = temp;
}
PrintResult(Array, iLength, "Insert Sort"); //向控制台打印排序结果
InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完
//若都执行完则设置程序结束信号量
return 0;
}
/*快速排序思想:快速排序是分治思想的一种应用,它先选取一个支点,然后把小于支点的元素交换到支点的前边,把大于支点的元素交换到支点的右边。然后再对支点左边部分和右
边部分进行同样的处理,这样若干次之后,数据就会变得有序。下面的实现使用了递归
建立两个游标:iLow,iHigh;iLow指向序列的第一个元素,iHigh指向最后一个先选第一个元素作为支点,并把它的值存贮在一个辅助变量里。那么第一个位置就变为空并可以放置其他的元素。 这样从iHigh指向的元素开始向前移动游标,iHigh查找比支点小的元素,如果找到,则把它放置到空置了的位置(现在是第一个位置),然后iHigh游标停止移动,这时iHigh指向的位置被空置,然后移动iLow游标寻找比支点大的元素放置到iHigh指向的空置的位置,如此往复直到iLow与iHigh相等。最后使用递归对左右两部分进行同样处理*/
int QuickSort(long* Array, int iLow, int iHigh)
{
if(iLow >= iHigh) return 1; //递归结束条件
long pivot = Array[iLow];
int iLowSaved = iLow, iHighSaved = iHigh; //保未改变的iLow,iHigh值保存起来
while (iLow < iHigh)
{
while (Array[iHigh] >= pivot && iHigh > iLow) //寻找比支点大的元素
iHigh -- ;
Array[iLow] = Array[iHigh]; //把找到的元素放置到空置的位置
while (Array[iLow] < pivot && iLow < iHigh) //寻找比支点小的元素
iLow ++ ;
Array[iHigh] = Array[iLow]; //把找到的元素放置到空置的位置
}
Array[iLow] = pivot; //把支点值放置到支点位置,这时支点位置是空置的
//对左右部分分别进行递归处理
QuickSort(Array, iLowSaved, iHigh-1);
QuickSort(Array, iLow+1, iHighSaved);
return 0;
}
//每一个线程都要使用这个函数进行输出,而且只有一个显示器,产生多个线程
//竞争对控制台的使用权。
void PrintResult(long* Array, int iLength, const char* HeadStr)
{
WaitForSingleObject(evtPrint, INFINITE); //等待事件有信号
//EnterCriticalSection(&csPrint); //标记有线程进入临界区
//WaitForSingleObject(mtxPrint, INFINITE); //等待互斥量空置(没有线程拥有它)
int i;
printf("%s: ", HeadStr);
for (i=0; i<iLength-1; i++)
{
printf("%d,", Array[i]);
Sleep(100); //延时(可以去掉)
/*只是使得多线程对临界区访问的问题比较容易看得到
如果你把临界控制的语句注释掉,输出就会变得很凌乱,各个排序的结果会
分插间隔着输出,如果不延时就不容易看到这种不对临界区控制的结果
*/
}
printf("%d\n", Array[i]);
SetEvent(evtPrint); //把事件信号量恢复,变为有信号
}
四、 小结
  对复杂的应用程序来说,线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本实例讲述了线程处理中经常遇到的问题,希望对读者朋友有一定的帮助,起到抛砖引玉的作用。

用Visual C++语言在局域网实现IP多播

 

在局域网中,管理员常常需要将某条信息发送给一组用户。如果使用一对一的发送方法,虽然是可行的,但是过于麻烦,也常会出现漏发、错发。为了更有效的解决这种组通信问题,出现了一种多播技术(也常称为组播通信),它是基于IP层的通信技术。为了帮助读者理解,下面将简要的介绍一下多播的概念。
  众所周知,普通IP通信是在一个发送者和一个接收者之间进行的,我们常把它称为点对点的通信,但对于有些应用,这种点对点的通信模式不能有效地满足实际应用的需求。例如:一个数字电话会议系统由多个会场组成,当在其中一个会场的参会人发言时,要求其它会场都能即时的得到此发言的内容,这是一个典型的一对多的通信应用,通常把这种一对多的通信称为多播通信。采用多播通信技术,不仅可以实现一个发送者和多个接收者之间进行通信的功能,而且可以有效减轻网络通信的负担,避免资源的无谓浪费。
  广播也是一种实现一对多数据通信的模式,但广播与多播在实现方式上有所不同。广播是将数据从一个工作站发出,局域网内的其他所有工作站都能收到它。这一特征适用于无连接协议,因为LAN上的所有机器都可获得并处理广播消息。使用广播消息的不利之处是每台机器都必须对该消息进行处理。多播通信则不同,数据从一个工作站发出后,如果在其它LAN上的机器上面运行的进程表示对这些数据"有兴趣",多播数据才会制给它们。
  本实例由Sender和Receiver两个程序组成,Sender用户从控制台上输入多播发送数据,Receiver端都要求加入同一个多播组,完成接收Sender发送的多播数据。
一、实现方法
1、 协议支持
  并不是所有的协议都支持多播通信,对Win32平台而言,仅两种可从WinSock内访问的协议(IP/ATM)才提供了对多播通信的支持。因通常通信应用都建立在TCP/IP协议之上的,所以本文只针对IP协议来探讨多播通信技术。
  支持多播通信的平台包括Windows CE 2.1、Windows 95、Windows 98、Windows NT 4、Windows 2000和WindowsXP。自2.1版开始,Windows CE才开始实现对IP多播的支持。本文实例建立在WindowsXP专业版平台上。
2、多播地址
IP采用D类地址来支持多播。每个D类地址代表一组主机。共有28位可用来标识小组。所以可以同时有多达25亿个小组。当一个进程向一个D类地址发送分组时,会尽最大的努力将它送给小组的所有成员,但不能保证全部送到。有些成员可能收不到这个分组。举个例子来说,假定五个节点都想通过I P多播,实现彼此间的通信,它们便可加入同一个组地址。全部加入之后,由一个节点发出的任何数据均会一模一样地复制一份,发给组内的每个成员,甚至包括始发数据的那个节点。D类I P地址范围在244.0.0.0到239.255.255.255之间。它分为两类:永久地址和临时地址。永久地址是为特殊用途而保留的。比如,244.0.0.0根本没有使用(也不能使用),244.0.0.1代表子网内的所有系统(主机),而244.0.0.2代表子网内的所有路由器。在RFC 1700文件中,提供了所有保留地址的一个详细清单。该文件是为特殊用途保留的所有资源的一个列表,大家可以找来作为参考。"Internet分配数字专家组"(I A N A)负责着这个列表的维护。在表1中,我们总结了目前标定为"保留"的一些地址。临时组地址在使用前必须先创建,一个进程可以要求其主机加入特定的组,它也能要求其主机脱离该组。当主机上的最后一个进程脱离某个组后,该组地址就不再在这台主机中出现。每个主机都要记录它的进程当前属于哪个组。
  表1 部分永久地址说明

地 址

说 明

244.0.0.1

基本地址(保留)

244.0.0.1

子网上的所有系统

244.0.0.2

子网上的所有路由器

244.0.0.5

子网上所有OSPF路由器

244.0.0.6

子网上所有指定的OSPF路由器

244.0.0.9

RIP第2版本组地址

244.0.1.1

网络时间协议

244.0.1.24

WINS服务器组地址

3、 多播路由器
  多播由特殊的多播路由器来实现,多播路由器同时也可以是普通路由器。各个多播路由器每分钟发送一个硬件多播信息给子网上的主机(目的地址为244.0.0.1),要求它们报告其进程当前所属的是哪一组,各主机将它感兴趣的D类地址返回。这些询问和响应分组使用IGMP(Internet group management protocol),它大致类似于ICMP。它只有两种分组:询问和响应,都有一个简单的固定格式,其中有效载荷字段的第一个字段是一些控制信息,第二字段是一个D类地址,在RFC1112中有详细说明。
  多播路由器的选择是通过生成树实现的,每个多播路由器采用修改过的距离矢量协议和其邻居交换信息,以便向每个路由器为每一组构造一个覆盖所有组员的生成树。在修剪生成树及删除无关路由器和网络时,用到了很多优化方法。
4.库支持
WinSock提供了实现多播通信的API函数调用。针对IP多播,WinSock提供了两种不同的实现方法,具体取决于使用的是哪个版本的WinSock。第一种方法是WinSock1提供的,要求通过套接字选项来加入一个组;另一种方法是WinSock2提供的,它是引入一个新函数,专门负责多播组的加入,这个函数便是WSAJoinLeaf,它是基层协议是无关的。本文将通过一个多播通信的实例的实现过程,来讲叙多播实现的主要步骤。因为Window98以后版本都安装了Winsock2.0以上版本,所以本文实例在WinSock2.0平台上开发的,但在其中对WinSock1实现不同的地方加以说明。
二、编程步骤
1、启动Visual C++6.0,创建一个控制台项目工程MultiCase。在此项目工程中添加Sender和Receiver两个项目。
Receiver项目实现步骤:
(1)、创建一个SOCK_DGRAM类型的Socket。
(2)、将此Socket绑定到本地的一个端口上,为了接收服务器端发送的多播数据。
(3)、加入多播组。
①、 WinSock2中引入一个WSAJoinLeaf,此函数原型如下:
SOCKET WSAJoinLeaf( SOCKET s, const struct sockaddr FAR *name, int namelen,
LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS, LPQOS lpGQOS, DWORD dwFlags );
  其中,第一个参数s代表一个套接字句柄,是自WSASocket返回的。传递进来的这个套接
  字必须使用恰当的多播标志进行创建;否则的话WSAJoinLeaf就会失败,并返回错误WSAEINVAL。第二个参数是SOCKADDR(套接字地址)结构,具体内容由当前采用的协议决定,对于IP协议来说,这个地址指定的是主机打算加入的那个多播组。第三个参数namelen(名字长度)是用于指定name参数的长度,以字节为单位。第四个参数lpCallerData(呼叫者数据)的作用是在会话建立之后,将一个数据缓冲区传输给自己通信的对方。第五个参数lpCalleeData(被叫者数据)用于初始化一个缓冲区,在会话建好之后,接收来自对方的数据。注意在当前的Windows平台上,lpCallerData和lpCalleeData这两个参数并未真正实现,所以均应设为NULL。LpSQOS和lpGQOS这两个参数是有关Qos(服务质量)的设置,通常也设为NULL,有关Qos内容请参阅MSDN或有关书籍。最后一个参数dwFlags指出该主机是发送数据、接收数据或收发兼并。该参数可选值分别是:JL_SENDER_ONLY、JL_RECEIVER_ONLY或者JL_BOTH。
②、在WinSock1平台上加入多播组需要调用setsockopt函数,同时设置IP_ADD_MEMBERSHIP选项,指定想加入的那个组的地址结构。具体实现代码将在下面代码注释列出。
(4)、接收多播数据。
Sender实现步骤:
(1)、创建一个SOCK_DGRAM类型的Socket。
(2)、加入多播组。
(3)、发送多播数据。
3、编译两个项目,在局域网中按如下步骤测试:
  (1)、将Sender.exe拷贝到发送多播数据的PC上。
  (2)、将Receiver.exe拷贝到多个要求接收多播数据的PC上。
  (3)、各自运行相应的程序。
  (4)、在Sender PC上输入多播数据后,你就可以在Receiver PC上看到输入的多播数据。

用Visual C++语言在局域网实现IP多播(2)

 

三、程序代码

//////////////////////////////Receiver.c程序代码:
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#define MCASTADDR "233.0.0.1" //本例使用的多播组地址。
#define MCASTPORT 5150 //绑定的本地端口号。
#define BUFSIZE 1024 //接收数据缓冲大小。
int main( int argc,char ** argv)
{
WSADATA wsd;
struct sockaddr_in local,remote,from;
SOCKET sock,sockM;
TCHAR recvbuf[BUFSIZE];
/*struct ip_mreq mcast; // Winsock1.0 */
int len = sizeof( struct sockaddr_in);
int ret;
//初始化WinSock2.2
if( WSAStartup( MAKEWORD(2,2),&wsd) != 0 )
{
printf("WSAStartup() failed\n");
return -1;
}
/*
 创建一个SOCK_DGRAM类型的SOCKET
 其中,WSA_FLAG_MULTIPOINT_C_LEAF表示IP多播在控制面层上属于"无根"类型;
WSA_FLAG_MULTIPOINT_D_LEAF表示IP多播在数据面层上属于"无根",有关控制面层和
 数据面层有关概念请参阅MSDN说明。
*/
if((sock=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF|WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("socket failed with:%d\n",WSAGetLastError());
WSACleanup();
return -1;
}
//将sock绑定到本机某端口上。
local.sin_family = AF_INET;
local.sin_port = htons(MCASTPORT);
local.sin_addr.s_addr = INADDR_ANY;
if( bind(sock,(struct sockaddr*)&local,sizeof(local)) == SOCKET_ERROR )
{
printf( "bind failed with:%d \n",WSAGetLastError());
closesocket(sock);
WSACleanup();
return -1;
}
//加入多播组
remote.sin_family = AF_INET;
remote.sin_port = htons(MCASTPORT);
remote.sin_addr.s_addr = inet_addr( MCASTADDR );
/* Winsock1.0 */
/*
mcast.imr_multiaddr.s_addr = inet_addr(MCASTADDR);
mcast.imr_interface.s_addr = INADDR_ANY;
if( setsockopt(sockM,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char*)&mcast,sizeof(mcast)) == SOCKET_ERROR)
{
printf("setsockopt(IP_ADD_MEMBERSHIP) failed:%d\n",WSAGetLastError());
closesocket(sockM);
WSACleanup();
return -1;
}
*/
/* Winsock2.0*/
if(( sockM = WSAJoinLeaf(sock,(SOCKADDR*)&remote,sizeof(remote),NULL,NULL,NULL,NULL, JL_BOTH)) == INVALID_SOCKET)
{
printf("WSAJoinLeaf() failed:%d\n",WSAGetLastError());
closesocket(sock);
WSACleanup();
return -1;
}
//接收多播数据,当接收到的数据为"QUIT"时退出。
while(1)
{
if(( ret = recvfrom(sock,recvbuf,BUFSIZE,0,(struct sockaddr*)&from,&len)) == SOCKET_ERROR)
{
printf("recvfrom failed with:%d\n",WSAGetLastError());
closesocket(sockM);
closesocket(sock);
WSACleanup();
return -1;
}
if( strcmp(recvbuf,"QUIT") == 0 ) break;
else {
recvbuf[ret] = '\0';
printf("RECV:' %s ' FROM <%s> \n",recvbuf,inet_ntoa(from.sin_addr));
}
}
closesocket(sockM);
closesocket(sock);
WSACleanup();
return 0;
}
////////////////////////Sender.c程序代码
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#define MCASTADDR "233.0.0.1" //本例使用的多播组地址。
#define MCASTPORT 5150 //本地端口号。
#define BUFSIZE 1024 //发送数据缓冲大小。
int main( int argc,char ** argv)
{
WSADATA wsd;
struct sockaddr_in remote;
SOCKET sock,sockM;
TCHAR sendbuf[BUFSIZE];
int len = sizeof( struct sockaddr_in);
//初始化WinSock2.2
if( WSAStartup( MAKEWORD(2,2),&wsd) != 0 )
{
printf("WSAStartup() failed\n");
return -1;
}
if((sock=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_MULTIPOINT_C_LEAF |WSA_FLAG_MULTIPOINT_D_LEAF|WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("socket failed with:%d\n",WSAGetLastError());
WSACleanup();
return -1;
}
//加入多播组
remote.sin_family = AF_INET;
remote.sin_port = htons(MCASTPORT);
remote.sin_addr.s_addr = inet_addr( MCASTADDR );
if(( sockM = WSAJoinLeaf(sock,(SOCKADDR*)&remote,
sizeof(remote),NULL,NULL,NULL,NULL,JL_BOTH)) == INVALID_SOCKET)
{
printf("WSAJoinLeaf() failed:%d\n",WSAGetLastError());
closesocket(sock);
WSACleanup();
return -1;
}
//发送多播数据,当用户在控制台输入"QUIT"时退出。
while(1)
{
printf("SEND : ");
scanf("%s",sendbuf);
if( sendto(sockM,(char*)sendbuf,strlen(sendbuf),0,(struct sockaddr*)&remote, sizeof(remote))==SOCKET_ERROR)
{
printf("sendto failed with: %d\n",WSAGetLastError());
closesocket(sockM);
closesocket(sock);
WSACleanup();
return -1;
}
if(strcmp(sendbuf,"QUIT")==0) break;
Sleep(500);
}
closesocket(sockM);
closesocket(sock);
WSACleanup();
return 0;
}
四、小结

  本实例对IP多播通信进行了探讨,实例程序由Sender和Receiver两部分组成,Sender用户从控制台上输入多播发送数据,Receiver端都要求加入同一个多播组,完成接收Sender发送的多播数据。

第11章 防火墙的设计与实现

网络安全基础:防火墙的概念及实现原理

  一. 防火墙的概念
  近年来,随着普通计算机用户群的日益增长,“防火墙”一词已经不再是服务器领域的专署,大部分家庭用户都知道为自己爱机安装各种“防火墙”软件了。但是,并不是所有用户都对“防火墙”有所了解的,一部分用户甚至认为,“防火墙”是一种软件的名称……
  到底什么才是防火墙?它工作在什么位置,起着什么作用?查阅历史书籍可知,古代构筑和使用木制结构房屋的时候为防止火灾的发生和蔓延,人们将坚固的石块堆砌在房屋周围作为屏障,这种防护构筑物就被称为“防火墙”(FireWall)。时光飞梭,随着计算机和网络的发展,各种攻击入侵手段也相继出现了,为了保护计算机的安全,人们开发出一种能阻止计算机之间直接通信的技术,并沿用了古代类似这个功能的名字——“防火墙”技术来源于此。用专业术语来说,防火墙是一种位于两个或多个网络间,实施网络之间访问控制的组件集合。对于普通用户来说,所谓“防火墙”,指的就是一种被放置在自己的计算机与外界网络之间的防御系统,从网络发往计算机的所有数据都要经过它的判断处理后,才会决定能不能把这些数据交给计算机,一旦发现有害数据,防火墙就会拦截下来,实现了对计算机的保护功能。
  防火墙技术从诞生开始,就在一刻不停的发展着,各种不同结构不同功能的防火墙,构筑成网络上的一道道防御大堤。
  二. 防火墙的分类
  世界上没有一种事物是唯一的,防火墙也一样,为了更有效率的对付网络上各种不同攻击手段,防火墙也派分出几种防御架构。根据物理特性,防火墙分为两大类,硬件防火墙和软件防火墙。软件防火墙是一种安装在负责内外网络转换的网关服务器或者独立的个人计算机上的特殊程序,它是以逻辑形式存在的,防火墙程序跟随系统启动,通过运行在Ring0级别的特殊驱动模块把防御机制插入系统关于网络的处理部分和网络接口设备驱动之间,形成一种逻辑上的防御体系。
  在没有软件防火墙之前,系统和网络接口设备之间的通道是直接的,网络接口设备通过网络驱动程序接口(Network Driver Interface Specification,NDIS)把网络上传来的各种报文都忠实的交给系统处理,例如一台计算机接收到请求列出机器上所有共享资源的数据报文,NDIS直接把这个报文提交给系统,系统在处理后就会返回相应数据,在某些情况下就会造成信息泄漏。而使用软件防火墙后,尽管NDIS接收到仍然的是原封不动的数据报文,但是在提交到系统的通道上多了一层防御机制,所有数据报文都要经过这层机制根据一定的规则判断处理,只有它认为安全的数据才能到达系统,其他数据则被丢弃。因为有规则提到“列出共享资源的行为是危险的”,因此在防火墙的判断下,这个报文会被丢弃,这样一来,系统接收不到报文,则认为什么事情也没发生过,也就不会把信息泄漏出去了。
  软件防火墙工作于系统接口与NDIS之间,用于检查过滤由NDIS发送过来的数据,在无需改动硬件的前提下便能实现一定强度的安全保障,但是由于软件防火墙自身属于运行于系统上的程序,不可避免的需要占用一部分CPU资源维持工作,而且由于数据判断处理需要一定的时间,在一些数据流量大的网络里,软件防火墙会使整个系统工作效率和数据吞吐速度下降,甚至有些软件防火墙会存在漏洞,导致有害数据可以绕过它的防御体系,给数据安全带来损失,因此,许多企业并不会考虑用软件防火墙方案作为公司网络的防御措施,而是使用看得见摸得着的硬件防火墙。
  硬件防火墙是一种以物理形式存在的专用设备,通常架设于两个网络的驳接处,直接从网络设备上检查过滤有害的数据报文,位于防火墙设备后端的网络或者服务器接收到的是经过防火墙处理的相对安全的数据,不必另外分出CPU资源去进行基于软件架构的NDIS数据检测,可以大大提高工作效率。
  硬件防火墙一般是通过网线连接于外部网络接口与内部服务器或企业网络之间的设备,这里又另外派分出两种结构,一种是普通硬件级别防火墙,它拥有标准计算机的硬件平台和一些功能经过简化处理的UNIX系列操作系统和防火墙软件,这种防火墙措施相当于专门拿出一台计算机安装了软件防火墙,除了不需要处理其他事务以外,它毕竟还是一般的操作系统,因此有可能会存在漏洞和不稳定因素,安全性并不能做到最好;另一种是所谓的“芯片”级硬件防火墙,它采用专门设计的硬件平台,在上面搭建的软件也是专门开发的,并非流行的操作系统,因而可以达到较好的安全性能保障。但无论是哪种硬件防火墙,管理员都可以通过计算机连接上去设置工作参数。由于硬件防火墙的主要作用是把传入的数据报文进行过滤处理后转发到位于防火墙后面的网络中,因此它自身的硬件规格也是分档次的,尽管硬件防火墙已经足以实现比较高的信息处理效率,但是在一些对数据吞吐量要求很高的网络里,档次低的防火墙仍然会形成瓶颈,所以对于一些大企业而言,芯片级的硬件防火墙才是他们的首选。
  有人也许会这么想,既然PC架构的防火墙也不过如此,那么购买这种防火墙还不如自己找技术人员专门腾出一台计算机来做防火墙方案了。虽然这样做也是可以的,但是工作效率并不能和真正的PC架构防火墙相比,因为PC架构防火墙采用的是专门修改简化过的系统和相应防火墙程序,比一般计算机系统和软件防火墙更高度紧密集合,而且由于它的工作性质决定了它要具备非常高的稳定性、实用性和非常高的系统吞吐性能,这些要求并不是安装了多网卡的计算机就能简单替代的,因此PC架构防火墙虽然是与计算机差不多的配置,价格却相差很大。
  现实中我们往往会发现,并非所有企业都架设了芯片级硬件防火墙,而是用PC架构防火墙甚至前面提到的计算机替代方案支撑着,为什么?这大概就是硬件防火墙最显著的缺点了:它太贵了!购进一台PC架构防火墙的成本至少都要几千元,高档次的芯片级防火墙方案更是在十万元以上,这些价格并非是小企业所能承受的,而且对于一般家庭用户而言,自己的数据和系统安全也无需专门用到一个硬件设备去保护,何况为一台防火墙投入的资金足以让用户购买更高档的电脑了,因而广大用户只要安装一种好用的软件防火墙就够了。
  为防火墙分类的方法很多,除了从形式上把它分为软件防火墙和硬件防火墙以外,还可以从技术上分为“包过滤型”、“应用代理型”和“状态监视”三类;从结构上又分为单一主机防火墙、路由集成式防火墙和分布式防火墙三种;按工作位置分为边界防火墙、个人防火墙和混合防火墙;按防火墙性能分为百兆级防火墙和千兆级防火墙两类……虽然看似种类繁多,但这只是因为业界分类方法不同罢了,例如一台硬件防火墙就可能由于结构、数据吞吐量和工作位置而规划为“百兆级状态监视型边界防火墙”,因此这里主要介绍的是技术方面的分类,即“包过滤型”、“应用代理型”和“状态监视型”防火墙技术。
  那么,那些所谓的“边界防火墙”、“单一主机防火墙”又是什么概念呢?所谓“边界”,就是指两个网络之间的接口处,工作于此的防火墙就被称为“边界防火墙”;与之相对的有“个人防火墙”,它们通常是基于软件的防火墙,只处理一台计算机的数据而不是整个网络的数据,现在一般家庭用户使用的软件防火墙就是这个分类了。而“单一主机防火墙”呢,就是我们最常见的一台台硬件防火墙了;一些厂商为了节约成本,直接把防火墙功能嵌进路由设备里,就形成了路由集成式防火墙……
  三. 防火墙技术
  传统意义上的防火墙技术分为三大类,“包过滤”(Packet Filtering)、“应用代理”(Application Proxy)和“状态监视”(Stateful Inspection),无论一个防火墙的实现过程多么复杂,归根结底都是在这三种技术的基础上进行功能扩展的。
1.包过滤技术
  包过滤是最早使用的一种防火墙技术,它的第一代模型是“静态包过滤”(Static Packet Filtering),使用包过滤技术的防火墙通常工作在OSI模型中的网络层(Network Layer)上,后来发展更新的“动态包过滤”(Dynamic Packet Filtering)增加了传输层(Transport Layer),简而言之,包过滤技术工作的地方就是各种基于TCP/IP协议的数据报文进出的通道,它把这两层作为数据监控的对象,对每个数据包的头部、协议、地址、端口、类型等信息进行分析,并与预先设定好的防火墙过滤规则(Filtering Rule)进行核对,一旦发现某个包的某个或多个部分与过滤规则匹配并且条件为“阻止”的时候,这个包就会被丢弃。适当的设置过滤规则可以让防火墙工作得更安全有效,但是这种技术只能根据预设的过滤规则进行判断,一旦出现一个没有在设计人员意料之中的有害数据包请求,整个防火墙的保护就相当于摆设了。也许你会想,让用户自行添加不行吗?但是别忘了,我们要为是普通计算机用户考虑,并不是所有人都了解网络协议的,如果防火墙工具出现了过滤遗漏问题,他们只能等着被入侵了。一些公司采用定期从网络升级过滤规则的方法,这个创意固然可以方便一部分家庭用户,但是对相对比较专业的用户而言,却不见得就是好事,因为他们可能会有根据自己的机器环境设定和改动的规则,如果这个规则刚好和升级到的规则发生冲突,用户就该郁闷了,而且如果两条规则冲突了,防火墙该听谁的,会不会当场“死给你看”(崩溃)?也许就因为考虑到这些因素,至今我没见过有多少个产品会提供过滤规则更新功能的,这并不能和杀毒软件的病毒特征库升级原理相提并论。为了解决这种鱼与熊掌的问题,人们对包过滤技术进行了改进,这种改进后的技术称为“动态包过滤”(市场上存在一种“基于状态的包过滤防火墙”技术,即Stateful-based Packet Filtering,他们其实是同一类型),与它的前辈相比,动态包过滤功能在保持着原有静态包过滤技术和过滤规则的基础上,会对已经成功与计算机连接的报文传输进行跟踪,并且判断该连接发送的数据包是否会对系统构成威胁,一旦触发其判断机制,防火墙就会自动产生新的临时过滤规则或者把已经存在的过滤规则进行修改,从而阻止该有害数据的继续传输,但是由于动态包过滤需要消耗额外的资源和时间来提取数据包内容进行判断处理,所以与静态包过滤相比,它会降低运行效率,但是静态包过滤已经几乎退出市场了,我们能选择的,大部分也只有动态包过滤防火墙了。
  基于包过滤技术的防火墙,其缺点是很显著的:它得以进行正常工作的一切依据都在于过滤规则的实施,但是偏又不能满足建立精细规则的要求(规则数量和防火墙性能成反比),而且它只能工作于网络层和传输层,并不能判断高级协议里的数据是否有害,但是由于它廉价,容易实现,所以它依然服役在各种领域,在技术人员频繁的设置下为我们工作着。
2.应用代理技术
  由于包过滤技术无法提供完善的数据保护措施,而且一些特殊的报文攻击仅仅使用过滤的方法并不能消除危害(如SYN攻击、ICMP洪水等),因此人们需要一种更全面的防火墙保护技术,在这样的需求背景下,采用“应用代理”(Application Proxy)技术的防火墙诞生了。我们的读者还记得“代理”的概念吗?代理服务器作为一个为用户保密或者突破访问限制的数据转发通道,在网络上应用广泛。我们都知道,一个完整的代理设备包含一个服务端和客户端,服务端接收来自用户的请求,调用自身的客户端模拟一个基于用户请求的连接到目标服务器,再把目标服务器返回的数据转发给用户,完成一次代理工作过程。那么,如果在一台代理设备的服务端和客户端之间连接一个过滤措施呢?这样的思想便造就了“应用代理”防火墙,这种防火墙实际上就是一台小型的带有数据检测过滤功能的透明代理服务器(Transparent Proxy),但是它并不是单纯的在一个代理设备中嵌入包过滤技术,而是一种被称为“应用协议分析”(Application Protocol Analysis)的新技术。
“应用协议分析”技术工作在OSI模型的最高层——应用层上,在这一层里能接触到的所有数据都是最终形式,也就是说,防火墙“看到”的数据和我们看到的是一样的,而不是一个个带着地址端口协议等原始内容的数据包,因而它可以实现更高级的数据检测过程。整个代理防火墙把自身映射为一条透明线路,在用户方面和外界线路看来,它们之间的连接并没有任何阻碍,但是这个连接的数据收发实际上是经过了代理防火墙转向的,当外界数据进入代理防火墙的客户端时,“应用协议分析”模块便根据应用层协议处理这个数据,通过预置的处理规则(没错,又是规则,防火墙离不开规则)查询这个数据是否带有危害,由于这一层面对的已经不再是组合有限的报文协议,甚至可以识别类似于“GET /sql.asp?id=1 and 1”的数据内容,所以防火墙不仅能根据数据层提供的信息判断数据,更能像管理员分析服务器日志那样“看”内容辨危害。而且由于工作在应用层,防火墙还可以实现双向限制,在过滤外部网络有害数据的同时也监控着内部网络的信息,管理员可以配置防火墙实现一个身份验证和连接时限的功能,进一步防止内部网络信息泄漏的隐患。最后,由于代理防火墙采取是代理机制进行工作,内外部网络之间的通信都需先经过代理服务器审核,通过后再由代理服务器连接,根本没有给分隔在内外部网络两边的计算机直接会话的机会,可以避免入侵者使用“数据驱动”攻击方式(一种能通过包过滤技术防火墙规则的数据报文,但是当它进入计算机处理后,却变成能够修改系统设置和用户数据的恶意代码)渗透内部网络,可以说,“应用代理”是比包过滤技术更完善的防火墙技术。
  但是,似乎任何东西都不可能逃避“墨菲定律”的规则,代理型防火墙的结构特征偏偏正是它的最大缺点,由于它是基于代理技术的,通过防火墙的每个连接都必须建立在为之创建的代理程序进程上,而代理进程自身是要消耗一定时间的,更何况代理进程里还有一套复杂的协议分析机制在同时工作,于是数据在通过代理防火墙时就不可避免的发生数据迟滞现象,换个形象的说法,每个数据连接在经过代理防火墙时都会先被请进保安室喝杯茶搜搜身再继续赶路,而保安的工作速度并不能很快。代理防火墙是以牺牲速度为代价换取了比包过滤防火墙更高的安全性能,在网络吞吐量不是很大的情况下,也许用户不会察觉到什么,然而到了数据交换频繁的时刻,代理防火墙就成了整个网络的瓶颈,而且一旦防火墙的硬件配置支撑不住高强度的数据流量而发生罢工,整个网络可能就会因此瘫痪了。所以,代理防火墙的普及范围还远远不及包过滤型防火墙,而在软件防火墙方面更是几乎没见过类似产品了——单机并不具备代理技术所需的条件,所以就目前整个庞大的软件防火墙市场来说,代理防火墙很难有立足之地。
3.状态监视技术
  这是继“包过滤”技术和“应用代理”技术后发展的防火墙技术,它是CheckPoint技术公司在基于“包过滤”原理的“动态包过滤”技术发展而来的,与之类似的有其他厂商联合发展的“深度包检测”(Deep Packet Inspection)技术。这种防火墙技术通过一种被称为“状态监视”的模块,在不影响网络安全正常工作的前提下采用抽取相关数据的方法对网络通信的各个层次实行监测,并根据各种过滤规则作出安全决策。
“状态监视”(Stateful Inspection)技术在保留了对每个数据包的头部、协议、地址、端口、类型等信息进行分析的基础上,进一步发展了“会话过滤”(Session Filtering)功能,在每个连接建立时,防火墙会为这个连接构造一个会话状态,里面包含了这个连接数据包的所有信息,以后这个连接都基于这个状态信息进行,这种检测的高明之处是能对每个数据包的内容进行监视,一旦建立了一个会话状态,则此后的数据传输都要以此会话状态作为依据,例如一个连接的数据包源端口是8000,那么在以后的数据传输过程里防火墙都会审核这个包的源端口还是不是8000,否则这个数据包就被拦截,而且会话状态的保留是有时间限制的,在超时的范围内如果没有再进行数据传输,这个会话状态就会被丢弃。状态监视可以对包内容进行分析,从而摆脱了传统防火墙仅局限于几个包头部信息的检测弱点,而且这种防火墙不必开放过多端口,进一步杜绝了可能因为开放端口过多而带来的安全隐患。
  由于状态监视技术相当于结合了包过滤技术和应用代理技术,因此是最先进的,但是由于实现技术复杂,在实际应用中还不能做到真正的完全有效的数据安全检测,而且在一般的计算机硬件系统上很难设计出基于此技术的完善防御措施(市面上大部分软件防火墙使用的其实只是包过滤技术加上一点其他新特性而已)。
  四. 技术展望
  防火墙作为维护网络安全的关键设备,在目前采用的网络安全的防范体系中,占据着举足轻重的位置。伴随计算机技术的发展和网络应用的普及,越来越多的企业与个体都遭遇到不同程度的安全难题,因此市场对防火墙的设备需求和技术要求都在不断提升,而且越来越严峻的网络安全问题也要求防火墙技术有更快的提高,否则将会在面对新一轮入侵手法时束手无策。
  多功能、高安全性的防火墙可以让用户网络更加无忧,但前提是要确保网络的运行效率,因此在防火墙发展过程中,必须始终将高性能放在主要位置,目前各大厂商正在朝这个方向努力,而且丰富的产品功能也是用户选择防火墙的依据之一,一款完善的防火墙产品,应该包含有访问控制、网络地址转换、代理、认证、日志审计等基础功能,并拥有自己特色的安全相关技术,如规则简化方案等,明天的防火墙技术将会如何发展,让我们拭目以待。


解析防火墙负载均衡技术与功能实现原理

  在谈及负载均衡应用的时候,说的最多的就是流量控制,网站负载,以及服务器负载等等。那么,现在要给大家介绍的是防火墙负载均衡的应用。还是让我们从基础了解,防火墙大家都知道,它是安全上网的一道屏障,有了它在很多不安因子都被挡在门外。那么,我们现在来看看如何进行防火墙的负载均衡。

  防火墙负载均衡技术

概述

  网络安全性是许多ICP和ISP长期担心的问题,网络安全已经成为了人们关注的焦点?网络安全技术将防火墙作为一种防止对网络资源进行非授权访问的常用方法?

  尽管目前防火墙产品可以有效地防止网络入侵?但是,它本身也给ICP和ISP网络带来了问题?尤其是,目前防火墙技术限制了网络的性能和可伸缩性,并且由于防火墙经常成为单故障点,因而它降低了整体网络的可用性?

  由于防火墙处于数据路径上,因此,它们可能会限制网络的性能和可伸缩性?在内部网络和外部网络之间的所有网络流量都必须经过防火墙?可惜的是,最适于防火墙的处理结构不适于检查高容量的数据包?由于防火墙必须处理每一个数据包,因而造成了通信速度的下降?扩展防火墙的性能十分困难,因为它一般要直接升级到功能更强大的硬件平台?

  也是由于防火墙安放在数据路径上,因此,它们形成了降低网络资源可用性的单故障点?尽管多数防火墙可以使用市面上已有的高可用性软件以热备份的配置部署,但是,迄今为止,没有一种解决方案可以安全?可靠?高效支持多台防火墙同时工作?

  新型Web交换机的防火墙负载均衡技术解决了上述问题?先进的Web交换机允许防火墙并行运行,使用户无需将防火墙升级就可以最大限度地发挥防火墙的工作效力,扩展防火的性能,同时使防火墙不再成为一种单故障点?

实现原理

  与传统包交换机不同,Web交换机支持第四层以上的交换功能并具有维护不同TCP会话状态的能力?这类设备为实现防火墙的负载均衡提供了完美的平台?在实施防火墙负载均衡技术时,至少需要两台Web交换机:一台安装在防火墙的外部,另一台安装在内部?

  改变所有输入数据流流向的过滤器被配置在连接外部网和内部网的Web交换机端口上?

  为保持高可用性,Web交换机对防火墙的健康进行监控,只将数据包发向健康的防火墙?Web交换机通过正常地向每个防火墙另一端对应的交换机发送ping数据包来监控防火墙的健康情况?如果某个Web交换机接口不能回应ping命令,累计达到用户定义的次数,这个Web交换机端口(以及暗指的相关防火墙)就被置成"服务器故障"状态?同时,对应Web交换机停止向这个接口发送数据流,而将数据流分配到其余的健康的Web交换机接口和防火墙上?

  当一个Web交换机接口处于"服务器故障"状态时,其对应的Web交换机继续以用户配置的速率向它发送ping命令?在发回第一个成功的ping回应后,该接口(以及防火墙)恢复到服务状态?

  前几节描述的防火墙健康监控技术是Web交换机保证应用程序高可用性的一种方式?在采用防火墙负载均衡技术的同时使用热备份Web交换机,可以使应用获得更高水平的可用性?在使用防火墙负载均衡技术的情况下,可以采用Web交换机对(一台处于工作状态,另一台处于热备份状态?)来构造整个系统无单故障点的网络拓扑结构?这意味着Web交换机不是单故障点,使用它不会造成网络另一些点上的单故障点?

 防火墙负载均衡功能实现

  在案例中,使用了四台交换机,两台位于防火墙的外部,两台位于防火墙的内部?

  在实施热备份配置时,每对Web交换机中的一台被指定为激活交换机,另一台被指定为备份交换机?在激活Web交换机与备份Web交换机之间配置一条直通链路,即所谓的故障转移链路?通常故障转移链路在激活的和备份Web交换机之间只传递Web交换机状态信息,数据流只有在激活Web交换机端口现出故障的情况下才经过故障转移链路传输?

  如果激活Web交换机检测到链路故障的话,它就将此信息通过故障转移链路通知备份Web交换机?如果备份Web交换机上的相应端口是健康的话,该端口就被激活?

一、防火墙基本原理&nbsp;
  首先,我们需要了解一些基本的防火墙实现原理。防火墙目前主要分包过滤,和状态检测的包过滤,应用层代理防火
墙。但是他们的基本实现都是类似的。&nbsp;
│ │---路由器-----网卡│防火墙│网卡│----------内部网络│ │&nbsp;
  防火墙一般有两个以上的网络卡,一个连到外部(router),另一个是连到内部网络。当打开主机网络转发功能时,两个网卡间的网络通讯能直接通过。当有防火墙时,他好比插在网卡之间,对所有的网络通讯进行控制。&nbsp;
  说到访问控制,这是防火墙的核心了:),防火墙主要通过一个访问控制表来判断的,他的形式一般是一连串的如下规则:&nbsp;
1 accept from+ 源地址,端口 to+ 目的地址,端口+ 采取的动作&nbsp;
2 deny ...........(deny就是拒绝。。)&nbsp;
3 nat ............(nat是地址转换。后面说)&nbsp;
  防火墙在网络层(包括以下的炼路层)接受到网络数据包后,就从上面的规则连表一条一条地匹配,如果符合就执行预先安排的动作了!如丢弃包。。。。&nbsp;
  但是,不同的防火墙,在判断攻击行为时,有实现上的差别。下面结合实现原理说说可能的攻击。&nbsp;
  二、攻击包过滤防火墙&nbsp;
  包过滤防火墙是最简单的一种了,它在网络层截获网络数据包,根据防火墙的规则表,来检测攻击行为。他根据数据包的源IP地址;目的IP地址;TCP/UDP源端口;TCP/UDP目的端口来过滤!!很容易受到如下攻击:&nbsp;
1 ip 欺骗攻击:&nbsp;
  这种攻击,主要是修改数据包的源,目的地址和端口,模仿一些合法的数据包来骗过防火墙的检测。如:外部攻击者,将他的数据报源地址改为内部网络地址,防火墙看到是合法地址就放行了:)。可是,如果防火墙能结合接口,地址来匹
配,这种攻击就不能成功了:(&nbsp;
2 d.o.s拒绝服务攻击&nbsp;
  简单的包过滤防火墙不能跟踪 tcp的状态,很容易受到拒绝服务攻击,一旦防火墙受到d.o.s攻击,他可能会忙于处理,而忘记了他自己的过滤功能。:)你就可以饶过了,不过这样攻击还很少的。!&nbsp;
3 分片攻击&nbsp;
  这种攻击的原理是:在IP的分片包中,所有的分片包用一个分片偏移字段标志分片包的顺序,但是,只有第一个分片包含有TCP端口号的信息。当IP分片包通过分组过滤防火墙时,防火墙只根据第一个分片包的Tcp信息判断是否允许通过,而其他后续的分片不作防火墙检测,直接让它们通过。&nbsp;
  这样,攻击者就可以通过先发送第一个合法的IP分片,骗过防火墙的检测,接着封装了恶意数据的后续分片包就可以直接穿透防火墙,直接到达内部网络主机,从而威胁网络和主机的安全。&nbsp;
4 木马攻击&nbsp;
  对于包过滤防火墙最有效的攻击就是木马了,一但你在内部网络安装了木马,防火墙基本上是无能为力的。&nbsp;
  原因是:包过滤防火墙一般只过滤低端口(1-1024),而高端口他不可能过滤的(因为,一些服务要用到高端口,因此防火墙不能关闭高端口的),所以很多的木马都在高端口打开等待,如冰河,subseven等。。。&nbsp;
  但是木马攻击的前提是必须先上传,运行木马,对于简单的包过滤防火墙来说,是容易做的。这里不写这个了。大概就是利用内部网络主机开放的服务漏洞。&nbsp;
  早期的防火墙都是这种简单的包过滤型的,到现在已很少了,不过也有。现在的包过滤采用的是状态检测技术,下面谈谈状态检测的包过滤防火墙。
  三、攻击状态检测的包过滤&nbsp;
  状态检测技术最早是checkpoint提出的,在国内的许多防火墙都声称实现了状态检测技术。&nbsp;
  可是:)很多是没有实现的。到底什么是状态检测?&nbsp;
  一句话,状态检测就是从tcp连接的建立到终止都跟踪检测的技术。&nbsp;
  原先的包过滤,是拿一个一个单独的数据包来匹配规则的。可是我们知道,同一个tcp连接,他的数据包是前后关联的,先是syn包,-》数据包=》fin包。数据包的前后序列号是相关的。&nbsp;
  如果割裂这些关系,单独的过滤数据包,很容易被精心够造的攻击数据包欺骗!!!如nmap的攻击扫描,就有利用syn包,fin包,reset包来探测防火墙后面的网络。!&nbsp;
  相反,一个完全的状态检测防火墙,他在发起连接就判断,如果符合规则,就在内存登记了这个连接的状态信息(地址,port,选项。。),后续的属于同一个连接的数据包,就不需要在检测了。直接通过。而一些精心够造的攻击数据包由于没有在内存登记相应的状态信息,都被丢弃了。这样这些攻击数据包,就不能饶过防火墙了。&nbsp;
  说状态检测必须提到动态规则技术。在状态检测里,采用动态规则技术,原先高端口的问题就可以解决了。实现原理是:平时,防火墙可以过滤内部网络的所有端口(1-65535),外部攻击者难于发现入侵的切入点,可是为了不影响正常的服务,防火墙一但检测到服务必须开放高端口时,如(ftp协议,irc等),防火墙在内存就可以动态地天加一条规则打开相关的高端口。等服务完成后,这条规则就又被防火墙删除。这样,既保障了安全,又不影响正常服务,速度也快。!&nbsp;
  一般来说,完全实现了状态检测技术防火墙,智能性都比较高,一些扫描攻击还能自动的反应,因此,攻击者要很小
心才不会被发现。&nbsp;
  但是,也有不少的攻击手段对付这种防火墙的。&nbsp;
1 协议隧道攻击&nbsp;
  协议隧道的攻击思想类似与VPN的实现原理,攻击者将一些恶意的攻击数据包隐藏在一些协议分组的头部,从而穿透防火墙系统对内部网络进行攻击。&nbsp;
  例如,许多简单地允许ICMP回射请求、ICMP回射应答和UDP分组通过的防火墙就容易受到ICMP和UDP协议隧道的攻击。
Loki和lokid(攻击的客户端和服务端)是实施这种攻击的有效的工具。在实际攻击中,攻击者首先必须设法在内部网络的一个系统上安装上lokid服务端,而后攻击者就可以通过loki客户端将希望远程执行的攻击命令(对应IP分组)嵌入在ICMP或
UDP包头部,再发送给内部网络服务端lokid,由它执行其中的命令,并以同样的方式返回结果。由于许多防火墙允许ICMP和UDP分组自由出入,因此攻击者的恶意数据就能附带在正常的分组,绕过防火墙的认证,顺利地到达攻击目标主机下面的命令是用于启动lokid服务器程序:&nbsp;
lokid-p CI Cvl&nbsp;
loki客户程序则如下启动:&nbsp;
loki Cd172.29.11.191(攻击目标主机)-p CI Cv1 Ct3&nbsp;
  这样,lokid和loki就联合提供了一个穿透防火墙系统访问目标系统的一个后门。&nbsp;
2 利用FTP-pasv绕过防火墙认证的攻击&nbsp;
FTP-pasv攻击是针对防火墙实施入侵的重要手段之一。目前很多防火墙不能过滤这种攻击手段。如CheckPoint的Firewall-1,在监视FTP服务器发送给客户端的包的过程中,它在每个包中寻找"227"这个字符串。如果发现这种包,将从中提取目标地址和端口,并对目标地址加以验证,通过后,将允许建立到该地址的TCP连接。&nbsp;
  攻击者通过这个特性,可以设连接受防火墙保护的服务器和服务。
3 反弹木马攻击
  反弹木马是对付这种防火墙的最有效的方法。攻击者在内部网络的反弹木马定时地连接外部攻击者控制的主机,由于连接是从内部发起的,防火墙(任何的防火墙)都认为是一个合法的连接,因此基本上防火墙的盲区就是这里了。防火墙不能区分木马的连接和合法的连接。
  但是这种攻击的局限是:必须首先安装这个木马!!!所有的木马的第一步都是关键!
  四、攻击代理
  代理是运行在应用层的防火墙,他实质是启动两个连接,一个是客户到代理,另一个是代理到目的服务器。
  实现上比较简单,和前面的一样也是根据规则过滤。由于运行在应用层速度比较慢/1  
  攻击代理的方法很多。
  这里就以wingate为例,简单说说了。(太累了)  
WinGate是目前应用非常广泛的一种Windows95/NT代理防火墙软件,内部用户可以通过一台安装有WinGate的主机访问外部网络,但是它也存在着几个安全脆弱点。
  黑客经常利用这些安全漏洞获得WinGate的非授权Web、Socks和Telnet的访问,从而伪装成WinGate主机的身份对下一
个攻击目标发动攻击。因此,这种攻击非常难于被跟踪和记录。
  导致WinGate安全漏洞的原因大多数是管理员没有根据网络的实际情况对WinGate代理防火墙软件进行合理的设置,只是简单地从缺省设置安装完毕后就让软件运行,这就给攻击者可乘之机。
1 非授权Web访问
  某些WinGate版本(如运行在NT系统下的2.1d版本)在误配置情况下,允许外部主机完全匿名地访问因特网。因此,外部攻击者就可以利用WinGate主机来对Web服务器发动各种Web攻击( 如CGI的漏洞攻击等),同时由于Web攻击的所有报文都是
从80号Tcp端口穿过的,因此,很难追踪到攻击者的来源。
  检测
  检测WinGate主机是否有这种安全漏洞的方法如下:
1) 以一个不会被过滤掉的连接(譬如说拨号连接)连接到因特网上。
2) 把浏览器的代理服务器地址指向待测试的WinGate主机。
  如果浏览器能访问到因特网,则WinGate主机存在着非授权Web访问漏洞。
2 非授权Socks访问
  在WinGate的缺省配置中,Socks代理(1080号Tcp端口)同样是存在安全漏洞。与打开的Web代理(80号Tcp端口)一样,外部攻击者可以利用Socks代理访问因特网。
  防范
  要防止攻击WinGate的这个安全脆弱点,管理员可以限制特定服务的捆绑。在多宿主(multi homed)系统上,执行以下步骤以限定如何提供代理服务。
1选择Socks或WWWProxyServer属性。
2选择Bindings标签。
3按下ConnectionsWillBeAcceptedOnTheFollowingInte***ceOnly按钮,并指定本WinGate服务器的内部接口。
  非授权Telnet访问
  它是WinGate最具威胁的安全漏洞。通过连接到一个误配置的inGate服务器的Telnet服务,攻击者可以使用别人的主机隐藏自己的踪迹,随意地发动攻击。
  检测
  检测WinGate主机是否有这种安全漏洞的方法如下:
1.使用telnet尝试连接到一台WinGate服务器。
[root@happy/tmp]#telnet172.29.11.191  
Trying172.29.11.191….  
Connectedto172.29.11.191.  
Escapecharacteris‘^]’.  
Wingate>10.50.21.5  
2.如果接受到如上的响应文本,那就输入待连接到的网站。
3.如果看到了该新系统的登录提示符,那么该服务器是脆弱的。
Connectedtohost10.50.21.5…Connected  
SunOS5.6  
Login:  
  对策
  防止这种安全脆弱点的方法和防止非授权Socks访问的方法类似。在WinGate中简单地限制特定服务的捆绑就可以解决这个问题。一般来说,在多宿主(multihomed)系统管理员可以通过执行以下步骤来完成:
1.选择TelnetSever属性。
2.选择Bindings标签。
3.按下ConnectionsWillBeAcceptedOnTheFollowingInte***ceOnly按钮,并指定本WinGate服务器的内部接口。
  五、后话
  有防火墙的攻击不单是上面的一点,我有什么写的不对的,大家指正。
  一直以来,黑客都在研究攻击防火墙的技术和手段,攻击的手法和技术越来越智能化和多样化。但是就黑客攻击防火墙的过程上看,大概可以分为三类攻击。
  第一类攻击防火墙的方法是探测在目标网络上安装的是何种防火墙系统并且找出此防火墙系统允许哪些服务。我们叫它为对防火墙的探测攻击。
  第二类攻击防火墙的方法是采取地址欺骗、TCP序号攻击等手法绕过防火墙的认证机制,从而 对防火墙和内部网络破坏。
  第三类攻击防火墙的方法是寻找、利用防火墙系统实现和设计上的安全漏洞,从而有针对性地发动攻击。这种攻击难度比较大,可是破坏性很大。

病毒分类及病毒命名规则详解 收藏

 很多时候大家已经用杀毒软件查出了自己的机子中了例如Rootkit.Vanti.zg、Trojan.Win32.SendIP.15等等这些一串英文还带数字的病毒名,这时有些人就懵了,那么长一串的名字,我怎么知道是什么病毒啊?

  其实只要我们掌握一些病毒的命名规则,我们就能通过杀毒软件的报告中出现的病毒名来判断该病毒的一些公有的特性了。

  世界上那么多的病毒,反病毒公司为了方便管理,他们会按照病毒的特性,将病毒进行分类命名。虽然每个反病毒公司的命名规则都不太一样,但大体都是采用一个统一的命名方法来命名的。

  一般格式为:<病毒前缀>.<病毒名>.<病毒后缀> 。
  病毒前缀是指一个病毒的种类,他是用来区别病毒的种族分类的。不同的种类的病毒,其前缀也是不同的。比如我们常见的木马病毒的前缀Trojan ,蠕虫病毒的前缀是Worm等等还有其他的。

  病毒名是指一个病毒的家族特征,是用来区别和标识病毒家族的,如以前著名的CIH病毒的家族名都是统一的“ CIH ”。

  病毒后缀是指一个病毒的变种特征,是用来区别具体某个家族病毒的某个变种的。一般都采用英文中的26个字母来表示,如Worm.Sasser.b就是指振荡波蠕虫病毒的变种B,因此一般称为“振荡波B变种”或者“振荡波变种B”。如果该病毒变种非常多(也表明该病毒生命力顽强^_^),可以采用数字与字母混合表示变种标识。

  综上所述,一个病毒的前缀对我们快速的判断该病毒属于哪种类型的病毒是有非常大的帮助的。通过判断病毒的类型,就可以对这个病毒有个大概的评估(当然这需要积累一些常见病毒类型的相关知识,这不在本文讨论范围)。而通过病毒名我们可以利用查找资料等方式进一步了解该病毒的详细特征。病毒后缀能让我们知道现在在你机子里呆着的病毒是哪个变种。

  下面附带一些常见的病毒前缀的解释(针对我们用得最多的Windows操作系统):

1、系统病毒

  系统病毒的前缀为:Win32、PE、Win95、W32、W95等。这些病毒的一般公有的特性是可以感染windows操作系统的*.exe和 *.dll文件,并通过这些文件进行传播。如CIH病毒。

2、蠕虫病毒

  蠕虫病毒的前缀是:Worm。这种病毒的公有特性是通过网络或者系统漏洞进行传播,很大部分的蠕虫病毒都有向外发送带毒邮件,阻塞网络的特性。比如冲击波(阻塞网络),小邮差(发带毒邮件)等。

3、木马病毒、黑客病毒

  木马病毒其前缀是:Trojan,黑客病毒前缀名一般为Hack 。木马病毒的公有特性是通过网络或者系统漏洞进入用户的系统并隐藏,然后向外界泄露用户的信息,而黑客病毒则有一个可视的界面,能对用户的电脑进行远程控制。木马、黑客病毒往往是成对出现的,即木马病毒负责侵入用户的电脑,而黑客病毒则会通过该木马病毒来进行控制。现在这两种类型都越来越趋向于整合了。这里补充一点,病毒名中有PSW或者什么PWD之类的一般都表示这个病毒有盗取密码的功能(这些字母一般都为“密码”的英文“password”的缩写)。

      4、脚本病毒

  脚本病毒的前缀是:Script。脚本病毒的公有特性是使用脚本语言编写,通过网页进行的传播的病毒,如红色代码。脚本病毒还会有如下前缀:VBS、JS(表明是何种脚本编写的。

5、宏病毒

  其实宏病毒是也是脚本病毒的一种,由于它的特殊性,因此在这里单独算成一类。宏病毒的前缀是:Macro,第二前缀是:Word、Word97、Excel、Excel97(也许还有别的)其中之一。凡是只感染WORD97及以前版本WORD文档的病毒采用Word97做为第二前缀,格式是:Macro.Word97;凡是只感染WORD97以后版本WORD文档的病毒采用Word做为第二前缀,格式是:Macro.Word;凡是只感染EXCEL97及以前版本EXCEL文档的病毒采用Excel97做为第二前缀,格式是:Macro.Excel97;凡是只感染EXCEL97以后版本EXCEL文档的病毒采用Excel做为第二前缀,格式是:Macro.Excel,依此类推。该类病毒的公有特性是能感染OFFICE系列文档,然后通过OFFICE通用模板进行传播。

6、后门病毒

  后门病毒的前缀是:Backdoor。该类病毒的公有特性是通过网络传播,给系统开后门,给用户电脑带来安全隐患。

7、病毒种植程序病毒

  这类病毒的公有特性是运行时会从体内释放出一个或几个新的病毒到系统目录下,由释放出来的新病毒产生破坏。

8.破坏性程序病毒

  破坏性程序病毒的前缀是:Harm。这类病毒的公有特性是本身具有好看的图标来诱惑用户点击,当用户点击这类病毒时,病毒便会直接对用户计算机产生破坏。

9.玩笑病毒

  玩笑病毒的前缀是:Joke。也称恶作剧病毒。这类病毒的公有特性是本身具有好看的图标来诱惑用户点击,当用户点击这类病毒时,病毒会做出各种破坏操作来吓唬用户,其实病毒并没有对用户电脑进行任何破坏。

10.捆绑机病毒

  捆绑机病毒的前缀是:Binder。这类病毒的公有特性是病毒作者会使用特定的捆绑程序将病毒与一些应用程序如QQ、IE捆绑起来,表面上看是一个正常的文件,当用户运行这些捆绑病毒时,会表面上运行这些应用程序,然后隐藏运行捆绑在一起的病毒,从而给用户造成危害。

  以上为比较常见的病毒前缀,有时候我们还会看到一些其他的,但比较少见,这里简单提一下:

DoS:会针对某台主机或者服务器进行DoS攻击;

Exploit:会自动通过溢出对方或者自己的系统漏洞来传播自身,或者他本身就是一个用于Hacking的溢出工具;

HackTool:黑客工具,也许本身并不破坏你的机子,但是会被别人加以利用来用你做替身去破坏别人。

你可以在查出某个病毒以后通过以上所说的方法来初步判断所中病毒的基本情况,达到知己知彼的效果。

第12章 Foxmail邮件转发器设计与实现

件系统经常会遇到以下问题:
1. 垃圾邮件,经常收到各类垃圾邮件,尤其是色情,股票等严重敏感的垃圾邮件。
2. 重要邮件经常无法发送至国外等地方。
3. 邮件系统不稳定,别人发过来的邮件经常丢失或退回。
4. 邮件系统暴露在Internet,经常受到外部攻击。
5. 带宽情况糟糕,尤其是国外地区的链路。
6. 邮件系统日志查询困难,无暇定期分析日志。

串联解决方案

clip_image026
华堂反垃圾邮件防火墙的邮件处理系统支持透明网桥的串联接入模式,在这种接入模式下,修改了用户的影环境,但几乎可以不改动当前的网络软环境。

并联解决方案

clip_image027
并联接入的优点是不破坏网络的拓扑结构,只是简单地增加一台设备。只要对用户的网络运行环境作一些软设置。为了防止绕过反垃圾邮件防火墙对邮件服务器的攻击,所以可能需要对防火墙和邮件服务器进行一些配置。例如,通过设置防火墙把SMTP的流量定向到反垃圾邮件服务器上进行处理,关闭对邮件服务器的直接SMTP访问。

第13章 基于Telnet的BBS客户端的设计和实现

作者介绍

posted @ 2011-01-16 09:46  Space Tian  阅读(3057)  评论(0编辑  收藏  举报