一、背景与概念
1.标准以太网
以太网是美国Xerox(施乐)公司的Palo Alto研究中心于1975年研制成功的,其核心技术起源于ALOHA网。目前以太网是指符合IEEE 802.3标准的局域网(LAN)产品组,其中IEEE 802.3是一组电气与电子工程师协会(IEEE)标准,用于定义有线以太网媒体访问控制的物理层和数据链路层,说明了配置以太网网络的规则,以及各种网络元件如何彼此协作。以太网分为:
1.标准以太网:早期的10Mbps以太网称之为标准以太网,是一种总线型局域网,由同轴电缆、网卡(网络适配器)组成。。
发送和接收数据通过CSMA/CD协议完成,流程可以简单概括为四点:先听后发,边听边发,冲突停止,延迟重发。
2.总线以太网通过中继器、网桥进行扩展。
中继器(Repeater)也叫转发器,将在传输介质中衰减的信号放大,以增加线缆的传输距离。
网桥(Bridge)又称桥接器,是一种存储转发设备。它工作在数据链路层,当它收到一个帧时,先检查此帧的目的MAC地址,然后再确定将该帧转发到哪一个端口。
以太网种类繁多,包括基于集线器的以太网,基于交换机的以太网等,目前已发展到十万兆以太网。。
2.工业以太网
工业以太网一般来讲是指技术上与商用以太网(即IEEE802.3标准)兼容,但在产品设计时,在材质的选用、产品的强度、适用性以及实时性等方面能满足工业现场的需要。
目前包括4种主要协议:HSE、Modbus TCP/IP、ProfINet、Ethernet/IP。
基金会现场总线FF于2000年发布Ethernet规范,称HSE(High Speed Ethernet)。
Modbus TCP/IP协议由施耐德公司推出,以一种非常简单的方式将Modbus帧嵌入到TCP帧中,使Modbus与以太网和TCP/IP结合。
德国西门子于2001年发布了ProfiNet网络方案,它是将原有的Profibus与互联网技术结合。ProfiNet采用标准TCP/IP协议加上应用层的RPC/DCOM来完成节点间的通信和网络寻址。
Ethernet/IP是适合工业环境应用的协议体系,基于CIP(Controland Information Proto-Col)协议。它是一种是面向对象的协议,能够保证网络上隐式(控制)的实时I/O信息和显式信息(包括用于组态、参数设置、诊断等) 的有效传输。
工业以太网设备包括以下几个重要部分:
1.工业以太网集线器
2.工业以太网非管理型交换机
3.工业以太网管理型交换机
4.工业以太网管理型冗余交换机
3.TCP/IP协议
上表为网络协议基本功能与协议,TCP(Transmission Control Protocol)协议在传输层,IP(Internet Protocol)协议在网络层。
1.TCP协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议,是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。互联网络与单个网络有很大的不同,因为互联网络的不同部分可能有截然不同的拓扑结构、带宽、延迟、数据包大小和其他参数。TCP的设计目标是能够动态地适应互联网络的这些特性,而且具备面对各种故障时的健壮性。
2.IP协议:是网络之间信息传送的协议,可将IP信息包从源设备(例如用户的计算机)传送到目的设备(例如某部门的www服务器)。其目的一是解决互联网问题,实现大规模、异构网络的互联互通;二是分割顶层网络应用和底层网络技术之间的耦合关系,以利于两者的独立发展。
3.三次握手,四次分手
其中,SYN:同步序列编号(Synchronize Sequence Numbers),是TCP/IP建立连接时使用的握手信号。ACK:ACK (Acknowledge character)即是确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误。
FIN_WAIT:FIN_WAIT_1和FIN_WAIT_2都是表示等待对方的FIN报文。
4. 套接字
应用层和传输层之间,是使用套接字来进行分离。套接字包含两个信息:连接至远程的本地的端口信息(本机地址和端口号),连接到的远程的端口信息(远程地址和端口号)。
5.TcpClient和TcpListener
.NET提供了这两个类将对套接字的编程进行了一个封装。
一般将发起连接的一方称为客户端,另一端称为服务端,则现在可以得出:总是服务端在使用TcpListener类,因为它需要建立起一个初始的连接。
IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });TcpListener listener = new TcpListener(ip, 8500);listener.Start(); // 开始侦听Console.WriteLine("Start Listening ...");
8500端口:提供获取服务列表、注册服务、注销服务等HTTP接口;提供UI服务,使用cmd:netstat -a;
每创建一个新的TcpClient便相当于创建了一个新的套接字Socket去与服务端通信,.Net会自动为这个套接字分配一个端口号.
创建TcpClient类型实例时,可以在构造函数中指定远程服务器的地址和端口号。这样在创建的同时,就会向远程服务端发送一个连接请求(“握手”),一旦成功,则两者间的连接就建立起来了。也可以使用重载的无参数构造函数创建对象,然后再调用Connect()方法,在Connect()方法中传入远程服务器地址和端口号,来与服务器建立连接,不管是使用有参数的构造函数与服务器连接,或者是通过Connect()方法与服务器建立连接,都是同步方法(或者说是阻塞的,英文叫block)。
6.端口通讯
在与服务端的连接建立以后,我们就可以通过此连接来发送和接收数据。端口与端口之间以流(Stream)的形式传输数据,因为几乎任何对象都可以保存到流中,所以实际上可以在客户端与服务端之间传输任何类型的数据。对客户端来说,往流中写入数据,即为向服务器传送数据;从流中读取数据,即为从服务端接收数据。对服务端来说,往流中写入数据,即为向客户端发送数据;从流中读取数据,即为从客户端接收数据。
二、示例
1.客户端和服务端建立连接
分别建立两个控制台程序。
class Client{static void Main(string[] args){TcpClient client = new TcpClient();try{client.Connect("local", 8500); }catch (Exception ex){Console.WriteLine(ex.Message);return;}Console.WriteLine("Server Connected!{0} --> {1}",client.Client.LocalEndPoint, client.Client.RemoteEndPoint);}}
static void Main(string[] args){Console.WriteLine("Server is running ... ");IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });TcpListener listener = new TcpListener(ip, 8500);listener.Start(); Console.WriteLine("Start Listening ...");TcpClient remoteClient = listener.AcceptTcpClient();Console.WriteLine("Client Connected!{0} <-- {1}",remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint); }
结果就不显示了,反正都成功了,两个应用程序建立了连接。
2.一个客户端发出多个请求
static void Main(string[] args){const int BufferSize = 8192; Console.WriteLine("Server is running ... ");IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });TcpListener listener = new TcpListener(ip, 8500);listener.Start(); Console.WriteLine("Start Listening ...");TcpClient remoteClient = listener.AcceptTcpClient();Console.WriteLine("Client Connected!{0} <-- {1}",remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);NetworkStream streamToClient = remoteClient.GetStream();do{byte[] buffer = new byte[BufferSize];int bytesRead = streamToClient.Read(buffer, 0, BufferSize);Console.WriteLine("Reading data, {0} bytes ...", bytesRead);string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);Console.WriteLine("Received: {0}", msg);} while (true);}
class Client{static void Main(string[] args){Console.WriteLine("Client Running ...");TcpClient client;try{client = new TcpClient();client.Connect("localhost", 8500); catch (Exception ex){Console.WriteLine(ex.Message);return;}Console.WriteLine("Server Connected!{0} --> {1}",client.Client.LocalEndPoint, client.Client.RemoteEndPoint);NetworkStream streamToServer = client.GetStream();ConsoleKey key;Console.WriteLine("Menu: M - Send, G - Exit");do{key = Console.ReadKey(true).Key;if (key == ConsoleKey.M){Console.Write("Input the message: ");string msg = Console.ReadLine();byte[] buffer = Encoding.Unicode.GetBytes(msg); streamToServer.Write(buffer, 0, buffer.Length); Console.WriteLine("Sent: {0}", msg);}} while (key != ConsoleKey.G);//client.Close();}}
###3.服务端回传处理过的字符
服务端截取客户端传输的字符串,回传给客户端。
static void Main(string[] args){const int BufferSize = 8192; ConsoleKey key;Console.WriteLine("Server is running ... ");IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });TcpListener listener = new TcpListener(ip, 8500);listener.Start(); Console.WriteLine("Start Listening ...");TcpClient remoteClient = listener.AcceptTcpClient();Console.WriteLine("Client Connected!{0} <-- {1}",remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);NetworkStream streamToClient = remoteClient.GetStream();do{byte[] buffer = new byte[BufferSize];int bytesRead;try{lock (streamToClient){bytesRead = streamToClient.Read(buffer, 0, BufferSize);}if (bytesRead == 0) throw new Exception("读取到0字节");Console.WriteLine("Reading data, {0} bytes ...", bytesRead);string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);Console.WriteLine("Received: {0}", msg);msg = msg.Substring(2);buffer = Encoding.Unicode.GetBytes(msg);lock (streamToClient){streamToClient.Write(buffer, 0, buffer.Length);}Console.WriteLine("Sent: {0}", msg);}catch (Exception ex){Console.WriteLine(ex.Message);break;}} while (true);streamToClient.Dispose();remoteClient.Close();Console.WriteLine("\n\n输入\"Q\"键退出。");do{key = Console.ReadKey(true).Key;} while (key != ConsoleKey.Q);}
class Client{static void Main(string[] args){Console.WriteLine("Client Running ...");TcpClient client;ConsoleKey key;const int BufferSize = 8192;try{client = new TcpClient();client.Connect("localhost", 8500); }catch (Exception ex){Console.WriteLine(ex.Message);return;}Console.WriteLine("Server Connected!{0} --> {1}",client.Client.LocalEndPoint, client.Client.RemoteEndPoint); NetworkStream streamToServer = client.GetStream();Console.WriteLine("Menu: S - Send, X - Exit");do{key = Console.ReadKey(true).Key;if (key == ConsoleKey.S){Console.Write("Input the message: ");string msg = Console.ReadLine();byte[] buffer = Encoding.Unicode.GetBytes(msg); try{lock (streamToServer){streamToServer.Write(buffer, 0, buffer.Length);}Console.WriteLine("Sent: {0}", msg);int bytesRead;buffer = new byte[BufferSize];lock (streamToServer){bytesRead = streamToServer.Read(buffer, 0, BufferSize);}msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);Console.WriteLine("Received: {0}", msg);}catch (Exception ex){Console.WriteLine(ex.Message);break;}}} while (key != ConsoleKey.X);streamToServer.Dispose();client.Close();Console.WriteLine("\n\n输入\"Q\"键退出。");do{key = Console.ReadKey(true).Key;} while (key != ConsoleKey.Q); }}
三、TCP端口状态说明
1.LISTENING状态
FTP (文件传输协议)服务器启动时后处于这一状态,直到和客户端连接成功。
2.ESTABLISHED状态
连接成功就处于这一状态,表示两端正在通讯。
3.CLOSE_WAIT状态
对方主动关闭连接或者网络异常导致连接中断均会变成此状态。
4.TIME_WAIT状态
主动调用close()断开连接,收到对方确认后状态变为TIME_WAIT。TCP协议规定TIME_WAIT状态会一直持续2MSL(两倍的分段最大生存期),以此来确保旧的连接状态不会对新连接产生影响。
5.SYN_SENT状态
SYN_SENT状态表示请求连接,当你要访问其它的计算机的服务时首先要发个同步信号给该端口,此时状态为SYN_SENT,如果连接成功了就变为 ESTABLISHED,此时SYN_SENT状态非常短暂。如果发现SYN_SENT非常多且在向不同的机器发出,机器可能中病毒了。
四、总结
总结下Socket:
1,建立服务端和客户端的连接。
2,客户端发送消息给服务端。
3,服务端接收到客户端的连接。
4,服务端对消息进行处理再发给客户端。
5. 客户端收到消息。
6. 结束。
参考文章:
以太网
七层网络协议–tcp/ip协议
网络编程