第9章:网络编程
本章目标
熟悉网络编程相关协议
了解TCP协议 的通信原理
了解UDP协议的通信原理
掌握基于Socket 方式的网络编程
本章内容 相关概念 协议 TCP协议:
TCP是一种面向连接的、可靠的,基于字节流的传输层通信协议。为两台主机提供高可靠性的数据通信服务。它可以将源主机的数据无差错地传输到目标主机。当有数据要发送时,对应用进程送来的数据进行分片,以适合于在网络层中传输;当接收到网络层传来的分组时,它要对收到的分组进行确认,还要对丢失的分组设置超时重发等。为此TCP需要增加额外的许多开销,以便在数据传输过程中进行一些必要的控制,确保数据的可靠传输。因此,TCP传输的效率比较低。
UDP协议:
UDP是一种简单的、面向数据报的无连接的协议,提供的是不一定可靠的传输服务。所谓“无连接”是指在正式通信前不必与对方先建立连接,不管对方状态如何都直接发送过去。这与发手机短信非常相似,只要知道对方的手机号就可以了,不要考虑对方手机处于什么状态。UDP虽然不能保证数据传输的可靠性,但数据传输的效率较高。
IP IP是Internet Protocol(网际互连协议)的缩写,是TCP/IP 体系中的网络层协议。设计IP的目的是提高网络的可扩展性:一是解决互联网 问题,实现大规模、异构网络 的互联互通;二是分割顶层网络应用和底层网络技术之间的耦合关系,以利于两者的独立发展。根据端到端 的设计原则,IP只为主机提供一种无连接、不可靠的、尽力而为的数据包传输服务。
A类
B类
C类
D类
E类
网络标志位
0
10
110
1110
11110
IP地址范围
1.0.0.0~ 127.255.255.255
128.0.0.0~ 191.255.255.255
192.0.0.0~ 223.255.255.255
224.0.0.0~ 239.255.255.255
240.0.0.0~ 247.255.255.255
可用IP地址范围
1.0.0.1~ 126.255.255.254
128.0.0.1~ 191.255.255.254
192.0.0.1~ 223.255.255.254
是否可以分配给主机使用
是
是
是
否
否
网络数量(个)
126 (2^7-2)
16384 (2^14)
2097152 (2^21)
–
–
每个网络中可容纳主机数(个)
16777214 (2^24-2)
65534 (2^16-2)
254 (2^8-2)
–
–
适用范围
大量主机的大型网络
中等规模主机数的网络
小型局域网
留给Internet体系结构委员会(IAB)使用【组播地址】
保留,仅作为搜索、Internet的实验和开发用
端口 “端口 “是英文port的意译,可以认为是设备 与外界通讯交流的出口。端口可分为虚拟端口和物理端口,其中虚拟 端口指计算机内部或交换机路由器内的端口,不可见。例如计算机中的80端口、21端口、23端口等。物理端口又称为接口,是可见端口,计算机背板的RJ45网口,交换机路由器集线器等RJ45端口。电话使用RJ11插口也属于物理端口的范畴。
核心类 DNS类 Dns类是一个静态类,它从 Internet 域名系统 (DNS) 检索关于特定主机的信息。在 IPHostEntry 类的实例中返回来自 DNS 查询的主机信息。 如果指定的主机在 DNS 数据库中有多个入口,则 IPHostEntry 包含多个 IP 地址和别名。
IPAddress类 IPAddress类提供了对IP地址的转换、处理等功能。其Parse方法可将IP地址字符串转换为IPAddress实例。如:
IPAddress ip = IPAddress.Parse(“192.168.1.1”);
IPEndPoint类 IPEndPoint类包含了应用程序连接到主机上的服务所需的IP地址和端口信息。
IPHostEntry类 IPHostEntry类将一个域名系统 (DNS) 主机名与一组别名和一组匹配的 IP 地址关联。
常用属性有:AddressList属性和HostName属性。
AddressList属性作用:获取或设置与主机关联的IP地址列表,是一个IPAddress类型的数组,包含了指定主机的所有IP地址;HostName属性则包含了服务器的主机名。
在Dns类中,有一个专门获取IPHostEntry对象的方法,通过IPHostEntry对象,可以获取本地或远程主机的相关IP地址。
Socket-TCP方式通信
服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 using System;using System.Net;using System.Net.Sockets;using System.Threading;using System.Text;namespace Socket_TCP_Server { class Program { static void Main (string [] args ) { Socket serverSockekt = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint point = new IPEndPoint(IPAddress.Parse("10.168.1.167" ), 5555 ); serverSockekt.Bind(point); serverSockekt.Listen(100 ); Console.WriteLine("服务端已启动,正在监听客户端...." ); while (true ) { Socket clientSocket= serverSockekt.Accept(); new Thread(new ParameterizedThreadStart(Receive)).Start(clientSocket); } } static void Receive (object obj ) { Socket socket = (Socket)obj; IPEndPoint clientPoint = socket.RemoteEndPoint as IPEndPoint; string ip = clientPoint.Address.ToString(); byte [] b = new byte [1024 ]; while (true ) { int length = socket.Receive(b); if (length == -1 ) continue ; string str = Encoding.UTF8.GetString(b,0 ,length); Console.WriteLine(string .Format("{0}:{1}" , ip, str)); } } } }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 using System;using System.Net;using System.Net.Sockets;using System.Text;namespace Socket_TCP_Client { class Program { static void Main (string [] args ) { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress ip = IPAddress.Parse("10.168.1.167" ); IPEndPoint point = new IPEndPoint(ip, 5555 ); try { socket.Connect(point); Console.WriteLine("连接成功,请输入要发送的消息!" ); Send(socket); } catch (Exception) { Console.WriteLine("服务端未启动" ); } Console.ReadKey(); } static void Send (Socket socket ) { while (true ) { string msg = Console.ReadLine(); socket.Send(Encoding.UTF8.GetBytes(msg)); } } } }
运行效果
Socket-UDP方式通信 服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 using System;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;namespace Socket_UDP_Server { class Program { static Socket serverSocket = null ; static void Main (string [] args ) { serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); serverSocket.Bind(new IPEndPoint(IPAddress.Parse("10.168.1.167" ), 5555 )); Console.WriteLine("服务端已启动!" ); new Thread(Receive).Start(); Console.ReadKey(); } static void Receive () { EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0 ); byte [] b = new byte [1024 ]; while (true ) { int length = serverSocket.ReceiveFrom(b, ref remoteEndPoint); if (length == -1 ) continue ; string ip = ((IPEndPoint)remoteEndPoint).Address.ToString(); string str = Encoding.UTF8.GetString(b, 0 , length); Console.WriteLine(string .Format("{0}:{1}" ,ip , str)); } } } }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 using System;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;namespace Socket_UDP_Client { class Program { static void Main (string [] args ) { Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); IPEndPoint targetPoint = new IPEndPoint(IPAddress.Parse("10.168.1.167" ), 5555 ); Console.WriteLine("请输入要发送的数据!" ); while (true ) { string line = Console.ReadLine(); clientSocket.SendTo(Encoding.UTF8.GetBytes(line), targetPoint); } } } }
运行效果
Listener-TCP方式通信 服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 using System;using System.Net;using System.Net.Sockets;using System.Collections;using System.Text;namespace TcpListener_Server { class Program { static void Main (string [] args ) { TcpListener listener = new TcpListener(IPAddress.Parse("10.168.1.167" ),5555 ); listener.Start(); Console.WriteLine("服务端已启动监听..." ); TcpClient client = listener.AcceptTcpClient(); NetworkStream stream= client.GetStream(); byte [] b = new byte [1024 ]; while (true ) { int length = stream.Read(b, 0 , b.Length); string ip = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString(); string msg = Encoding.UTF8.GetString(b, 0 , length); Console.WriteLine(string .Format("{0}:{1}" , ip, msg)); } } } }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 using System;using System.Net.Sockets;using System.Text;namespace TcpListener_Client { class Program { static void Main (string [] args ) { TcpClient client = new TcpClient("10.168.1.167" , 5555 ); NetworkStream stream = client.GetStream(); Console.WriteLine("与服务端连接成功,请输入要发送的数据..." ); while (true ) { string message = Console.ReadLine(); byte [] data = Encoding.UTF8.GetBytes(message); stream.Write(data, 0 , data.Length); } Console.ReadKey(); } } }
运行效果
UdpClient方式通信 消息接收方 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 using System;using System.Collections.Generic;using System.Linq;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading.Tasks;namespace UdpClient_Receive { class Program { static void Main (string [] args ) { UdpClient udpLocal = new UdpClient(new IPEndPoint(IPAddress.Parse("10.168.1.167" ), 5555 )); IPEndPoint point = new IPEndPoint(IPAddress.Any, 0 ); byte [] b = null ; Console.WriteLine("等待接收消息..." ); while (true ) { b = udpLocal.Receive(ref point); string ip = point.Address.ToString(); string msg = Encoding.UTF8.GetString(b); Console.WriteLine(string .Format("{0}:{1}" , ip, msg)); } Console.ReadKey(); } } }
消息发送方 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using System;using System.Collections.Generic;using System.Linq;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading.Tasks;namespace UdpClient_Send { class Program { static void Main (string [] args ) { UdpClient client = new UdpClient(); Console.WriteLine("请输入要发送的消息..." ); while (true ) { string message = Console.ReadLine(); byte [] data = Encoding.UTF8.GetBytes(message); client.Send(data, data.Length, new IPEndPoint(IPAddress.Parse("10.168.1.167" ), 5555 )); } } } }
运行效果
综合案例1-局域网聊天室
N对N的方式实现局域网聊天室。
单播 单播地址是IP网络中最常见的。包含单播目标地址的分组发送给特定主机,一个这样的例子是,IP地址为192.168.1.5(源地址)的主机向IP地址为192.168.1.200(目标地址)的服务器请求网页,如下图所示
广播 广播分组的目标IP地址的主机部分全为1,这意味着本地网络(广播域)中的所有主机都将接收并查看该分组。诸如ARP和DHCP等很多网络协议都使用广播。
例如:
C类网络192.168.1.0的默认子网掩码为255.255.255.0(掩码的255个数对应网络的网络地址个数),其广播地址为192.168.1.255 ,其主机部分为十进制数255或二进制数11111111(全为1);
B类网络172.16.0.0的默认子网掩码为255.255.0.0,其广播地址为172.16.255.255 ;
A类网络10.0.0.0的默认子网掩码为255.0.0.0,其广播地址为10.255.255.255 。
在以太网帧中,必须包含与广播IP地址对应的广播MAC地址。在以太网中,广播MAC地址长48位,其十六进制表示为FF-FF-FF-FF-FF-FF(全1为广播mac,主机地址为全1即广播ip地址)。图5.9所示的是一个广播IP分组。
多播 多播地址让源设备能够将分组发送给一组设备。属于多播组的设备将被分配一个多播组IP地址,多播地址范围为224.0.0.0~239.255.255.255。由于多播地址表示一组设备(有时被称为主机组),因此只能用作分组的目标地址。源地址总是为单播地址。
远程游戏就是一个使用多播地址的例子,很多玩家通过远程连接玩同一个游戏;另一例子是通过视频会议进行远程教学,其中很多学生连接到同一个教室。还有一个例子是硬盘映像应用程序,这种程序用于同时恢复众多硬盘的内容。
同单播地址和广播地址一样,多播IP地址也需要相应的多播MAC地址在本地网络中实际传送帧。多播MAC地址以十六进制值01-00-5E打头,余下的6个十六进制位是根据IP多播组地址的最后23位转换得到的。一个MAC多播地址是01-00-5E-0F-64-C5,如图5.10所示。每个十六进制位相对于4个二进制位。
代码案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;namespace Room2 { public partial class Form1 : Form { private delegate void AppentContentCallBack (string content ) ; private AppentContentCallBack appentContentCallBack; public Form1 () { InitializeComponent(); } private void Form1_Load (object sender, EventArgs e ) { appentContentCallBack = new AppentContentCallBack(AppendContent); Thread th = new Thread(ReciveMessage); th.IsBackground = true ; th.Start(); } private void button1_Click (object sender, EventArgs e ) { try { IPAddress targetAddress=IPAddress.Parse("255.255.255.255" ); int targetPort=5555 ; IPEndPoint groupEP= new IPEndPoint(targetAddress, targetPort); UdpClient sendClient= new UdpClient(); sendClient.EnableBroadcast = true ; string message = this .rtxtMsgSend.Text; byte [] data = Encoding.UTF8.GetBytes(message); sendClient.Send(data, data.Length, groupEP); this .rtxtMsgSend.Text = null ; } catch (Exception ex) { MessageBox.Show(ex.Message); } } public void ReciveMessage () { UdpClient reciveClient = new UdpClient(new IPEndPoint(IPAddress.Any, 5555 )); IPEndPoint point = new IPEndPoint(IPAddress.Any, 0 ); byte [] b = null ; while (true ) { b = reciveClient.Receive(ref point); string ip = point.Address.ToString(); int port = point.Port; string msg = Encoding.UTF8.GetString(b); string content = string .Format("{0}:{1}\n{2}" , ip,port, msg); this .rtxtMsgList.Invoke(appentContentCallBack, content); } } public void AppendContent (string content ) { this .rtxtMsgList.AppendText(content+"\n" ); } } }
综合案例2-文件上传 服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace FileUpLoad_Server { internal class Program { static async Task Main (string [] args ) { FileTransferServer server = new FileTransferServer(5555 ); await server.StartAsync(); Console.ReadKey(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Net.Sockets;using System.Net;using System.Text;using System.Threading.Tasks;namespace FileUpLoad_Server { internal class FileTransferServer { private TcpListener listener; private int port; public FileTransferServer (int port ) { this .port = port; listener = new TcpListener(IPAddress.Any, port); } public async Task StartAsync () { listener.Start(); Console.WriteLine($"服务器已启动,IP:{this .GetIP()} 端口: {port} ." ); while (true ) { TcpClient client = await listener.AcceptTcpClientAsync(); await Task.Run(() => HandleClient(client)); } } private void HandleClient (TcpClient client ) { NetworkStream stream = client.GetStream(); byte [] buffer = new byte [1024 ]; int bytesRead; IPEndPoint point = client.Client.RemoteEndPoint as IPEndPoint; try { string fileName = $"{point.Address} -{DateTime.Now.ToString("hhmmss" )} .zip" ; Console.WriteLine($"正在接收文件{fileName} ..." ); using (FileStream fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write)) { while ((bytesRead = stream.Read(buffer, 0 , buffer.Length)) != 0 ) { fileStream.Write(buffer, 0 , bytesRead); } } Console.WriteLine($"文件{fileName} 接收完成!" ); } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { client.Close(); } } public string GetIP () { IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName()); string ip = "127.0.0.1" ; foreach (IPAddress ipAddress in host.AddressList) { if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !IPAddress.IsLoopback(ipAddress)) { ip = ipAddress.ToString(); } } return ip; } } }
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.IO;using System.Linq;using System.Net.Sockets;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;namespace FileUpLoad_Client { public partial class Form1 : Form { private string host; private int port; TcpClient server; private delegate void UpdateProgressCallBack (int length ) ; private UpdateProgressCallBack updateProgressCallBack; int maxLength; public Form1 () { InitializeComponent(); } private void button1_Click (object sender, EventArgs e ) { this .host = this .txtIP.Text.Trim(); this .port = int .Parse(this .txtPort.Text.Trim()); try { server = new TcpClient(host, port); this .button1.Text = "OK" ; this .button1.BackColor = Color.Green; } catch (Exception ex) { this .button1.Text = "Error" ; this .button1.BackColor = Color.Red; MessageBox.Show(ex.Message); } } private void button2_Click (object sender, EventArgs e ) { if (this .server == null ) { MessageBox.Show("请先连接服务器!" ); return ; } updateProgressCallBack = new UpdateProgressCallBack(UpdateProgress); FileStream fileStream = new FileStream(this .txtFilePath.Text, FileMode.Open, FileAccess.Read); this .progressBar1.Maximum = (int )fileStream.Length; this .maxLength = (int )fileStream.Length; Thread th = new Thread(SendFile); th.IsBackground = true ; th.Start(); } public void SendFile () { string filePath = this .txtFilePath.Text.Trim(); try { NetworkStream stream = server.GetStream(); using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { byte [] buffer = new byte [1024 ]; int bytesRead; while ((bytesRead = fileStream.Read(buffer, 0 , buffer.Length)) != 0 ) { stream.Write(buffer, 0 , bytesRead); this .progressBar1.Invoke(updateProgressCallBack, bytesRead); } } } catch (Exception ex) { MessageBox.Show(ex.Message); } finally { server.Close(); } } public void UpdateProgress (int length ) { this .progressBar1.Value += length; if (this .progressBar1.Value == this .maxLength) { MessageBox.Show("文件上传成功!" ); } } private void txtFilePath_MouseDoubleClick (object sender, MouseEventArgs e ) { if (this .openFileDialog1.ShowDialog() == DialogResult.OK) { this .txtFilePath.Text= this .openFileDialog1.FileName; } } } }
课后作业