一、套接字
Windows Sockets 只支持一个通信域:网际域(AF-INET),这个域被使用网际协议族通信的进程所使用。
TCP/IP 的 Socket 则提供3种类型的套接字
1、流式套接字(SOCK_STREAM)
提供面向连接、可靠的数据传输服务,数据无差错、无重复的发送,且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。文件传输协议(FTP)即使用流式套接字。
2、数据报式套接字(SOCK_DGRAM)
提供无连接服务。数据包以独立包形式发送,不提供无差错保证,数据可能丢失或重复,并且接收顺序混乱。网络文件系统(NFS)使用数据报式套接字。
3、原始套接字(SOCK_RAW)
该接口允许对较低层协议,如 IP、ICMP 直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备。
二、C/S 编程模式
通信的两个进程间相互作用的主要模式式客户机/服务器模式。
客户端: 请求另一台计算机上的服务(如访问数据库)的计算机。
服务端: 处理这些服务请求(例如对数据库进行检索,将结果返回)的计算机。
对称协议: 每一方都有可能扮演主从角色。
非对称协议: 一方被不可改变的认为是主机,而另一方则是从机。
三、Socket 编程的通信方式
(1)同步方法
通信的同步,指客户端在发送请求后,必须在服务端有回应后才能发送下一个请求。所以这个时候的所有请求将会在服务端得到同步。
(2)异步方法
通信的异步,指客户端在发送请求后,不必等待服务端的回应就可以发送下一个请求,这样对于所有的请求动作来说将会在服务端得到异步,这条请求的链路就像是一个请求队列,所有的动作在这里不会得到同步。
(3)阻塞方式
阻塞套接字是指执行此套接字的网络调用时,所调用的函数只有在得到结果之后才会返回,在调用结果返回之前,当前线程会被挂起,即此套接字一直阻塞在网络调用上。比如调用 StreamReader 类的 ReadLine() 方法读取网络缓冲区的数据,如果调用的时候没有数据到达,那么此 ReadLine() 方法将一直挂在调用上,直到读到一些数据,此函数才返回。
(4)非阻塞方法
非阻塞套接字是指在执行此套接字的网络调用时,即使不能立刻得到结果,该函数也不会阻塞当前线程,而会立刻返回。对于非阻塞套接字,同样调用 StreamReader 类的 ReadLine() 方法读取网络缓冲区的数据,不管是否读取数据都立即返回,而不会一直挂在此函数调用上。
阻塞对象上可以有非阻塞的调用方式,可以通过一定的 API 去轮询状态,在适当的时候调用阻塞函数,就可以避免阻塞。而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。
客户端/服务端结构的软件采用的方式就是异步非阻塞模式。在利用 C# 进行网络编程时,由于 .NET Framework SDK 对阻塞和非阻塞的工作机制进行了封装。
四、.NET 中的 Socket 类
1、Socket 类
1)TCP Socket:
2)UDP Socket:
Socket 类的构造函数原型:
public Socket(
AddressFamily addressFamily,//对于常规 IP 通信网络,只能使用 AddressFamily.InterNetwork。
SoncketType socketType,
ProtocolType protocolType
);
AddressFamily: 用来指定网络类型;
SoncketType: 用来指定套接字类型(即数据连接方法)
ProtocolType: 用来指定网络协议。
表- IP 套接字定义组合
SocketType 值 | ProtocolType 值 | 描述 |
---|---|---|
Stream | Tcp | 面向连接套接字 |
Dgram | Udp | 无连接套接字 |
Raw | Icmp | 网际消息控制协议套接字 |
Raw | Raw | 基础传输协议套接字 |
表- Socket 类的公共属性
属性名 | 描述 |
---|---|
AddressFaimly | 获取 Socket 的地址族 |
Available | 获取已经从网络接收且可供读取的数据量 |
Blocking | 获取或设置一个值,该值指示 Socket 是否处于阻塞模式 |
Connected | 获取一个值,该值指示 Socket 是否已连接到远程主机 |
Handle | 获取 Socket 的操作系统句柄 |
LocalEndPoint | 获取本地终结点 EndPoint |
RemoteEndPoint | 获取远程终结点 EndPoint |
ProtocolType | 获取 Socket 的协议类型 |
SocketType | 获取 Socket 的类型 |
2、Socket 类的常用方法
(1)Bind(EndPoint adddress)
在服务器端,当一个套接字被创建后,需要将它绑定到系统的一个特定地址。可以使用 Bind() 方法来完成,其参数为一个 IPEndPoint 实例(包含 IP 地址和端口信息)。
(2)Listen(int con_num)
服务器端的套接字完成了与地址的绑定后,就使用 Listen() 方法监听客户发送的连接请求。其参数 con_num 为一整型值,该值表示服务器可以接收的最大连接数目。超过这个数目的连接都会被拒绝。con_num 数值的设定会影响到服务器的运行,因为每个接受的连接都要使用 TCP 缓冲区,如果连接的数目过大,收发数据的缓存将减少。
(3)Accept()
在服务器进入监听状态时,如有从客户端发来的连接请求,服务器将使用 Accept() 方法来接受连接请求。 Accept() 返回一个新的套接字,该套接字包含所建立的连接的信息并负责处理本连接的所有通信。而服务器刚开始创建的套接字仍然负责监听,并在需要时调用 Accept() 接受新的连接请求。
(4)Send()
当服务器接受了来自客户端的连接请求后,服务器和客户端双方就可以利用 Send() 方法来发送数据。
表-Send()、Receive() 重载方法
方法 | 说明 |
---|---|
Send(byte[] data) | 将数据发送到连接的 Socket |
Send(byte[] data, SocketFlags sf) | 使用指定的 SocketFlags 将数据发送到连接的 Socket |
Send(byte[] data , int size, SocketFlags sf) | 使用指定的 SocketFlags,将指定字节数的数据发送到已连接的 Socket |
Send(byte[] data, int offset, int size, SocketFlags sf) | 使用指定的 SocketFlags,将指定字节数的数据发送到已连接的 Socket(从指定的偏移量开始) |
– | – |
Receive(byte[] data) | 从绑定的套接字接收数据,将数据存入接收缓冲区 |
Receive(byte[] data, SocketFlags sf) | 使用指定的 SocketFlags,从绑定的套接字接收数据,将数据存入接收缓冲区 |
Receive(byte[] data, int size, SocketFlags sf) | 使用指定的 SocketFlags,从绑定的套接字接收指定字节数的数据,并将数据存入接收缓冲区 |
Receive(byte[] data , int pffset, int size, SocketFlags sf) | 使用指定的 SocketFlags,从绑定的套接字接收指定字节数的数据,并将数据存入接收缓冲区的指定偏移量位置 |
(5)Receive()
当服务器接受了来自客户端的连接请求后,服务器和客户端双方就可以利用 Receive() 方法来接受数据。
(6) Connect(EndPoint remoteEP)
同服务器端一样,客户端的套接字建立后也必须与一个地址绑定。在客户端使用 Connect() 方法实现绑定,remoteEP 参数为索要连接的服务器端的 IPEndPoint 实例。调用 Connect() 方法后,它将一直阻塞到连接建立,如果连接不成功,将返回一个异常。
(7)Shutdown(SocketShutdown how)
当客户端和服务器端的通信结束时,必须关闭相应的套接字实例。可以使用 Shutdown() 方法来禁止该套接字上的发送和接收,Shutdown() 方法有一个枚举类型的参数,如 SocketShutdown.Send 表示禁用发送套接字,SocketShutdown.Receive 表示禁用接收套接字,SocketShutdown.Both 表示禁用发送和接收的套接字。
(8)Close()
禁止套接字上的发送和接收之后,使用 Close() 方法关闭套接字连接并释放所有相关资源。这样套接字会在系统内部缓冲区处理完毕后套接字并释放资源。
代码例子
static void Main(string[] args){//创建 IPEndPoint 实例IPAddress ipa = IPAddress.Parse("127.0.0.1");IPEndPoint ipep = new IPEndPoint(ipa, 8080);//创建 Socket 实例Socket test_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);Console.WriteLine("AddressFamily:{0}", test_socket.AddressFamily);Console.WriteLine("SocketType:{0}", test_socket.SocketType);Console.WriteLine("ProtocolType:{0}", test_socket.ProtocolType);Console.WriteLine("Blocking:{0}", test_socket.Blocking);//修改 Socket 实例的属性test_socket.Blocking = false;Console.WriteLine("new Blocking:{0}", test_socket.Blocking);Console.WriteLine("Connected:{0}", test_socket.Connected);//调用 Bind() 方法,使 Socket 与一个本地终结点相关联test_socket.Bind(ipep);IPEndPoint sock_iep = (IPEndPoint)test_socket.LocalEndPoint;Console.WriteLine("Local EndPoint:{0}", sock_iep.ToString());//关闭 Sockettest_socket.Close();Console.ReadKey();}
输出结果:
AddressFamily:InterNetwork
SocketType:Stream
ProtocolType:Tcp
Blocking:True
new Blocking:False
Connected:False
Local EndPoint:127.0.0.1:8080