zhouqijie

一、概述

Osi网络七层模型

OSI网络七层模型 功能 TCP/IP参考模型 对应网络协议
应用层 文件传输、文件服务、电子邮件、虚拟终端。 应用层 HTTP、FTP、DNS
表示层 数据格式化、代码转换、数据加密。
会话层 解除或者建立与其他结点的连接。
传输层 提供端对端接口。 传输层 TCP、UDP
网络层 为数据包选择路由 网络层 IP、ICMP...
数据链路 传输有地址的帧,错误检测功能。 网络接口 ...
物理层 以二进制形式在物理媒体上传输数据。

TCP 和 UDP

2.面向报文。

3.不可靠传输。

4.头部开销小,高效。

2.面向字节流传输。

3.可靠传输。

4.效率较低。

Socket抽象层

两个进程需要通信必须唯一标识一个进程。本地进程通讯可以用PID。网络进程通信使用IP+协议+端口可以唯一标识网络中的一个进程。

Socket是在应用层和传输层之间的一个抽象层,把TCP/IP层的复杂操作抽象为几个简单接口供应用层调用,以实现网络进程通信。

1、服务器申请一个welcoming socket,绑定到一个IP和一个端口上。 2.开始监听端口。(监听客户端连接信息)(不负责通信) 3.接到连接请求后,产生一个新的socket(端口大于1024)与客户端建立连接并通信。 4.原welcoming socket继续监听。

服务端:

1.客户端申请一个client socket。 2.连接服务器指定端口。

  • 报式Socket(DATAGRAM):

    对应UDP协议。

二、System.Net.Sockets.Socket

构造函数:

public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType pProtocolType)
//示例:
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

方法:

Bind(); //绑定一个本地的IP和端口
Listen();//让Socket侦听传入的连接尝试,并指定侦听队列容量。
Connect();//初始化与另一个Socket的连接。
Accept();//接收连接并返回一个新的Socket。(阻塞线程)

Send();//输出数据到Socket。
Receive();//从Socket读取数据。(阻塞线程)


Close();//关闭socket。
Shutdown(...);//关闭socket。

关于socket关闭:

Close()和Shutdown()都可以关闭socket,但是有区别:

shutdown是一种优雅地单方向或者双方向关闭socket的方法。shutdown先把socket缓冲区中的数据发送出去,然后发送FIN数据包。并且这个时候,并没有释放本地资源。

close则立即双方向暴力关闭socket并释放相关资源。closesocket会把socket缓冲区中的数据先马上丢弃,然后发送FIN数据包。并且关闭本地文件对象,释放资源。

补充:

close把描述符的引用计数减1,仅在该计数变为0时关闭套接字(即发送FIN)。shutdown可以不管引用计数就激发TCP的正常连接终止序列。如果有多个进程共享一个socket,shutdown影响所有进程,而close只影响本进程。

三、流式Socket通信实现

示例:字符串发送(聊天室)

//------------示例聊天室服务端代码-------------------------
Socket welcomingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAddress = IPAddress.Parse("192.168.0.101");
IPEndPoint endPoint = new IPEndPoint(ipAddress, 23333);

welcomingSocket.Bind(endPoint);
welcomingSocket.Listen(100);

while (true)
{
    Socket newSocket = welcomingSocket.Accept();

    //实例化socket连接对象。(构造函数创建新线程开始ReceiveMsg方法循环接收数据)
    //每次接收到数据都要调用列表中所有socket连接对象的SendMsg方法进行广播。
    SocketConnection conn = new SocketConnection(newSocket);
    //添加进列表。
    conns.Add(conn);
}
//------------示例聊天室客户端代码-------------------------
//对象声明:
public Socket clientSocket;
public IPEndPoint endPoint;
//建立Socket连接:
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
endPoint = new IPEndPoint(IPAddress.Parse("192.168.0.101"), 23333);
clientSocket.Connect(endPoint);
//开启新线程,循环接收消息:
//略

示例:其他多种信息发送(文件/震动)

⭐设计协议:

例如把传递的二进制信息前面都加一个字节作为标识。字符串信息用0开头+字符串字节数组。文件信息用1开头+文件二进制信息。

补充:判断远程主机是否断开

⭐不能使用Socket.Connected属性来判断远程主机是否断开。Socket类的Connected属性只表示最后一次I/O操作的状态,如果这之后远程主机断开了,它还一直返回true, 除非你再通过socket来发送数据。

⭐可根据Socket.Receive()方法返回长度是否为0来判断。但是只适用于ShutDown()关闭的socket,Close()关闭的和意外关闭的都不行。

⭐可使用Poll(Int32, SelectMode)方法检查socket状态,从而进行判断。但是此方法无法检测某些类型的连接问题,例如网络电缆损坏或远程主机已关闭意外。 您必须尝试发送或接收数据,以检测这些类型的错误。

msdn: 为 SelectMode.SelectRead 参数指定 selectMode 以确定 Socket 是否可读。 指定 SelectMode.SelectWrite 以确定 Socket 是否可写。 使用 SelectMode.SelectError 检测错误条件。 Poll 将阻止执行到指定的时间段(以为单位) microseconds 。 如果要 microSeconds 无限期等待响应,请将参数设置为负整数。 如果要检查多个套接字的状态,可能更倾向于使用 Select 方法。

补充:如果远程主机意外断开

客户端意外断开时,服务器如果处于Receive中,则会错误关闭。应该在try语句块中Receive信息,并且不要在catch中throw抛出错误信息,否则也会发生错误关闭,但是可以exception.ToString()打印出来。

补充:心跳机制

参考:https://blog.csdn.net/qq_23167527/article/details/54290726

要判定掉线,只需要send或者recv一下,如果结果为零,则为掉线。但是,在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。

它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。

在获知了断线之后,服务器逻辑可能需要做一些事情,比如断线后的数据清理呀,重新连接呀……当然,这个自然是要由逻辑层根据需求去做了。

总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。