TCP/IP协议三次握手与四次挥手与使用异步方式进行消息的接收

三次握手

所谓三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:

20131022025346218.png

(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来触发,整个流程如下图所示:

20131022025350523.png

由于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();
        }
    }
}

设计异步监听和异步接收数据后发现可以连接多个客户端,可以发送多条消息


Paste_Image.png

当我们异常关闭客户端一号的时候


Paste_Image.png
当然按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个是接收到的数据。
数据
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容