第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方式通信
服务端
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
namespace Socket_TCP_Server
{
/// <summary>
/// 基于TCP协议的Socket服务端
/// </summary>
class Program
{
static void Main(string[] args)
{
//1.创建服务端Socket
Socket serverSockekt = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.绑定IP和端口
IPEndPoint point = new IPEndPoint(IPAddress.Parse("10.168.1.167"), 5555);
serverSockekt.Bind(point);
//3.启动监听(设置最大监听数量)
serverSockekt.Listen(100);
Console.WriteLine("服务端已启动,正在监听客户端....");
//4.等待客户端连接
while (true)
{
//客户端
Socket clientSocket= serverSockekt.Accept();
//为指定客户端开辟一个线程接收消息
new Thread(new ParameterizedThreadStart(Receive)).Start(clientSocket);
}
}
/// <summary>
/// 接收消息
/// </summary>
/// <param name="obj"></param>
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));
}
}
}
}
客户端
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Socket_TCP_Client
{
/// <summary>
/// 基于TCP协议的Socket客户端
/// </summary>
class Program
{
static void Main(string[] args)
{
//1.Socket
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.连接到服务器(Ip地址和端口号要和服务器保持一致)
IPAddress ip = IPAddress.Parse("10.168.1.167");
IPEndPoint point = new IPEndPoint(ip, 5555);
try
{
socket.Connect(point);
Console.WriteLine("连接成功,请输入要发送的消息!");
//3.发送消息
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方式通信
服务端
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Socket_UDP_Server
{
/// <summary>
/// 基于UDP协议的服务端
/// </summary>
class Program
{
static Socket serverSocket = null;
static void Main(string[] args)
{
//创建Socket
serverSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
//绑定ip和端口号
serverSocket.Bind(new IPEndPoint(IPAddress.Parse("10.168.1.167"), 5555));
Console.WriteLine("服务端已启动!");
//接收数据
new Thread(Receive).Start();
Console.ReadKey();
}
/// <summary>
/// 接收消息
/// </summary>
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));
}
}
}
}
客户端
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Socket_UDP_Client
{
/// <summary>
/// 基于UDP协议的Socket客户端
/// </summary>
class Program
{
static void Main(string[] args)
{
//1.客户端Socket
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPEndPoint targetPoint = new IPEndPoint(IPAddress.Parse("10.168.1.167"), 5555);
//2.发送数据
Console.WriteLine("请输入要发送的数据!");
while (true)
{
string line = Console.ReadLine();
clientSocket.SendTo(Encoding.UTF8.GetBytes(line), targetPoint);
}
}
}
}
运行效果
Listener-TCP方式通信
服务端
using System;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Text;
namespace TcpListener_Server
{
/// <summary>
/// 基于TCP协议的监听器 服务端
/// </summary>
class Program
{
static void Main(string[] args)
{
//1.对socket进行封装,tcpListener内本身包含套接字
TcpListener listener = new TcpListener(IPAddress.Parse("10.168.1.167"),5555);
//2.开始监听
listener.Start();
Console.WriteLine("服务端已启动监听...");
//3.等待客户端连接
TcpClient client = listener.AcceptTcpClient();
//4.获取客户端数据流通道
NetworkStream stream= client.GetStream();
//5.接收数据
byte[] b = new byte[1024];
while (true)
{
int length = stream.Read(b, 0, b.Length);
//string ip = client.Client.RemoteEndPoint.ToString().Split(':')[0];
string ip = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
string msg = Encoding.UTF8.GetString(b, 0, length);
Console.WriteLine(string.Format("{0}:{1}", ip, msg));
}
//6.释放资源
//tream.Close();
//client.Close();
//listener.Stop();
}
}
}
客户端
using System;
using System.Net.Sockets;
using System.Text;
namespace TcpListener_Client
{
/// <summary>
/// 基于TCP协议的监听器 客户端端
/// </summary>
class Program
{
static void Main(string[] args)
{
//1.创建TCP对象的时候,就会与服务器建立联系
TcpClient client = new TcpClient("10.168.1.167", 5555);
//2.通过网络流获取数据信息
NetworkStream stream = client.GetStream();
Console.WriteLine("与服务端连接成功,请输入要发送的数据...");
while (true)
{
//手动输入需要发送的信息
string message = Console.ReadLine();
byte[] data = Encoding.UTF8.GetBytes(message);
//将信息写入网络流
stream.Write(data, 0, data.Length);
}
//3.释放资源
//stream.Close();
//client.Close();
Console.ReadKey();
}
}
}
运行效果
UdpClient方式通信
消息接收方
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
{
/// <summary>
/// 基于UDP协议的UDPClinet 消息接收方
/// </summary>
class Program
{
static void Main(string[] args)
{
//1.本地UDPClient
UdpClient udpLocal = new UdpClient(new IPEndPoint(IPAddress.Parse("10.168.1.167"), 5555));
//2.接收数据
IPEndPoint point = new IPEndPoint(IPAddress.Any, 0);
byte[] b = null;
Console.WriteLine("等待接收消息...");
while (true)
{
b = udpLocal.Receive(ref point);//通过point确定数据来自哪个ip和端口号,并返回接收的数据
string ip = point.Address.ToString();
string msg = Encoding.UTF8.GetString(b);
Console.WriteLine(string.Format("{0}:{1}", ip, msg));
}
Console.ReadKey();
}
}
}
消息发送方
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
{
/// <summary>
/// 基于UDP协议的UDPClinet 消息发送方
/// </summary>
class Program
{
static void Main(string[] args)
{
//1.本地UdpClient
UdpClient client = new UdpClient();
//2.发送消息
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个二进制位。
代码案例
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();
}
/// <summary>
/// 窗体加载
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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
{
// 广播地址,对于IPv4来说,255代表广播地址
IPAddress targetAddress=IPAddress.Parse("255.255.255.255");
//端口
int targetPort=5555;
//广播结点
IPEndPoint groupEP= new IPEndPoint(targetAddress, targetPort);
// UdpClient实例
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);
}
}
/// <summary>
/// 接收消息
/// </summary>
public void ReciveMessage()
{
//1.本地UDPClient
UdpClient reciveClient = new UdpClient(new IPEndPoint(IPAddress.Any, 5555));//IPAddress.Parse("172.16.220.26")
//2.接收数据
IPEndPoint point = new IPEndPoint(IPAddress.Any, 0);
byte[] b = null;
while (true)
{
b = reciveClient.Receive(ref point);//通过point确定数据来自哪个ip和端口号,并返回接收的数据
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);
}
}
/// <summary>
/// 追加内容
/// </summary>
/// <param name="content"></param>
public void AppendContent(string content)
{
this.rtxtMsgList.AppendText(content+"\n");
}
}
}
综合案例2-文件上传
服务端
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();
}
}
}
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
{
/// <summary>
/// 文件上传服务类
/// </summary>
internal class FileTransferServer
{
private TcpListener listener;
private int port;
public FileTransferServer(int port)
{
this.port = port;
listener = new TcpListener(IPAddress.Any, port);
}
/// <summary>
/// 启动
/// </summary>
/// <returns></returns>
public async Task StartAsync()
{
listener.Start();
Console.WriteLine($"服务器已启动,IP:{this.GetIP()} 端口: {port}.");
while (true)
{
TcpClient client = await listener.AcceptTcpClientAsync();
await Task.Run(() => HandleClient(client));
}
}
/// <summary>
/// 处理客户端
/// </summary>
/// <param name="client"></param>
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();
}
}
/// <summary>
/// 获取本机IP
/// </summary>
/// <returns></returns>
public string GetIP()
{
// 获取本地主机的相关信息
IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
string ip = "127.0.0.1";
// 遍历所有IP地址
foreach (IPAddress ipAddress in host.AddressList)
{
// 过滤掉IPv6地址和非本地链接地址
if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !IPAddress.IsLoopback(ipAddress))
{
ip = ipAddress.ToString();
}
}
return ip;
}
}
}
客户端
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();
}
/// <summary>
/// 连接
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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();
}
/// <summary>
/// 发送文件
/// </summary>
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();
}
}
/// <summary>
/// 更新进度条
/// </summary>
/// <param name="length"></param>
public void UpdateProgress(int length)
{
this.progressBar1.Value += length;
if (this.progressBar1.Value == this.maxLength)
{
MessageBox.Show("文件上传成功!");
}
}
/// <summary>
/// 选择文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void txtFilePath_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (this.openFileDialog1.ShowDialog() == DialogResult.OK)
{
this.txtFilePath.Text= this.openFileDialog1.FileName;
}
}
}
}