《CSharp第04章套接字与数据流.ppt》由会员分享,可在线阅读,更多相关《CSharp第04章套接字与数据流.ppt(59页珍藏版)》请在三一办公上搜索。
1、第4章 套接字与数据流,4.1 套接字4.1.1 面向连接的套接字 4.1.2 无连接的套接字4.2 数据流4.2.1 文件流4.2.2 内存流4.2.3 网络流4.2.4 StreamWriter与StreamReader类4.2.5 BinaryReader与BinaryWriter类,本章教学目的学习套接字编程的基本方法,以及数据流相关类的用法。本章教学要求(1)掌握套接字的概念及Socket类用法;(2)掌握面向连接套接字编程、无连接套接字编程基本步骤(3)掌握FileStream、MemoryStream、NetworkStream类的用法;(4)掌握StreamReader、Str
2、eamWriter、BinaryReader、BinaryWriter类的基本用法。,本章重点,4.1 套接字,一个连接由它的两个端点标识,这样的端点称为套接字。,4.1 套接字(续),套接字是支持TCP/IP协议的网络通信的基本操作单元。可以将套接字看作不同主机间的进程进行双向通信的端点。上图连接1的一对套接字为:(,5000)和(,8888)上图连接2的一对套接字为:(,5001)和(,8888)对于UDP协议尽管两个进程之间没有建立连接,但是也同样存在发送端点和接收端点,也同样使用套接字的概念。,4.1 套接字(续),套接字的类型有:流式套接字:提供了面向连接的、可靠的、数据无错并且无重
3、复的数据发送服务,而且接收数据的顺序和发送数据的顺序是相同的。流式套接字用来实现TCP通信。数据报套接字:提供了面向无连接的服务,它以独立的数据包形式发送数据(数据包长度不能大于32KB),不提供正确性检查,也不保证各数据包的发送顺序和接收顺序相同,因此,可能出现数据的重发、丢失等现象。数据报套接字用来实现UDP通信。原始套接字:用于直接访问协议的较低层。常用于检验新的协议实现或访问现有服务中配置的新设备,一般不提倡直接使用原始套接字。原始套接字用来实现IP数据包通信。,4.1 套接字(续),Socket类包含在命名空间中。一个Socket实例包含了一个本地或者一个远程端点的套接字信息。使用S
4、ocket类编程,由于很多细节都需要自己考虑,相对来说复杂一些,易出错。一般对套接字编程比较熟悉的人,或者使用非标准协议(自定义的新协议)进行编程的时候,才使用Socket类。Socket类的构造函数为:public Socket(AddressFamily addressFamily,/网络类型 SocketType socketType,/套接字类型 ProtocolType protocolType);/使用的协议,4.1 套接字(续),参数含义:(1)addressFamily addressFamily表示网络类型,该参数使用AddressFamily枚举指定Socket使用的寻址方
5、案例如AddressFamily.InterNetwork表示IP版本4的地址。,4.1 套接字(续),4.1 套接字(续),(2)socketTypesocketTyp指定Socket的类型,该参数使用SocketType枚举指定使用哪种套接字。例如:SocketType.Stream表明连接是基于流套接字SocketType.Dgram表示连接是基于数据报套接字SocketType.Raw表示连接基于原始套接字;详细见课本表4-2,4.1 套接字(续),(3)protocolTypeprotocolType指定Socket使用的协议,该参数使用ProtocolType枚举指定使用哪种协议。
6、例如:ProtocolType.Tcp表明连接协议是TCPProtocolType.Udp表明连接协议是UDP详细见课本表4-3,4.1 套接字(续),Socket构造函数的三个参数中,对于网络上的IP通信来说,AddressFamily总是使用AddressFamily.InterNetwork枚举值。而SocketType参数则与ProtocolType参数配合使用,不允许其他的匹配形式,也不允许混淆匹配。下表列出了可用于IP通信的组合。,4.1.1 面向连接的套接字,IP连接领域有两种通信类型:面向连接的(connection-oriented)无连接的(connectionless)。
7、根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听客户端请求连接确认,4.1.1 面向连接的套接字,服务器监听:是指服务器套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器套接字的地址和端口号,然后再向服务器套接字提出连接请求。连接确认:是指当服务器套接字监听到客户端套接字的连接请求时,它就响应客户端套接字的请求,把服务器套接字的信息发给客户端,一旦客户端确认了此信息,连接即可建立。
8、而服务器套接字继续监听其他客户端套接字的连接请求。,4.1.1 面向连接的套接字,1.创建本地Socket,2.调用Bind方法绑定到本地端点,3.在指定端口监听Listen,1.创建本地Socket,2.Connect连接请求,4.Accept(),5.收发数据,3.收发数据,6.Close(),4.Close(),服务器,客户端,4.1.1 面向连接的套接字,4.1.1 面向连接的套接字,同步TCP编写服务器端程序的一般步骤为:1)创建一个包含采用的网络类型、数据传输类型和协议类型的本地套接字对象,并将其与服务器的IP地址和端口号绑定。这个过程可以通过Socket类。2)在指定的端口进行监
9、听,以便接受客户端连接请求。3)一旦接受了客户端的连接请求,就根据客户端发送的连接信息创建与该客户端对应的Socket对象。4)根据创建的Socket对象,分别与每个连接的客户进行数据传输。5)根据传送信息情况确定是否关闭与对方的连接。,4.1.1 面向连接的套接字,使用同步TCP编写客户端程序的一般步骤为:1)创建一个包含传输过程中采用的网络类型、数据传输类型和协议类型的Socket对象。2)与远程服务器建立连接。3)与服务器进行数据传输。4)完成工作后,向服务器发送关闭信息,并关闭与服务器的连接。,4.1.1 面向连接的套接字(续),1.建立连接(服务器)IPHostEntry local
10、=Dns.GetHostByName(Dns.GetHostName();IPEndPoint iep=new IPEndPoint(local.AddressList0,1180);Socket localSocket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);localSocket.Bind(iep);localSocket.Listen(10);Socket clientSocket=localSocket.Accept();,4.1.1 面向连接的套接字(续),1.建立连接(客户端
11、)IPAddress remoteHost=IPAddress.Parse(192.168.0.1);IPEndPoint iep=new IPEndPoint(remoteHost,1180);Socket localSocket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);localSocket.Connect(iep);,4.1.1 面向连接的套接字(续),2.发送、接收信息(服务器)Socket clientSocket=localSocket.Accept();/建立连接后,利用S
12、end方法向客户端发送信息clientSocket.Send(Encoding.ASCII.GetBytes(server send Hello);/接收客户端信息byte myresult=new Byte1024;int receiveNum=clientSocket.Receive(myresult);Console.WriteLine(接收客户端消息:0,Encoding.ASCII.GetString(myresult);,4.1.1 面向连接的套接字(续),2.发送、接收信息(客户端)/建立连接成功后,向服务器发送信息string sendMessage=client send M
13、essage Hello+DateTime.Now;localSocket.Send(Encoding.ASCII.GetBytes(sendMessage);Console.WriteLine(向服务器发送消息:0,sendMessage);/接收服务器信息byte result=new Byte1024;localSocket.Receive(result);Console.WriteLine(接收服务器消息:0,Encoding.ASCII.GetString(result);,4.1.1 面向连接的套接字(续),3.关闭连接通信完成后,必须先用Shutdown方法停止会话,然后关闭So
14、cket实例。例如:sock.Shutdown(SocketShutdown.Both);sock.Close();,4.1.1 面向连接的套接字(续),面向连接的套接字编程举例(控制台应用程序)一对一通信举例 课堂演示(书上代码组合)一对多通信举例【例题4-1】使用同步Socket实现客户端与服务器端的消息通信。其中,服务器可以与多个客户端通信,并随时接收客户端发送的消息。使用控制台应用程序,编写代码简单;但是发送消息内容不可变,界面不美观。,4.1.1 面向连接的套接字(续),一对一通信(Socket实现控制台应用程序)服务器端和客户端可以不开辟线程直接通信,也可以开辟线程进行通信;开辟线
15、程实现时:服务器端(12个线程)接收线程1个(也可以没有)收消息线程1个客户端(1个线程)接收消息线程1个注意:本章内所说的线程个数只涉及基本通信,不考虑其他情况下开辟的线程。,4.1.1 面向连接的套接字(续),一对多通信(Socket实现控制台应用程序)服务器端(开辟线程数1+n个):接收连接线程1:负责接收客户端的连接;接收消息线程(1-n):负责接收来自客户端的消息;n表示与服务端相连的客户端的个数;客户端(开辟线程数1个):接收消息线程1:负责接收来自服务器的消息。,4.1.1 面向连接的套接字(续),面向连接套接字通信举例(windows Form版)1对1通信【补充例题E01-S
16、ocketCommunication(1Vs1)】1对多通信【补充例题E01-SocketCommunication(1VsN)】创建windows Froms应用程序时,界面美观、消息内容可自定义;但需要考虑跨线程操作界面控件;这种程序是实际应用中最常用的。,1对1通信(winForm版)程序运行界面截图,1对多通信(winForm版)程序运行界面截图,4.1.1 面向连接的套接字(续),一对一通信(Socket实现WinForm应用程序)服务器端(开辟线程数2个)接收连接线程1:负责接收客户端的连接;接收消息线程1:负责接收来自客户端的消息;客户端(开辟线程数1个)接收消息线程1:负责接收
17、来自服务器的消息。一对多通信(Socket实现WinForm应用程序)服务器端(开辟线程数1+n个):接收连接线程1+接收消息线程n个客户端(开辟线程数1个):接收消息线程1个,4.1.2 无连接的套接字,UDP使用无连接的套接字,无连接的套接字不需要在网络设备之间发送连接信息。注意:必须使用Bind方法将套接字绑定到一个本地地址和端口之后才能使用ReceiveFrom方法接收数据,如果只发送而不接收,则不需要使用Bind方法。,4.1.2 无连接的套接字,1.创建本地Socket,2.调用Bind方法绑定到本地端点,3.接收数据ReceiveFrom,1.创建本地Socket,4.发送数据S
18、endTo,4.接收数据ReceiveFrom,5.Close(),5.Close(),机器1,机器2,2.调用Bind方法绑定到本地端点,3.发送数据SendTo,4.1.2 无连接的套接字,4.2 数据流,流(stream)是对串行传输的数据(以字节为单位)的一种抽象表示,底层的设备可以是文件、外部设备、主存、网络套接字等。,4.2 数据流,流提供三种基本操作:写入:将数据从内存缓冲区传输到外部源。读取:将数据从外部源传输到内存缓冲区。查找:重新设置流的当前位置,以便随机读写。需要注意的是,并不是所有的流类型都能够支持查找,例如,网络流没有当前位置的统一概念,因此一般不支持查找。说明:St
19、ream类提供有多种操作流的方法,其中Read和Write方法是Stream类及其派生类都提供的实现,可支持在字节级别上对数据进行读写。实际的程序开发中,仅支持字节级别的数据处理会给开发人员带来不便。,4.2 数据流,.NET Framework提供一些类能够以字符串或二进制方式读取或写入流。优点:方法更灵活;部分方法可解决TCP消息通信无边界问题;,4.2.1 FileStream类,FileStream类继承于Stream类,一个FileStream类的实例实际上代表一个磁盘文件,使用FileStream类可以对文件系统上的文件进行读取、写入、打开和关闭操作。1、创建FileStream实
20、例(1)常用的构造函数具有三个参数,例如:FileStream(string path,FileMode mode,FileAccess access),4.2.1 FileStream类,FileMode值用于指定当文件不存在时是否创建该文件,并确定是保留还是改写现有文件的内容,4.2.1 FileStream类,FileAccess值是枚举的一个成员,它控制对文件的访问权限。表4-10列出了FileAccess所有的枚举形式(2)File和FileInfo类也提供了创建FileStream对象的方法。其中,OpenRead方法返回只读文件流,OpenWrite方法返回只写文件流。例如:Fi
21、leStream fs=File.OpenRead(C:File1.txt);,4.2.1 FileStream类,2.读文件 在获取FileStream实例之后,可利用FileStream对象的Read方法读取文件中的数据。该方法用于从流中读取字节块并将该数据写入给定字节数组中。其语法形式为:public override int Read(byte array,int offset,int count)array:存储从文件流中读取的数据。offset:array字节数组中开始写入数据的下标,一般为0。size:要从文件流中读出字节的大小 返回值:从FileStream中读取的字节数。【例
22、4-2】利用FileStream的Read方法从一个文本文件中读取内容并显示在屏幕上。,4.2.1 FileStream类,3.写文件Stream类及其所有子类都提供了Write方法,FileStream类也不例外。该方法可将字节数组写入流。语法形式为public override void Write(byte buffer,/包含要写入流的数据int offset,/buffer中开始写入数据的位置int size/要写入流的字节数)【例4-3】利用FileStream的Write方法向文本文件中追加数据。,4.2.2 MemoryStream类,MemoryStream类表示的是保存在内
23、存中的数据流。由内存流封装的数据可以在内存中直接访问。MemoryStream类的构造函数具有多种重载形式,常用的构造函数有:(1)MemoryStream()该构造函数初始分配的容量大小为0,随着数据的不断写入容量可以不断扩展。(2)MemoryStream(Byte)该构造函数获取的MemoryStream实例根据Byte字节数组进行初始化,并且实例容量大小固定即为字节数组的长度。由于实例的容量不能扩展,该构造函数一般用于数据不发生变化的场合。,4.2.2 MemoryStream类,String testdata=测试数据;char chars=testdata.ToCharArray(
24、);Byte bytes=new Byteencoder.GetByteCount(chars,0,chars.Length,true);MemoryStream mem=new MemoryStream(bytes);或者:byte bytes=Encoding.Unicode.GetBytes(aaasd);MemoryStream mem=new MemoryStream(bytes);(3)MemoryStream(int capacity)通过该构造函数创建初始容量大小为capacity的实例,并且实例容量大小可扩展。【例4-4】利用MemoryStream暂存数据。,4.2.3 网
25、络流,在名称空间中有一个NetworkStream类,用于通过网络套接字发送和接收数据。NetworkStream类支持对网络数据的同步或异步访问,它可被视为在数据来源端和接收端之间架设了一个数据通道.NetWorkStream只用于面向连接的数据传输,写入操作是指从来源端内存缓冲区到网络上的数据传输;读取操作是从网络上到接收端内存缓冲区(如字节数组)的数据传输。,4.2.3 网络流(续),4.2.3 网络流(续),NetworkStream的用法1、构造NetworkStream:(1)利用TcpClient获取网络流对象,例如:TcpClient tcpClient=new TcpClie
26、nt();tcpClient.Connect(,51888);NetworkStream networkStream=client.GetStream();(2)利用Socket获取网络流对象,例如:NetworkStream myNetworkStream=new NetworkStream(mySocket);,4.2.3 网络流(续),2、发送数据 public override void Write(byte buffer,int offset,int size),4.2.3 网络流(续),3、接收数据public override int Read(InAttribute OutAt
27、tribute byte buffer,int offset,int size)各参数的含义:buffer:内存中用于存储从NetworkStream读取的数据的位置。offset:buffer 中开始将数据存储到的位置。Size:要从NetworkStream中读取的字节数。返回值:实际从NetworkStream中读取的字节数。,4.2.3 网络流(续),Write方法:NetworkStream对象的Write方法的返回值为void,该对象之所以不返回实际发送的字节数,是因为能保证字节数组中的数据全部发送到TCP发送缓冲区中。在使用NetworkStream对象的Write方法前最好先检
28、测NetworkStream对象的Writeable属性是否为True。如果发送的全部是单行文本信息,创建NetworkStream对象后,使用StreamReader和StreamWriter的ReadLine和WriteLine方法更简单。,4.2.3 网络流(续),Read方法:调用NetworkStream类的Read方法前应确保NetworkStream对象的CanRead属性值有效由于有可能TCP接收缓冲区还没有接收到对方发送过来的指定长度的数据,所以Read方法有一个整型的返回值。如果远程主机关闭了套接字连接,并且此时有效数据已经被完全接收,那么Read方法的返回值将会是0字节。
29、,4.2.3 网络流(续),使用NetworkStream对象时,需要注意:(1)通过DataAvailable属性,可以迅速查看在缓冲区中是否有数据等待读出。(2)网络流没有当前位置的概念,因此它不支持对数据流的查找和随机访问,NetworkStream对象的CanSeek属性始终返回false,读取Position属性和调用Seek方法时,都会引发NotSupportedException异常。(3)网络数据传输完成后,不要忘记用Close方法关闭NetworkStream对象。,4.2.4 StreamWriter与StreamReader类,可以看到所有的NetworkStream、M
30、emoryStream、FileStream类都提供了以字节为基本单位的读写方法,但是这种方法需要首先将待写入的数据转化为字节数组后才能够进行读写,当操作的是使用字符编码的文本数据时,给开发人员带来了不便。StreamReader类主要完成以一种特定的编码从流中读取字符的功能,一般用于对文本数据的读取操作;StreamWriter类则主要以特定的编码向流中写入字符,一般用于对文本数据的写操作。,4.2.4 StreamWriter与StreamReader类,1、创建StreamWriter实例(1)StreamWriter(String path)根据文件路径创建以UTF-8编码Stream
31、Writer对象。例如:StreamWriter sw=new StreamWriter(C:file1.txt);(2)File及FileInfo类提供的CreateText方法。例如:StreamWriter sw=File.CreateText(C:file1.txt);,4.2.4 StreamWriter与StreamReader类,1、创建StreamWriter实例(续)(3)StreamWriter(Stream stream)直接使用流对象创建StreamWriter对象。例如:FileStream fs=new FileStream(filePath,FileMode.Op
32、en,FileAccess.ReadWrite);StreamWriter sw=new StreamWriter(fs);如果已经有了网络流对象,同样可以直接对网络流对象进行封装。NetworkStream networkStream=client.GetStream();StreamWriter sw=new StreamWriter(networkStream);,4.2.4 StreamWriter与StreamReader类,2、写入文本 利用StreamWriter类将以一种特定的编码向流中写入字符。常见方法如下所示:Write():向数据流写入数据。Write()方法只是把传送给
33、它的字符串写入流,但不追加换行符,因此可以使用Write()语句写入完整的句子或段落。WriteLine():向数据流写入指定数据和一个换行符。Close():关闭流。【例4-5】利用StreamWriter类向C盘中的文本文件file1.txt中写入一行数据。,4.2.4 StreamWriter与StreamReader类,3.创建StreamReader实例(1)SteamReader(Stream stream)利用流对象创建SteamReader对象。例如:NetworkStream networkStream=client.GetStream();SteamReader sr=ne
34、w SteamReader(networkStream)(2)SteamReader(String path)如果需要处理的是文件流,则可以根据文件路径创建一个以UTF8编码的SteamReader对象。例如:SteamReader sr=new SteamReader(C:file1.txt);,4.2.4 StreamWriter与StreamReader类,读取文本利用StremReader将以一种特定的编码从流中读取字符。常用方法有:ReadLine():读取数据直到遇到换行符(Unix)或者回车换行(Windows)为止。ReadToEnd():读取到文件尾的全部数据。Peek():
35、返回数据中下一个可用字符的编码值,如到达文件末尾则Peek()方法的值为-1。Close():关闭流,使用StreamReader之后,需要调用Close方法关闭流。【例4-6】利用StreamReader类将文本文件中的内容在控制台中显示。,4.2.5 BinaryReader和BinaryWriter类,BinaryReader和BinaryWriter类,用于以二进制模式读写流。它们提供的一些读写方法是对称的,比如针对不同的数据结构,BinaryReader提供了ReadByte、ReadBoolean、ReadInt、ReadInt16、ReadDouble、ReadString等方法,与之对应BinaryWriter则提供了多个重载Write方法。例如当Write方法传递的参数为Int32类型时,BinaryWriter类的Write方法将Int32类型数据转化为长度为4的字节数组,并将字节流传递给一个Stream对象。方法详细说明参看课本表4-13【例4-7】向二进制文件中写入数据。,本章习题及实验,习题:习题2、习题4实验:本章无实验。,