C#网络编程(一)

Sokect

在计算机通信领域,Socket被翻译为“套接字”,他是计算机之间进行通信的一种约定或者是一种方式。通过Socket这种约定,一台计算机可以接受到其他计算机的数据,也可以向其他计算机发送数据。

TCP和UDP

TCP是面向连接的,保证可靠的数据传输,每次建立连接都需要经历三次握手,数据传输完成都需要经历4次挥手断开连接,由于TCP是面向连接的所以只能用于端到端的通信。
UDP是面向无连接的通讯协议,每次发送数据不需要建立连接,因此可以用于广播发送并不局限于端到端。

TCP和UDP区别

UDP:

1.面向无连接,将数据及源封装在数据包中,不需要建立连接

2.每个数据报的大小限制在64K内

3.因为无连接 是不可靠协议

4.不需要建立连接,速度快

TCP:

1.建立连接,形成传输数据的通道

2.在连接中进行大数据量传输,以字节流的形式

3.通过三次握手(四次挥手)完成连接,是可靠协议

4.必须建立连接,效率会稍低

TCP三次握手四次挥手

一般需要了解一下几个字段:

序号:Seq序号,占32位,用来表示从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标识

确认序号:ACK序号,占32位,只有ACK标志位为1时,确认序号字段才有效,ACK=Seq+1

标志位共六个:URG、ACK、PSH、RST、SYN、FIN含义:

URG:紧急指针

ACK:确认序号有效

PSH:接收方应该尽快将这个报文交给应用层

RST:充值连接

SYN:发起一个新连接

FIN:释放一个连接
在这里插入图片描述
第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SEND状态,等待Server确认。

第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ACK=J+1,随机产生一个seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RECV状态。

第三次握手:Client收到确认后,检查ACK是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ACK=K+1,并将数据包发送给Server,Server检查ACK是否为K+1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间就可以开始传输数据了。
在这里插入图片描述
由于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状态,完成四次挥手

服务端

using System;
using System.Net;
using System.Net.Sockets;


class namespace Server
{
    static void Main(string[] args)
    {
        Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
        IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 88);
        //绑定ip和端口号
        serverSocket.Bind(ipEndPoint);
        serverSocket.Listen(0);//开始监听端口号
        Console.WriteLine("Server Start");
        Socket clientSocket = serverSocket.Accept();//接收客户端连接
        //向客户端发送消息
        string msg = " 你好 Client!";
        byte[] data = System.Text.Encoding.UTF8.GetBytes(msg);
        clientSocket.Send(data);
        //接收客户端消息
        byte[] dataBuffer = new byte[1024];
        int count = clientSocket.Receive(dataBuffer);
        string msgReceive = System.Text.Encoding.UTF8.GetString(dataBuffer, 0, count);
        Console.Write(msgReceive);

        Console.ReadLine();
        clientSocket.Close();
        serverSocket.Close();
    }
}


客户端

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;


class Client
{
    static void Main(string[] args)
    {
        Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 88));

        //接收消息
        byte[] data = new byte[1024];
        int count = clientSocket.Receive(data);//接收到数据才继续执行
        string msg = Encoding.UTF8.GetString(data, 0, count);
        Console.WriteLine(msg);
        //发送消息
        string s = Console.ReadLine();
        Console.Write(s);
        clientSocket.Send(Encoding.UTF8.GetBytes(s));
        
        Console.ReadLine();
        clientSocket.Close();
    }
}


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
以上是同步接收数据,只能接收一个客户端的消息

异步接收

服务器端:

class Server
{
    static void Main(string[] args)
    {
        StartServerAsync();
        Console.ReadLine();

    }

    public static void StartServerAsync() {
        Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
        IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 88);
        //绑定ip和端口号
        serverSocket.Bind(ipEndPoint);
        serverSocket.Listen(0);//开始监听端口号
        Console.WriteLine("Server Start");
        //Socket clientSocket = serverSocket.Accept();
        //异步接收客户端连接
        serverSocket.BeginAccept(AcceptCallBack, serverSocket);
        
    }

    public static void AcceptCallBack(IAsyncResult ar) {
        Socket serverSocket = ar.AsyncState as Socket;
        Socket clientSocket = serverSocket.EndAccept(ar);
        //向客户端发送消息
        string msg = " 你好 Client!";
        byte[] data = System.Text.Encoding.UTF8.GetBytes(msg);
        clientSocket.Send(data);
        //接收客户端消息
        clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceiveCallBack, clientSocket);//异步接收数据

        serverSocket.BeginAccept(AcceptCallBack, serverSocket);

    }

    public static byte[] dataBuffer = new byte[1024];
    public 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();
            }
        }
    }
}

客户端:

class Client
{
    static void Main(string[] args)
    {
        Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 88));

        //接收消息
        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));

        }
    }
}

粘包和分包

粘包产生原因:
(1)发送端原因:由于TCP本身的优化机制,如果发送的网络数据很多,数据包太小,那么会启动Nagle算法(可配置是否启用)对较小的数据包进行合并再发送。服务器在接收到消息(数据流)的时候就无法区分哪些数据包是客户端自己分开发送的,这样产生了粘包。
(2)接收端原因:服务器在接收到数据库后,放到缓冲区中,如果消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的情况,造成粘包现象。

分包产生的原因:在取数据的时候,只取到了一部分(与接收的缓冲区大小有关系),一个数据包被分成了多次接收。

在这里插入图片描述
解决办法:客户端发送数据时,将数据长度也告诉服务端,这样服务端就知道需要解析多长的数据,从而避免粘包。

客户端代码:
客户端处理比较简单,只需要在数据的头部加上该数据长度,以便服务端解析。新建一个类来处理。

public class Message
    {
        public static byte[] GetBytes(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;
        }
    }

将要发送的数据经过这个方法处理后,数据头部就有四个字节来表示数据长度。

服务端代码:
服务端接收到数据后,首先需要解析出头部四个字节,得到数据长度。然后根据长度解析出相应数据。也新建一个类来处理。

 class Message
    {
        private byte[] data = new byte[1024];
        private int startIndex = 0;//数据标志位

        public void AddCount(int count)
        {
            startIndex += count;
        }

        public byte[] Data {
            get { return data; }

        }

        public int StartIndex {
            get { return startIndex; }
        }
        //剩余长度
        public int RemainSize {
            get { return data.Length - startIndex; }
        }

        //读取数据
        public void ReadMessage() {

            while (true) {
                if (startIndex <= 4) return;
                //获取数据长度
                int count = BitConverter.ToInt32(data, 0);//将前面四个字节转换成int
                Console.WriteLine("count :" + count);
                //如果有完整数据 则解析and重新给data赋值
                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;
                }
            }
        }
    }
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值