三次握手
所谓三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。(通俗的说就是客户端向服务器申请通话)
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。(通俗的说就是服务器同意与客户端连接并返回消息给客户端)
(3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。(通俗的说就是客户端与服务器建立通话连接)
四次挥手
所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示:
由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。(客户端请求服务器切断连接)
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。(针对客户端的请求做出确认应答)
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。(服务器请求客户端切断连接)
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。(针对服务器的请求做出应答)
为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。
下面我们来实现下服务器的异步消息的接收与服务器处理客户端正常关闭和非正常关闭的操作,下面附上代码
namespace TCP服务器端
{
class Program
{
static void Main(string[] args)
{
StartServerAsync();
Console.ReadKey();
}
static void StartServerAsync() {
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//本机ip :127.0.0.1(永远是本地IP地址)
IPAddress ipAddress = IPAddress.Parse("192.168.0.144");
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 4747);
serverSocket.Bind(ipEndPoint);//绑定ip和端口号
Console.WriteLine("服务器已连接");
serverSocket.Listen(50);//开始监听端口号
// Socket clientSocket = serverSocket.Accept();//接收一个客户端连接
serverSocket.BeginAccept(AcceptCallBack,serverSocket);
}
static void AcceptCallBack(IAsyncResult ar) {
Socket serverSocket = ar.AsyncState as Socket;
Socket clientSocket= serverSocket.EndAccept(ar);
//向客户端发送一条消息
string msg = "Hello client! 你好...";
byte[] data = Encoding.UTF8.GetBytes(msg);
clientSocket.Send(data);
clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceivecallBack, clientSocket);//异步进行接受数据
serverSocket.BeginAccept(AcceptCallBack, serverSocket);
}
static byte[] dataBuffer = new byte[1024];
static void ReceivecallBack(IAsyncResult ar)
{
Socket clientSocket=null;
try
{
clientSocket = ar.AsyncState as Socket;
int count = clientSocket.EndReceive(ar);
if (count==0){
clientSocket.Close();
return;
}
string msg = Encoding.UTF8.GetString(dataBuffer, 0, count);
Console.WriteLine("从客户端接收到数据:" + msg);
clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceivecallBack, clientSocket);//异步进行接受数据
}
catch (Exception e)
{
Console.WriteLine(e);
if (clientSocket != null)
{
clientSocket.Close();
}
}
}
}
namespace TCP客户端
{
class Program
{
static void Main(string[] args)
{
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect(new IPEndPoint(IPAddress.Parse("192.168.0.144"), 4747));//连接ip和端口号
Console.WriteLine("客户端已连接");
byte[] data = new byte[1024];
int count = clientSocket.Receive(data);
string msg = Encoding.UTF8.GetString(data,0,count);
Console.WriteLine(msg);
while (true) {
string s = Console.ReadLine();
if (s=="c") {
clientSocket.Close();
return;
}
clientSocket.Send(Encoding.UTF8.GetBytes(s));
}
clientSocket.Close();
}
}
}
设计异步监听和异步接收数据后发现可以连接多个客户端,可以发送多条消息
当我们异常关闭客户端一号的时候
当然按c回车就是正常关闭客户端这样就不会报错
粘包和分包
解决方案直接上代码
客户端发送的时候使用BitConverter.GetBytes将数据长度转成byte数组,并且将数据长度和数据放到同一个byte里面一起发送给服务器端,下面是客户端发送的代码
namespace 客户端
{
class Message
{
public static byte[] GetButes(string data)
{
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
int dataLength = dataBytes.Length;
byte[] lengthBytes = BitConverter.GetBytes(dataLength);
byte[] newBytes = lengthBytes.Concat(dataBytes).ToArray();
return newBytes;
}
}
}
namespace 客户端
{
class Program
{
static void Main(string[] args)
{
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect(new IPEndPoint(IPAddress.Parse("192.168.31.145"), 4747));//连接ip和端口号
Console.WriteLine("客户端已连接");
byte[] data = new byte[1024];
int count = clientSocket.Receive(data);
string msg = Encoding.UTF8.GetString(data, 0, count);
Console.WriteLine(msg);
for (int i = 0; i < 100; i++)
{
clientSocket.Send(Message.GetButes("客户端发送了消息"+i.ToString()));
}
Console.ReadKey();
clientSocket.Close();
}
}
}
服务器端接收的时候也是需要去将数据长度解析出来。
namespace Tcp服务器端
{
class Message
{
public int RemainSize
{
get {return Data.Length - startIndex; }
}
public byte[] Data { get; } = new byte[1024];
public int startIndex { get; private set; } = 0;
public void AddCount(int count)
{
startIndex += count;
}
public void ReadMessage()
{
while (true)
{
if (startIndex <=4)
return;
int count = BitConverter.ToInt32(Data, 0);
if (startIndex - 4 >= count)
{
string s = Encoding.UTF8.GetString(Data, 4, count);
Console.WriteLine("解析出一条数据" + s);
Array.Copy(Data, count + 4, Data, 0, startIndex - 4 - count);
startIndex -= (count + 4);
}
else
{
break;
}
}
}
}
}
namespace Tcp服务器端
{
class Program
{
static void Main(string[] args)
{
StartServerAsync();
Console.ReadKey();
}
static void StartServerAsync()
{
Socket serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPAddress ipAddress = IPAddress.Parse("192.168.31.145");
IPEndPoint end =new IPEndPoint(ipAddress, 4747);
serverSocket.Bind(end);
serverSocket.Listen(50);
serverSocket.BeginAccept(AcceptCallBack,serverSocket);
}
static Message msg=new Message();
static void AcceptCallBack(IAsyncResult ar)
{
Socket serverSocket = ar.AsyncState as Socket;
Socket clientSocket = serverSocket.EndAccept(ar);
string msgStr = "Hello client! 你好...";
byte[] data = Encoding.UTF8.GetBytes(msgStr);
clientSocket.Send(data);
clientSocket.BeginReceive(msg.Data, msg.startIndex, msg.RemainSize, SocketFlags.None, ReceivecallBack, clientSocket);
serverSocket.BeginAccept(AcceptCallBack,serverSocket);
}
static byte[] dataBuffer=new byte[1024];
static void ReceivecallBack(IAsyncResult ar)
{
Socket clientsocket =null;
try
{
clientsocket = ar.AsyncState as Socket;
int count = clientsocket.EndReceive(ar);
if (count == 0)
{
clientsocket.Close();
return;
}
msg.AddCount(count);
msg.ReadMessage();
clientsocket.BeginReceive(msg.Data, msg.startIndex, msg.RemainSize, SocketFlags.None, ReceivecallBack, clientsocket);
}
catch (Exception e)
{
Console.WriteLine(e);
if (clientsocket != null)
clientsocket.Close();
}
}
}
}
发送到服务器端的时候可以看到前面4个是数据长度25,后面的第4个到第28个是接收到的数据。