Visual C#网络编程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.2 网络编程常识

1.2.1 什么是网络编程

1.网络编程的概念

在今天的Web互联网时代,几乎所有的应用程序都运行在网上或具有网络功能,但并不是所有的程序开发都是网络编程,在此有必要特别澄清概念。

本书认为,只有主要实现进程(线程)相互之间的通信和基本的网络应用原理性(协议)功能的程序,才能算是真正的网络编程。例如,用TCP/UDP协议实现进程之间的网络会话,利用网络编程相关类和函数接口实现基本的网络聊天、浏览器、文件上传下载、电子邮件收发等不同类型网络应用的程序,甚至直接编写新的协议以创造出一种崭新的应用方式的程序……这些才是真正的网络编程。由此可见,网络编程是比较基础的,当下流行的Web开发是建立在它基础之上的高层次编程工作,而网页编程和设计则属于更高层次的艺术创造活动。

2.网络编程的层次

由于现实中的计算机互联网是按照“TCP/IP分层协议栈”的体系结构构建的,故从事网络编程的程序员首先必须搞清楚自己要做的是哪一个层次上的编程工作。在网络四层次体系结构中的三层已经有了成熟可靠的实现实体,如图1.21所示。

图1.21 TCP/IP协议体系的实现情况

其中,网络接口层已经被绝大多数计算机生产厂家集成在了主板上,就是经常所说的网卡(也称为网络接口卡NIC),除非是搞计算机硬件研发的人,否则一般学软件的人都不用管这一层是怎么做出来的;网际层和传输层也已经实现好了,Windows操作系统内核中就集成了TCP/IP协议的实现。TCP/IP协议的核心部分是传输层协议(TCP与UDP)、网际层协议(IP)。一般用户感受到的只有应用程序(包括系统应用程序)。那么,应用程序通过什么样的界面与内核打交道呢?通过编程界面(即程序员界面),如图1.21所示,各种应用程序,包括系统应用程序都是在此界面上开发的。

编程界面有两种形式:一种是由内核直接提供的系统调用,在Windows下表现为Windows API函数;另一种是以程序库方式提供的各种函数和类。MFC就是微软用C++语言对Windows API进行面向对象封装后形成的功能强大的类库。前者在核内实现,后者在核外实现。TCP/IP网络环境下的应用程序是通过网络(应用)编程界面(套接字,即Socket)实现的。用VC编程一般是使用MFC封装好的Socket类,而在C#和.NET中,不仅可以直接使用Socket类,还可以使用TcpListener、TcpClient和UdpClient等,它们都是.NET对普通Socket类的进一步封装。

3.本书所介绍的网络编程

编程是一项实践性很强的活动,需要学习者多多上机练习,这就要求所采用的编程环境必须是学习者能够经常接触和很容易获得的。在个人计算机市场上,95%以上的计算机都使用Windows操作系统,其中使用Windows XP的用户最多,达到了70%以上;即使是在一般的服务器端,Windows也占据了多达半数的市场份额,只有大型网络的高端服务器才会使用到UNIX、Solaris等操作系统,而这样的服务器普通人很难有机会见到,缺乏感性体验。

如前所述,.NET是当前Windows平台上最理想的编程环境,C#又是.NET的“第一语言”,也是Windows网络编程的最佳语言。鉴于目前PC市场Windows的绝对垄断地位,本书只介绍Windows环境下的网络编程,且采用最常见的Windows XP,使用Visual Studio 2008作为开发环境,选择C#这一最有前途的新型语言作为学习网络编程的入门语言。

另外,考虑到本书面向的是网络编程初学者而不是黑客或编程高手,普通人学习网络编程的目的主要还是想了解网络应用软件的工作原理,希望能够自己开发出实用的网络程序,并非为了达到寻找系统漏洞、研究底层协议和网络攻防等安全目的。为此,本书定位为网络应用开发的基础教程,介绍的内容主要包括:C#语言的基础特性(网络编程常用)、Socket传输编程(基础+应用),Internet应用编程(包括客户端开发和服务器端应用协议的编程),而并不涉及编程界面之下TCP/IP核心协议的编程,以便初学者快速入门并对Windows网络编程产生兴趣。

1.2.2 网络程序工作机制

在了解了什么是网络编程,以及明确了本书所要介绍的是哪一类网络编程之后,读者一定会问:究竟是什么让普通的单机程序具备丰富多样的网络功能的,在纷繁复杂的网络应用背后有没有什么共同的东西呢?网络应用程序都是运行在编程界面之上的,因此首先必须从这个编程界面入手,弄清Sockets是什么。

1.Windows Sockets简介

(1)Windows Sockets的概念。

Windows Sockets,顾名思义就是在Windows环境下使用的Sockets,那么Sockets又是什么呢?它是一套网络编程机制(或规范),常简称为Winsock。该规范是在20世纪90年代初制定的,是在Windows操作系统下得到广泛应用的、开放的、支持多种协议的网络编程接口。从1991年的1.0版到1997年的2.2.1版,经过不断完善并在Intel、Microsoft、Sun、SGI、Informix和Novell等公司的大力支持下,现已成为Windows环境下网络编程事实上的标准。

(2)Windows Sockets的来源。

Sockets本来是UNIX操作系统下流行的一种网络编程接口(API),它是1983 年在Berkeley(加州大学伯克利分校)的4.2 BSD操作系统中被首先引入的,因此被称为“Berkeley Socket API”。20世纪70年代,BSD作为一种主流的UNIX操作系统被广泛使用在各公司、大学的大中小型主机上,并经历了20世纪70~80年代全球范围的计算机网络互联高潮,在美国政府和军方的推动下,阿帕网以TCP/IP协议为规范与全美各个大公司、名牌大学的网络相连,因此“Berkeley Socket API”模型也就成了TCP/IP网络的编程接口标准。从4.2 BSD开始,Berkeley套接口API在不断地完善和发展,一直到1995年的4.4 BSD-Lite2为止。

Windows网络应用程序编程接口Windows Sockets API就是在1991年根据4.3 BSD操作系统的“Berkeley Socket API”制定的。

(3)Windows Sockets的版本。

目前常用的Winsock有两个版本:一个是16 位的Winsock 1.1,由动态链接库winsock.dll提供支持;另一个是32位的Winsock 2.2,由wsock32.dll提供支持。前者主要用在Windows早期的版本中,如Windows 95等,后者主要用在Windows 2000和Windows XP中。由于Winsock 2.2与Winsock1.1规范中所有的函数调用完全兼容,所以低版本的Winsock应用程序在Winsock 2库加载的情况下也能够正常运行。

2.网络程序的工作机制——网络上的进程通信

网络程序与传统单机程序的本质区别在于它能够与网络上其他计算机(主机)中的程序互通信息。因此,如何实现网络中不同主机上程序之间的通信也就成了网络程序实现的最最基础的技术。在同一台计算机的操作系统中,不同的两个进程间要进行通信时,通过系统分配的进程号(Process ID)就可以唯一标志某个进程,也就是说两个相互通信的进程,只要知道对方的进程号就可以进行通信。网络情况下进程间的通信问题要复杂得多,首先,要解决如何识别网络中不同主机的问题;其次,因各个主机系统中都独立地进行进程号分配,并且不同系统中进程号的产生与分配策略也不同,所以在网络环境中不能再简单地通过进程号来识别两个相互通信的进程了。

因此,为了标志通信的进程,首先要标志进程所在的主机,其次要标志主机上不同的进程。在互联网中使用IP地址来标志不同的主机,在网络协议中使用端口号识别主机上不同的进程。为了唯一地标志网络中的一个进程就要使用如下的二元组。

(IP地址,端口号)

这个二元组可以看做是网络进程地址,它是编程界面呈现给其上应用程序的“插口”,可以看成是两个网络应用进程在通信时,各自通信连接中的一个端点,所谓“端点”是个逻辑上的概念。通信时,其中一个程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过与网络接口卡(Network Interface Cards,NIC)相连的传输介质将这段信息发往另外一台主机的Socket中,使这段信息能够被其他程序使用,如图1.22所示。

图1.22 套接字工作原理

下面根据套接字工作原理分析使用套接字进行通信的过程。在图1.22 中,主机A上的网络应用程序A要发送数据时,首先通过调用发送函数,将要发送的一段信息写入其Socket中,Socket中的内容通过主机A的网络管理软件由主机A的网络接口卡发送到主机B,主机B的网络接口卡收到这段信息后,传送给自己的网络管理软件,网络管理软件将信息保存在主机B的Socket中,然后程序B才能从本地Socket中读取并使用这段信息。

从以上的通信过程可以看出,如果不考虑通信过程中的网络接口卡和传输介质等,网络通信的过程就是由数据的发送者将要发的信息写入一个套接字,在通过中间环节传输到接收端的套接字中后,就可以由接收端的程序将信息从本地套接字中取出。因此,两个应用程序之间的数据传输要通过套接字来完成。套接字实质上是网络进程通信过程中所要使用的一些缓冲区及其相关的数据结构。

为了满足不同程序对通信质量和性能的要求,一般的网络系统都提供了以下3种不同类型的套接字,以供用户在设计程序时根据不同需要来选择。

● 流式套接字(SOCK_STREAM)。提供了一种可靠的,面向连接的双向数据传输服务。实现了数据无差错、无重复的发送,内设流量控制,被传输的数据被看做无记录边界的字节流。在TCP/IP协议簇中,使用TCP实现字节流的传输,当用户要发送大批量数据,或对数据传输的可靠性有较高要求时使用流式套接字。

● 数据报套接字(SOCK_DGRAM)。提供了一种无连接、不可靠的双向数据传输服务。数据以独立的包形式被发送,并且保留了记录边界,不提供可靠性保证。数据在传输过程中可能会丢失或重复,并且不能保证在接收端数据按发送顺序接收。在TCP/IP协议簇中,使用UDP实现数据报套接字。

● 原始套接字(SOCK_RAW)。该套接字允许对较低层协议(如IP或ICMP)进行直接访问。一般用于对TCP/IP核心协议的网络编程(本书不涉及)。

可以说,网络上所用应用程序的通信都是基于这样的同一套套接字进行的,套接字在编程时对用户来说是可见的,图1.23所示是网络上两个Windows应用程序通过套接字通信的过程。由此也可以看出,套接字Winsock屏蔽了下面TCP/IP协议栈的复杂性,使得在网络编程者看来,两个程序之间的通信实质上就是它们各自所绑定的套接字之间的通信,这就使得网络程序的工作机制和编程模型变得十分简单且易于理解了。也正是这种简单而统一的界面,促进了丰富多样的网络应用软件之间的互联互通和交互操作,使得今天的互联网变得如此精彩。

图1.23 Windows程序通过套接字通信

1.2.3 网络应用编程界面

为了让大家更好地理解本书后续章节的内容,下面结合TCP/IP网络协议栈谈一谈有关图1.21中的那个编程界面,这也是本书后面学习网络编程时要时常打交道的部分。

1.TCP/IP协议栈

TCP/IP协议是当今信息社会整个全球互联网工作的基础,正是它架构了互联网全部的系统结构。该协议将网络功能自上而下划分成:应用层、传输层、网际层和网络接口层,如图1.21左所示。其实TCP/IP并不是一个单独的协议,而是由一系列网络协议所组成的协议集合(协议簇)。这个庞大的协议簇按照图1.24所示的分层结构组织起来构成的有机整体称为网络协议栈

下面分别介绍栈中各层的主要功能(自上而下)。

(1)应用层(Application Layer)。

应用层在TCP/IP协议簇的第4层,即最高层,它提供面向用户的网络服务,如进行文件的传输服务和远程登录服务等。不同的用户,对应用层服务的需求也不同,因此应用层定义了许多面向用户的,提供特定服务的协议。比较常用的有远程登录协议(Telnet)、文件传输协议(FTP)、超文本传输协议(HTTP)、域名系统(DNS)、简单网络管理协议(SNMP)、简单邮件传输协议(SMTP),邮局协议(POP3)和即时通信协议(OICQ)等。由于传输层可以使用TCP(Transmission Control Protocol,传输控制协议),也可以使用UDP(User Datagram Protocol,用户数据报协议),因此,有些应用层协议是基于TCP的(如FTP和HTTP等),有些则是基于UDP的(如SNMP和OICQ等)。

图1.24 TCP/IP协议栈

Socket支持两个应用进程之间最基本的消息传递功能,但是要实现具体的应用,还必须使这种消息传递过程按一定的规范进行。应用层上诸多的协议正是提供了这种规范,因此网络应用开发在本质上是遵循应用层上的某一种或几种协议的规范去编写 Socket 通信程序(即实现某个应用层协议)的过程。尽管应用层提供了较多通用的标准协议,但这些协议只能满足用户在一般情况下使用网络的需求,如果用户要在网络上进行一些特殊的应用,如网吧管理或一个公司内部使用的邮件系统等,应用层并没有提供这样的协议,这就要由用户根据自己的实际需要开发所需的新应用协议了。

(2)传输层(Transport Layer)。

应用层之下是传输层,有的书上也称为“运输层”。在TCP/IP协议簇中,传输层处于第3层。传输层完成两台主机之间的通信,其实质是两台主机上对应的应用进程之间的通信(大家熟知的简单Socket程序所实现的正是这种通信),也叫端到端(End to End)通信。端到端通信是在传输层两个通信实体(进程或线程)之间进行的,就好像是在两个实体之间建立了一条逻辑通路,它屏蔽了IP层的路由选择和物理网络等细节。

在实际通信过程中,进程对通信质量的要求是不一样的。为了满足不同的需要,在TCP/IP协议簇中该层定义了两个不同的协议:一个是TCP,另一个是UDP。

TCP为两台主机提供高可靠性的数据通信服务,它可以将源主机的数据流无差错地传输到目标主机。当有数据要发送时,对应用进程送来的数据进行分片,以适合于在网络层中传输;当接收到网络层传来的分组时,它要对收到的分组进行确认,还要对丢失的分组设置超时重发等。为此TCP需要增加额外的许多开销,以便在数据传输过程中进行一些必要的控制,确保数据的可靠传输。

UDP则为应用层提供一种非常简单的服务,它只是把称做数据报的分组从一台主机发送到另一台主机,并不保证该数据报能够正确到达目标端,通信的可靠性必须由相应的应用程序提供。

综上所述,TCP和UDP各有特点。TCP可以确保数据传输的可靠性,但由于需要额外的开销,所以数据传输的效率比较低;UDP虽然不能保证数据传输的可靠性,但数据传输的效率较高。用户在开发程序时,可以根据实际情况,选用TCP或UDP进行数据传输。

(3)网际层(Internet Layer)。

网际层在协议栈的第2层,也称为互联网络层、互联层或Internet层,它是整个协议栈中最重要的一层,因该层的主要协议是IP,所以也简称为IP层。在网际层传输的数据单元叫IP数据报,也称为IP分组。网际层的主要功能是把源主机上的分组根据需要发送到互联网中的任何一台目标主机上。当然,发送信息的源主机必须知道接收信息的目标主机的“地址”。

通信时,源主机与目标主机可以在同一个网络中,也可以在不同的网络中。在一个由很多网络组成的互联网中,一台主机(即源主机)与不在同一个网络中的另一台主机(目标主机)通信时,可能有多条通路相连,网际层的一个重要功能就是要在这些通路中做出选择,也就是所谓的路由选择功能。路由选择是网际层一个非常重要的功能。

网际层的本质是使用IP将各种不同的物理网络互联,组成一个传输IP数据报的虚拟网络,所以网际层实现了不同网络的互联功能。但网际层提供的是一种“尽力而为”的数据报传输服务,它不能保证数据总是可靠地从源主机传输到目标主机。在TCP/IP协议簇中,网际层协议包括IP(网际协议)、Internet控制报文协议(Internet Control Message Protocol,ICMP)和Internet组管理协议(Internet Group Management Protocol,IGMP)等。

(4)网络接口层(Host to Net Layer)。

网络接口层处于协议栈最底层,它负责将其之上网络层要发的数据(即IP数据报)发送到其下的物理网络,或接收由物理网络送到该目标机的数据帧,并抽出IP数据报交给网际层。要注意,这里所说的物理网络是指各种实际传输数据的局域网或广域网等。

在TCP/IP协议栈中并没有具体定义网络接口层的内容。一般情况下,只要是在其上能进行IP数据报传输的网络都可以当成TCP/IP协议栈接口的网络,这也就是图1.24中用虚线框画出它的原因。

对于日常使用的计算机来说,厂家都会把以太网MAC协议做在网卡适配器中。鉴于目前所有的局域网已经几乎都是以太网的现状,普通人的计算机一般都会支持以太网的两个兼容协议——DIX Ethernet V2和IEEE 802.3系列。广大上网用户要么是通过局域网接入Internet,要么就是在自己家里装宽带,使用ISP(电信运营商)提供的接口,后者几乎无一例外都遵循点对点协议PPP。宽带用户的PPP协议是在以太网上运行的,故称为PPPoE。宽带用户的计算机都是通过PPPoE接入运营商的各种网络的……综合上述这些现实,在图1.24中网络接口层只标出了DIX Ethernet V2、IEEE 802.3和PPPoE这3个协议。

为什么在TCP/IP协议栈中没有定义网络接口层呢?这是因为不定义网络接口层的具体内容有如下两点好处。

● 便于实现不同网络之间的互联。实现不同网络的互联是TCP/IP要解决的最主要问题。不同的网络尽管其数据传输介质、传输速率等有很大的差异,但都可以实现网络内数据的传输,当然也可以进行TCP/IP协议栈网际层IP数据报的传输。这样,TCP/IP就可以将重点放在网络之间的互联上,而不用去纠缠各种物理网络的具体实现细节,从而非常巧妙地解决了不同类型物理网络的互联问题。这也是TCP/IP得以广泛应用的一个重要原因。

● 为将来物理网络的发展留下了广阔的空间。物理网络与计算机硬件技术和通信技术的发展紧密相连,尽管物理网络的数据传输率在不断提高,网络设备的性能也在不断增强,但所有这些都不会影响到TCP/IP的使用。

2.编程界面

在了解TCP/IP协议栈的体系结构后,读者已经知道,网络应用开发实质上就是实现某种应用层协议(标准的通用协议或自己设计创造的协议)。随着互联网应用多样化的发展,应用层协议处于快速地更新换代之中,不断有新协议被设计出来并投入使用。虽然那些通用的协议(如HTTP、FTP和SMTP等)基本不变,但不同的软件开发商在实现自己产品的时候都会或多或少地对它们加以适当改造和扩展,至于具体实现的方式更是千差万别了!因此从这个意义上说,协议栈的顶层(应用层)是不稳定的。鉴于这种状况,肯定不能把应用层作为网络应用开发的通用平台。

再来看协议栈的最底层(网络接口层),为了适应通信技术的日新月异和物理网络的多样性,TCP/IP根本就没有对该层做任何具体的规定。

这样一来,四层协议栈只有两层是稳定的并且有具体的标准规范,也就是传输层网际层。这两层中的协议是在同一时期作为协议整体设计出来的,它们几乎伴随着互联网的诞生而产生,几十年来没有多大变动并且都已有了很成熟的实现。基于此,本书将传输层的TCP和UDP加上网际层的IP,一共是3 个协议合起来作为互联网的核心协议(ICMP/IGMP也是IP封装的,可看做IP的扩展协议)。

如此看来,“TCP/IP协议”这个说法是不准确的,准确而全面的称呼应该是“TCP/UDP/IP协议”。但长久以来,人们已经习惯于说互联网使用的是“TCP/IP协议”,也就没有必要硬是改名称了。但读者心里必须十分清楚:互联网的核心协议是3个,而且只有这3个,它们是——TCPUDPIP,通常将它们看做一个整体,称做“TCP/IP核心协议栈”,这是整个互联网的灵魂之所在。

几乎所有的主流操作系统(Windows、Linux/UNIX和Mac OS X等)都在自己的内核中实现了TCP/IP核心协议栈。大家平时上网的时候,如果单击桌面右下角任务栏上表示网络连接的计算机形图标,在弹出的网络连接状态对话框中单击“属性”按钮,就能在连接使用的项目列表中看到已经内置于操作系统之中的TCP/IP了,如图1.25 的框中所示。

TCP/IP核心协议栈作为网络应用开发通用的基础平台,为开发应用的程序员提供服务。在很多书籍文献资料中(甚至一般场合),人们所说的“TCP/IP协议”往往指的就是这个核心协议栈,而并不包括应用层诸多协议和最下面的网络接口层,这一点请大家务必注意。

图1.25 内置于Windows之中的TCP/IP

TCP/IP核心协议栈虽然为上层网络应用开发人员提供服务,但由于它自身内部的工作机制比较复杂,开发人员一般不会直接使用它,而是通过操作系统提供的接口使用这个平台。Windows在实现的时候已经将TCP/IP做到内核里了,并实现了一组接口供程序员使用其功能,这个接口正是前面介绍的Socket。

Socket最初是以Windows API(Win 32 API)的形式提供的,所以又叫Windows Socket API,简称为Winsock API。由于面向对象编程方式早已成为软件开发主流,原来的Windows API接口越来越显得与面向对象思想格格不入了。于是,微软与时俱进地将Windows API重新进行了面向对象的封装,将它们包装成一个个类供用户使用。在C++中,这些类的全体集合是MFC;而在C#中,则是.NET。因此也可以认为.NET是C#的类库,但作为微软的新一代开发平台,.NET又不仅仅局限于支持C#,它同时也是其他高级语言(VB.NET、VC++.NET和ASP.NET)的“公共基础设施”,这样看来,C#与.NET又是各自独立的。

于是,原来的Winsock API就封装在.NET之中了,经由 .NET框架封装的套接字类有TcpListener、TcpClient、UdpClient和NetworkStream等,它们都位于C#库的System.Net.Sockets命名空间下,在C#程序中可以直接使用它们。在C#编程中,.NET对套接字封装后的类集合构成了程序员直接可见的网络应用编程界面,而这个界面之下的一切则隐藏在神秘的OS内核之中,对应用开发人员是透明的,如图1.26所示。

从图1.26可见,传输层的TCP协议被Winsock封装(⑬)为SOCK_STREAM(流式套接口),UDP则被封装(⑭)为SOCK_DGRAM(数据报套接口)。开发一般的聊天室软件时直接使用.NET封装好的Socket类(⑨)。应用层中几个比较常用的协议(HTTP/FTP/SMTP/POP3/IMAP等)都是通过SOCK_STREAM(⑪)使用TCP协议的功能。常用的即时通信软件(QQ、MSN和Skype等)则是基于一类叫做OICQ的协议(⑩),OICQ是无连接应用,使用传输层UDP协议的服务,它通过SOCK_DGRAM使用UDP(⑫)。

图1.26 TCP/IP体系中的网络应用编程界面

虽然应用层本身是不稳定的,但其中仍然有不少经常使用的比较通用和基础的协议,如图1.26画出的这几个。但这些协议的标准本身仍然比较复杂,为了给Internet应用开发程序员提供最大可能的方便,很多著名软件厂商都对这些协议进行了再封装,用类库或函数库的形式先将这些协议实现好再提供接口供程序员使用。例如,在.NET平台上,微软将HTTP和FTP协议统一封装成WebRequest和WebResponse类,再由这两个类的派生类分别实现具体协议的特定功能,这些类都位于命名空间System.Net下;将邮件应用类协议(SMTP/POP3/IMAP)封装成一系列邮件收发相关类,内置于System.Net.Mail命名空间供用户使用,如图1.26应用层中虚框所示,经过.NET这样一包装,C#程序员在从事Internet编程时就可以完全不考虑具体应用层协议的实现细节,而能快速轻易地开发出功能强大的Internet常用软件。

在应用层之上跑的就是各种网络应用软件程序(当然也包括用户自己编写的)。本书后面将要实现的浏览器间接通过.NET的WebBrowser组件(①)和System.Net中的类(②)使用HTTP;FTP上传下载工具也是用的System.Net内置类(④)使用FTP;邮件应用类程序则是通过System.Net.Mail(⑦)使用SMTP、POP3等协议。当然Outlook是直接实现SMTP、POP3这些邮件类协议收发邮件的(⑥),新浪、网易等主要运营商的邮件服务系统也是直接实现的邮件应用协议(⑧)。

通常服务器端直接实现应用协议(③、⑤)为广大网络用户提供服务,而开发客户端程序则只需使用.NET平台内置现成的网络编程功能就可以了,故网络编程分为客户端编程和服务器编程。一般来说,服务器端的编程由于涉及应用层协议的工作细节和具体实现,故它较之于客户端编程要复杂很多。

本书就将在图1.26所展示的编程界面的基础上,由浅入深,分类探讨在.NET平台上用C#语言进行各种应用开发型网络编程的知识和技巧。