600 likes | 855 Views
第 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 协议的网络通信的基本操作单元。可以将套接字看作 不同主机间的进程进行双向通信的 端点 。
E N D
第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协议的网络通信的基本操作单元。可以将套接字看作不同主机间的进程进行双向通信的端点。 对于UDP协议尽管两个进程之间没有建立连接,但是也同样存在发送端点和接收端点,也同样使用套接字的概念。
4.1 套接字(续) 套接字的类型有: 流式套接字:提供了面向连接的、可靠的、数据无错并且无重复的数据发送服务,而且接收数据的顺序和发送数据的顺序是相同的。流式套接字用来实现TCP通信。 数据报套接字:提供了面向无连接的服务,它以独立的数据包形式发送数据(数据包长度不能大于32KB),不提供正确性检查,也不保证各数据包的发送顺序和接收顺序相同 ,因此,可能出现数据的重发、丢失等现象。数据报套接字用来实现UDP通信。 原始套接字:用于直接访问协议的较低层。常用于检验新的协议实现或访问现有服务中配置的新设备,一般不提倡直接使用原始套接字。原始套接字用来实现IP数据包通信。
4.1 套接字(续) .NET Framework中的System.Net.Sockets命名空间下的Socket类可以实现套接字。 一个Socket实例包含了一个本地或者一个远程端点的套接字信息。 使用Socket类编程,由于很多细节都需要自己考虑,相对来说复杂一些,易出错。一般对套接字编程比较熟悉的人,或者使用非标准协议(自定义的新协议)进行编程的时候,才使用Socket类。
4.1 套接字(续) 1. Socket对象的创建 Socket类的构造函数: public Socket( AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType); 参数含义: (1)addressFamily addressFamily表示网络类型,该参数使用AddressFamily枚举指定Socket使用的寻址方案。 例如AddressFamily.InterNetwork表示IP版本4的地址。
4.1 套接字(续) (2)socketType socketTyp指定Socket的类型,该参数使用SocketType枚举指定使用哪种套接字。 例如: SocketType.Stream表明连接是基于流套接字 SocketType.Dgram表示连接是基于数据报套接字 SocketType.Raw表示连接基于原始套接字; SocketType枚举详细见表4-2(P58).
4.1 套接字(续) (3)protocolType protocolType指定Socket使用的协议,该参数使用ProtocolType枚举指定使用哪种协议。例如: ProtocolType.Tcp表明Socket使用的协议是TCP ProtocolType.Udp表明Socket使用的协议是UDP ProtocolType.IP表明Socket使用的协议是IP 详细见课本表4-3
4.1 套接字(续) Socket构造函数的三个参数中,对于网络上的IP通信来说,AddressFamily总是使用AddressFamily.InterNetwork枚举值。而SocketType参数则与ProtocolType参数配合使用,不允许其他的匹配形式,也不允许混淆匹配。下表列出了可用于IP通信的组合。
2.Socket类的常用属性 Connected属性:是否连接 LocalEndPoint属性:套接字本地EndPoint对象 RemoteEndPoint属性:套接字远程EndPoint对象 3.Socket类的常用方法 Accept方法:为新建连接创建新的 Socket Bind方法:与本地端点关联 Listen方法:在指定的端口监听 4.1 套接字(续)
4.1 套接字(续) 3.Socket类的常用方法 Send方法:发送数据 Receive方法:接收数据 Shutdown方法:关闭会话 Close方法:关闭Socket对象 4.Socket使用场合 编写基本TCP或UDP应用程序时,可以直接使用Socket类实现 编写自定义的新协议程序时,只能使用Socket类实现
4.1.1 面向连接的套接字 IP连接领域有两种通信类型: 面向连接的(connection-oriented)通信 无连接的(connectionless)通信 面向连接套接字编程有三个步骤: 1. 服务器与客户端建立连接(建立连接阶段) 2. 服务器与客户端之间收发消息(收发消息阶段) 3. 断开连接,关闭套接字(断开连接阶段)
1、建立连接阶段 套接字之间的连接过程可以分为三个步骤: 服务器监听:服务器套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。 客户端请求:由客户端的套接字提出连接请求,要连接的目标是服务器的套接字。客户端必须指出服务器套接字的地址和端口号,然后再向服务器套接字提出连接请求。 连接确认:当服务器套接字监听到客户端套接字的连接请求时,它就响应客户端套接字的请求,把服务器套接字的信息发给客户端,一旦客户端确认了此信息,连接即可建立。
2、收发消息阶段 该阶段主要完成服务器与客户端端间的消息传递。 发送消息 将要发送的消息,经过编码成字节序列,发送给对方。 接收消息 接收对方发过来的消息,然后对其进行解码,变成字符串显示。 注意:收发消息阶段一定要保证数据的编码格式和解码格式一致,否则就会出现乱码。
3、断开连接阶段 该阶段主要是在消息发送和接收完毕之后,断开连接,关闭对象,进行资源的释放。 此阶段一般需要添加try-catch语句,捕获可能出现的异常。 例如:Socket类提供了Shutdown方法和Close方法来进行连接的断开和关闭操作。
4.1.1 面向连接的套接字 客户端 服务器 1.创建本地Socket 1.创建本地Socket 2.调用Bind方法绑定到本地端点 3.在指定端口监听Listen 2.Connect连接请求 4.Accept() 3.收发数据 5.收发数据 6.Close() 4.Close()
4.1.1 面向连接的套接字 同步TCP编写服务器端程序的一般步骤为: 1) 创建一个包含采用的网络类型、数据传输类型和协议类型的本地套接字对象,并将其与服务器的IP地址和端口号绑定。这个过程通过Socket类实现。 2) 在指定的端口进行监听,以便接受客户端连接请求。 3) 一旦接受了客户端的连接请求,就根据客户端发送的连接信息创建与该客户端对应的Socket对象。 4) 根据创建的Socket对象,分别与每个连接的客户进行数据传输。 5) 根据传送信息情况确定是否关闭与对方的连接。
4.1.1 面向连接的套接字 使用同步TCP编写客户端程序的一般步骤为: 1) 创建一个包含传输过程中采用的网络类型、数据传输类型和协议类型的Socket对象。 2) 与远程服务器建立连接。 3) 与服务器进行数据传输。 4) 完成工作后,向服务器发送关闭信息,并关闭与服务器的连接。
面向连接套接字编程——具体代码实现 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();
面向连接套接字编程——具体代码实现 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);
面向连接套接字编程——具体代码实现 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));
面向连接套接字编程——具体代码实现 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));
面向连接套接字编程——具体代码实现 3.断开连接 通信完成后,必须先用Shutdown方法停止会话,然后关闭 Socket实例 。 例如: serverSocket.Shutdown(SocketShutdown.Both); serverSocket.Close();
4.1.1 面向连接的套接字(续) 面向连接的套接字编程举例(控制台应用程序) 一对一通信举例 课堂演示(书上代码组合) 一对多通信举例 【例题4-1】使用同步Socket实现客户端与服务器端的消息通信。其中,服务器可以与多个客户端通信,并随时接收客户端发送的消息。
4.1.1 面向连接的套接字(续) 一对一通信(Socket实现控制台应用程序) 服务器端和客户端可以不开辟线程直接通信,也可以开辟线程进行通信; 开辟线程实现时: 服务器端(1~2个线程) 接收线程1个(也可以没有) 收消息线程1个 客户端(1个线程) 接收消息线程1个 注意:本章内所说的线程个数只涉及基本通信,不考虑其他情况下开辟的线程。
4.1.1 面向连接的套接字(续) 一对多通信(Socket实现控制台应用程序) 服务器端(开辟线程数1+n个): 接收连接线程1:负责接收客户端的连接; 接收消息线程(1-n):负责接收来自客户端的消息;n表示与服务端相连的客户端的个数; 客户端(开辟线程数1个): 接收消息线程1:负责接收来自服务器的消息。
4.1.2 无连接的套接字 UDP使用无连接的套接字,无连接的套接字不需要在网络设备之间发送连接信息。 注意: 必须使用Bind方法将套接字绑定到一个本地地址和端口之后才能使用ReceiveFrom方法接收数据。如果只发送而不接收,则不需要使用Bind方法。
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()
接收方: //本地端点 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 无连接的套接字
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();
接收方: //从远程计算机接受数据 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 无连接的套接字
4.2 数据流 4.2.1 文件流 4.2.2 内存流 4.2.3 网络流 4.2.4 StreamWriter与StreamReader类 4.2.5 BinaryReader与BinaryWriter类
4.2 数据流 流(stream)是对串行传输的数据(以字节为单位)的一种抽象表示,底层的设备可以是文件、外部设备、主存、网络套接字等。
4.2 数据流 流提供三种基本操作: 写入:将数据从内存缓冲区传输到外部源。 读取:将数据从外部源传输到内存缓冲区。 查找:重新设置流的当前位置,以便随机读写。需要注意的是,并不是所有的流类型都能够支持查找,例如,网络流没有当前位置的统一概念,因此一般不支持查找。 说明:Stream类提供有多种操作流的方法,其中Read和Write方法是Stream类及其派生类都提供的实现,可支持在字节级别上对数据进行读写。实际的程序开发中,仅支持字节级别的数据处理会给开发人员带来不便。
4.2 数据流 .NET Framework提供一些类能够以字符串或二进制方式读取或写入流。 优点: 方法更灵活; 部分方法可解决TCP消息通信无边界问题;
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");
FileMode枚举 FileMode值用于指定当文件不存在时是否创建该文件,并确定是保留还是改写现有文件的内容.
FileAccess枚举 FileAccess值是枚举的一个成员,它控制对文件的访问权限。表4-10列出了FileAccess所有的枚举形式
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方法从一个文本文件中读取内容并显示在屏幕上。
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类表示的是保存在内存中的数据流。由内存流封装的数据可以在内存中直接访问。 由于内存流具有流的特征,并且容量能够自动增长,因此MemoryStream适合数据加密以及处理长度不定的数据进行缓存的场合。 MemoryStream类的构造函数具有多种重载形式,常用的构造函数有: (1)MemoryStream () 该构造函数初始分配的容量大小为0,随着数据的不断写入容量可以不断扩展。
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暂存数据。
4.2.3 网络流 在System.Net.Sockets名称空间中有一个NetworkStream类,用于通过网络套接字发送和接收数据。 NetworkStream类支持对网络数据的同步或异步访问,它可被视为在数据来源端和接收端之间架设了一个数据通道. NetWorkStream只用于面向连接的数据传输
写入操作是指从来源端内存缓冲区到网络上的数据传输;写入操作是指从来源端内存缓冲区到网络上的数据传输; 读取操作是从网络上到接收端内存缓冲区(如字节数组)的数据传输。 4.2.3 网络流(续)
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);
4.2.3 网络流(续) 2、发送数据 public override void Write (byte[] buffer,int offset,int size )
4.2.3 网络流(续) Write方法: NetworkStream对象的Write方法的返回值为void,该对象之所以不返回实际发送的字节数,是因为能保证字节数组中的数据全部发送到TCP发送缓冲区中。 在使用NetworkStream对象的Write方法前最好先检测NetworkStream对象的CanWrite属性是否为True。 如果发送的全部是单行文本信息,创建NetworkStream对象后,使用StreamReader和StreamWriter的ReadLine和WriteLine方法更简单。
4.2.3 网络流(续) 3、接收数据 public override int Read ( [InAttribute] [OutAttribute] byte[] buffer, int offset, int size) 各参数的含义: buffer :内存中用于存储从NetworkStream读取的数据的位置。 offset:buffer 中开始将数据存储到的位置。 Size:要从NetworkStream中读取的字节数。 返回值:实际从NetworkStream中读取的字节数。
4.2.3 网络流(续) Read方法: 调用NetworkStream类的Read方法前应确保NetworkStream对象的CanRead属性值有效 由于有可能TCP接收缓冲区还没有接收到对方发送过来的指定长度的数据,所以Read方法有一个整型的返回值。 如果远程主机关闭了套接字连接,并且此时有效数据已经被完全接收,那么Read方法的返回值将会是0字节。