1 / 59

第 4 章 套接字与数据流

第 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 类. 4.1 套接字. 套接字是支持 TCP/IP 协议的网络通信的基本操作单元。可以将套接字看作 不同主机间的进程进行双向通信的 端点 。

isra
Download Presentation

第 4 章 套接字与数据流

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  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类

  2. 4.1 套接字 套接字是支持TCP/IP协议的网络通信的基本操作单元。可以将套接字看作不同主机间的进程进行双向通信的端点。 对于UDP协议尽管两个进程之间没有建立连接,但是也同样存在发送端点和接收端点,也同样使用套接字的概念。

  3. 4.1 套接字(续) 套接字的类型有: 流式套接字:提供了面向连接的、可靠的、数据无错并且无重复的数据发送服务,而且接收数据的顺序和发送数据的顺序是相同的。流式套接字用来实现TCP通信。 数据报套接字:提供了面向无连接的服务,它以独立的数据包形式发送数据(数据包长度不能大于32KB),不提供正确性检查,也不保证各数据包的发送顺序和接收顺序相同 ,因此,可能出现数据的重发、丢失等现象。数据报套接字用来实现UDP通信。 原始套接字:用于直接访问协议的较低层。常用于检验新的协议实现或访问现有服务中配置的新设备,一般不提倡直接使用原始套接字。原始套接字用来实现IP数据包通信。

  4. 4.1 套接字(续) .NET Framework中的System.Net.Sockets命名空间下的Socket类可以实现套接字。 一个Socket实例包含了一个本地或者一个远程端点的套接字信息。 使用Socket类编程,由于很多细节都需要自己考虑,相对来说复杂一些,易出错。一般对套接字编程比较熟悉的人,或者使用非标准协议(自定义的新协议)进行编程的时候,才使用Socket类。

  5. 4.1 套接字(续) 1. Socket对象的创建 Socket类的构造函数: public Socket( AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType); 参数含义: (1)addressFamily addressFamily表示网络类型,该参数使用AddressFamily枚举指定Socket使用的寻址方案。 例如AddressFamily.InterNetwork表示IP版本4的地址。

  6. 4.1 套接字(续)

  7. 4.1 套接字(续) (2)socketType socketTyp指定Socket的类型,该参数使用SocketType枚举指定使用哪种套接字。 例如: SocketType.Stream表明连接是基于流套接字 SocketType.Dgram表示连接是基于数据报套接字 SocketType.Raw表示连接基于原始套接字; SocketType枚举详细见表4-2(P58).

  8. 4.1 套接字(续) (3)protocolType protocolType指定Socket使用的协议,该参数使用ProtocolType枚举指定使用哪种协议。例如: ProtocolType.Tcp表明Socket使用的协议是TCP ProtocolType.Udp表明Socket使用的协议是UDP ProtocolType.IP表明Socket使用的协议是IP 详细见课本表4-3

  9. 4.1 套接字(续) Socket构造函数的三个参数中,对于网络上的IP通信来说,AddressFamily总是使用AddressFamily.InterNetwork枚举值。而SocketType参数则与ProtocolType参数配合使用,不允许其他的匹配形式,也不允许混淆匹配。下表列出了可用于IP通信的组合。

  10. 2.Socket类的常用属性 Connected属性:是否连接 LocalEndPoint属性:套接字本地EndPoint对象 RemoteEndPoint属性:套接字远程EndPoint对象 3.Socket类的常用方法 Accept方法:为新建连接创建新的 Socket Bind方法:与本地端点关联 Listen方法:在指定的端口监听 4.1 套接字(续)

  11. 4.1 套接字(续) 3.Socket类的常用方法 Send方法:发送数据 Receive方法:接收数据 Shutdown方法:关闭会话 Close方法:关闭Socket对象 4.Socket使用场合 编写基本TCP或UDP应用程序时,可以直接使用Socket类实现 编写自定义的新协议程序时,只能使用Socket类实现

  12. 4.1.1 面向连接的套接字 IP连接领域有两种通信类型: 面向连接的(connection-oriented)通信 无连接的(connectionless)通信 面向连接套接字编程有三个步骤: 1. 服务器与客户端建立连接(建立连接阶段) 2. 服务器与客户端之间收发消息(收发消息阶段) 3. 断开连接,关闭套接字(断开连接阶段)

  13. 1、建立连接阶段 套接字之间的连接过程可以分为三个步骤: 服务器监听:服务器套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。 客户端请求:由客户端的套接字提出连接请求,要连接的目标是服务器的套接字。客户端必须指出服务器套接字的地址和端口号,然后再向服务器套接字提出连接请求。 连接确认:当服务器套接字监听到客户端套接字的连接请求时,它就响应客户端套接字的请求,把服务器套接字的信息发给客户端,一旦客户端确认了此信息,连接即可建立。

  14. 2、收发消息阶段 该阶段主要完成服务器与客户端端间的消息传递。 发送消息 将要发送的消息,经过编码成字节序列,发送给对方。 接收消息 接收对方发过来的消息,然后对其进行解码,变成字符串显示。 注意:收发消息阶段一定要保证数据的编码格式和解码格式一致,否则就会出现乱码。

  15. 3、断开连接阶段 该阶段主要是在消息发送和接收完毕之后,断开连接,关闭对象,进行资源的释放。 此阶段一般需要添加try-catch语句,捕获可能出现的异常。 例如:Socket类提供了Shutdown方法和Close方法来进行连接的断开和关闭操作。

  16. 4.1.1 面向连接的套接字 客户端 服务器 1.创建本地Socket 1.创建本地Socket 2.调用Bind方法绑定到本地端点 3.在指定端口监听Listen 2.Connect连接请求 4.Accept() 3.收发数据 5.收发数据 6.Close() 4.Close()

  17. 4.1.1 面向连接的套接字 同步TCP编写服务器端程序的一般步骤为: 1) 创建一个包含采用的网络类型、数据传输类型和协议类型的本地套接字对象,并将其与服务器的IP地址和端口号绑定。这个过程通过Socket类实现。 2) 在指定的端口进行监听,以便接受客户端连接请求。 3) 一旦接受了客户端的连接请求,就根据客户端发送的连接信息创建与该客户端对应的Socket对象。 4) 根据创建的Socket对象,分别与每个连接的客户进行数据传输。 5) 根据传送信息情况确定是否关闭与对方的连接。

  18. 4.1.1 面向连接的套接字 使用同步TCP编写客户端程序的一般步骤为: 1) 创建一个包含传输过程中采用的网络类型、数据传输类型和协议类型的Socket对象。 2) 与远程服务器建立连接。 3) 与服务器进行数据传输。 4) 完成工作后,向服务器发送关闭信息,并关闭与服务器的连接。

  19. 面向连接套接字编程——具体代码实现 1.建立连接(服务器) IPHostEntry local = Dns.GetHostByName(Dns.GetHostName()); IPEndPoint iep = new IPEndPoint(local.AddressList[0], 1180); Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); localSocket.Bind(iep); localSocket.Listen(10); Socket clientSocket = serverSocket .Accept();

  20. 面向连接套接字编程——具体代码实现 1.建立连接(客户端) 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);

  21. 面向连接套接字编程——具体代码实现 2. 发送、接收信息(服务器) …… Socket clientSocket = serverSocket.Accept(); //建立连接后,利用Send方法向客户端发送信息 clientSocket.Send(Encoding.ASCII.GetBytes("server send Hello")); //接收客户端信息 byte[] myresult = new Byte[1024]; int receiveNum = clientSocket.Receive(myresult); Console.WriteLine("接收客户端消息:{0}", Encoding.ASCII.GetString(myresult));

  22. 面向连接套接字编程——具体代码实现 2. 发送、接收信息(客户端) //建立连接成功后,向服务器发送信息 string sendMessage = "client send Message Hello"+DateTime.Now; localSocket.Send(Encoding.ASCII.GetBytes(sendMessage)); Console.WriteLine("向服务器发送消息:{0}", sendMessage); //接收服务器信息 byte[] result = new Byte[1024]; localSocket.Receive(result); Console.WriteLine("接收服务器消息:{0}", Encoding.ASCII.GetString(result));

  23. 面向连接套接字编程——具体代码实现 3.断开连接 通信完成后,必须先用Shutdown方法停止会话,然后关闭 Socket实例 。 例如: serverSocket.Shutdown(SocketShutdown.Both); serverSocket.Close();

  24. 4.1.1 面向连接的套接字(续) 面向连接的套接字编程举例(控制台应用程序) 一对一通信举例 课堂演示(书上代码组合) 一对多通信举例 【例题4-1】使用同步Socket实现客户端与服务器端的消息通信。其中,服务器可以与多个客户端通信,并随时接收客户端发送的消息。

  25. 4.1.1 面向连接的套接字(续) 一对一通信(Socket实现控制台应用程序) 服务器端和客户端可以不开辟线程直接通信,也可以开辟线程进行通信; 开辟线程实现时: 服务器端(1~2个线程) 接收线程1个(也可以没有) 收消息线程1个 客户端(1个线程) 接收消息线程1个 注意:本章内所说的线程个数只涉及基本通信,不考虑其他情况下开辟的线程。

  26. 4.1.1 面向连接的套接字(续) 一对多通信(Socket实现控制台应用程序) 服务器端(开辟线程数1+n个): 接收连接线程1:负责接收客户端的连接; 接收消息线程(1-n):负责接收来自客户端的消息;n表示与服务端相连的客户端的个数; 客户端(开辟线程数1个): 接收消息线程1:负责接收来自服务器的消息。

  27. 4.1.2 无连接的套接字 UDP使用无连接的套接字,无连接的套接字不需要在网络设备之间发送连接信息。 注意: 必须使用Bind方法将套接字绑定到一个本地地址和端口之后才能使用ReceiveFrom方法接收数据。如果只发送而不接收,则不需要使用Bind方法。

  28. 4.1.2 无连接的套接字 机器2 机器1 1.创建本地Socket 1.创建本地Socket 2.调用Bind方法绑定到本地端点 2.调用Bind方法绑定到本地端点 3.接收数据ReceiveFrom 3.发送数据SendTo 4.接收数据ReceiveFrom 4.发送数据SendTo 5.Close() 5.Close()

  29. 接收方: //本地端点 IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9050); //定义一个Socket用来接收消息 Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); //Socket与本地的一个终结点相关联 newsock.Bind(ipep); 4.1.2 无连接的套接字

  30. 4.1.2 无连接的套接字 发送方: //指定目标主机的端点 IPEndPoint destIPEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9050); //创建udp套接字发送数据 Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); //将数据发送到指定的终结点 Byte[] data = Encoding.ASCII.GetBytes(“say hello”); udpSocket.SendTo(data, data.Length, SocketFlags.None, destIPEP); udpSocket.Close();

  31. 接收方: //从远程计算机接受数据 byte [] data = new byte[1024]; IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0); //定义远程计算机的地址 EndPoint Remote = (EndPoint)(sender); int recvNum = newsock.ReceiveFrom(data, ref Remote); Console.WriteLine("Message received from {0}:{1}", Remote.ToString(),Encoding.ASCII.GetString(data, 0, recvNum)); 4.1.2 无连接的套接字

  32. 4.2 数据流 4.2.1 文件流 4.2.2 内存流 4.2.3 网络流 4.2.4 StreamWriter与StreamReader类 4.2.5 BinaryReader与BinaryWriter类

  33. 4.2 数据流 流(stream)是对串行传输的数据(以字节为单位)的一种抽象表示,底层的设备可以是文件、外部设备、主存、网络套接字等。

  34. 4.2 数据流 流提供三种基本操作: 写入:将数据从内存缓冲区传输到外部源。 读取:将数据从外部源传输到内存缓冲区。 查找:重新设置流的当前位置,以便随机读写。需要注意的是,并不是所有的流类型都能够支持查找,例如,网络流没有当前位置的统一概念,因此一般不支持查找。 说明:Stream类提供有多种操作流的方法,其中Read和Write方法是Stream类及其派生类都提供的实现,可支持在字节级别上对数据进行读写。实际的程序开发中,仅支持字节级别的数据处理会给开发人员带来不便。

  35. 4.2 数据流 .NET Framework提供一些类能够以字符串或二进制方式读取或写入流。 优点: 方法更灵活; 部分方法可解决TCP消息通信无边界问题;

  36. 4.2.1 FileStream类 FileStream类继承于Stream类,一个FileStream类的实例实际上代表一个磁盘文件,使用FileStream类可以对文件系统上的文件进行读取、写入、打开和关闭操作。 1、创建FileStream实例 (1)常用的构造函数具有三个参数,例如: FileStream(string FileMode mode,FileAccess access) (2)File和FileInfo类也提供了创建FileStream对象的方法。 其中,OpenRead方法返回只读文件流,OpenWrite方法返回只写文件流。 例如:FileStream fs= File.OpenRead("C:\\File1.txt");

  37. FileMode枚举 FileMode值用于指定当文件不存在时是否创建该文件,并确定是保留还是改写现有文件的内容.

  38. FileAccess枚举 FileAccess值是枚举的一个成员,它控制对文件的访问权限。表4-10列出了FileAccess所有的枚举形式

  39. 4.2.1 FileStream类 2. 读文件 在获取FileStream实例之后,可利用FileStream对象的Read方法读取文件中的数据。该方法用于从流中读取字节块并将该数据写入给定字节数组中。其语法形式为: public override int Read(byte[] array,int offset, int count) array : 存储从文件流中读取的数据。 offset : array字节数组中开始写入数据的下标,一般为0。 size : 要从文件流中读出字节的大小 返回值: 从FileStream中读取的字节数。 【例4-2】利用FileStream的Read方法从一个文本文件中读取内容并显示在屏幕上。

  40. 4.2.1 FileStream类 3. 写文件 Stream类及其所有子类都提供了Write方法,FileStream类也不例外。该方法可将字节数组写入流。语法形式为 public override void Write ( byte[] buffer, //包含要写入流的数据 int offset, // buffer中开始写入数据的位置 int size //要写入流的字节数 ) 【例4-3】利用FileStream的Write方法向文本文件中追加数据。

  41. 4.2.2 MemoryStream类 MemoryStream类表示的是保存在内存中的数据流。由内存流封装的数据可以在内存中直接访问。 由于内存流具有流的特征,并且容量能够自动增长,因此MemoryStream适合数据加密以及处理长度不定的数据进行缓存的场合。 MemoryStream类的构造函数具有多种重载形式,常用的构造函数有: (1)MemoryStream () 该构造函数初始分配的容量大小为0,随着数据的不断写入容量可以不断扩展。

  42. 4.2.2 MemoryStream类 (2)MemoryStream (Byte[]) 该构造函数获取的MemoryStream实例根据Byte[]字节数组进行初始化,并且实例容量大小固定即为字节数组的长度。由于实例的容量不能扩展,该构造函数一般用于数据不发生变化的场合。 byte[] bytes = Encoding.Unicode.GetBytes("aaasd"); MemoryStream mem = new MemoryStream(bytes); (3)MemoryStream (int capacity) 通过该构造函数创建初始容量大小为capacity的实例,并且实例容量大小可扩展。 【例4-4】利用MemoryStream暂存数据。

  43. 4.2.3 网络流 在System.Net.Sockets名称空间中有一个NetworkStream类,用于通过网络套接字发送和接收数据。 NetworkStream类支持对网络数据的同步或异步访问,它可被视为在数据来源端和接收端之间架设了一个数据通道. NetWorkStream只用于面向连接的数据传输

  44. 写入操作是指从来源端内存缓冲区到网络上的数据传输;写入操作是指从来源端内存缓冲区到网络上的数据传输; 读取操作是从网络上到接收端内存缓冲区(如字节数组)的数据传输。 4.2.3 网络流(续)

  45. 4.2.3 网络流(续) NetworkStream的用法 1、构造NetworkStream: (1)利用TcpClient获取网络流对象,例如: TcpClient client=new TcpClient(); client.Connect("www.abcd.com", 51888); NetworkStream networkStream = client.GetStream(); (2)利用Socket获取网络流对象,例如: NetworkStream myNetworkStream = new NetworkStream(mySocket);

  46. 4.2.3 网络流(续) 2、发送数据 public override void Write (byte[] buffer,int offset,int size )

  47. 4.2.3 网络流(续) Write方法: NetworkStream对象的Write方法的返回值为void,该对象之所以不返回实际发送的字节数,是因为能保证字节数组中的数据全部发送到TCP发送缓冲区中。 在使用NetworkStream对象的Write方法前最好先检测NetworkStream对象的CanWrite属性是否为True。 如果发送的全部是单行文本信息,创建NetworkStream对象后,使用StreamReader和StreamWriter的ReadLine和WriteLine方法更简单。

  48. 4.2.3 网络流(续) 3、接收数据 public override int Read ( [InAttribute] [OutAttribute] byte[] buffer, int offset, int size) 各参数的含义: buffer :内存中用于存储从NetworkStream读取的数据的位置。 offset:buffer 中开始将数据存储到的位置。 Size:要从NetworkStream中读取的字节数。 返回值:实际从NetworkStream中读取的字节数。

  49. 4.2.3 网络流(续) Read方法: 调用NetworkStream类的Read方法前应确保NetworkStream对象的CanRead属性值有效 由于有可能TCP接收缓冲区还没有接收到对方发送过来的指定长度的数据,所以Read方法有一个整型的返回值。 如果远程主机关闭了套接字连接,并且此时有效数据已经被完全接收,那么Read方法的返回值将会是0字节。

More Related