【C#-socket】网络通信实例(异步通信、多客户端异步通信、同步通信)

ea722fce39752c995a5ddefbe3e177ad.png

winform客户端、服务器异步通信

winform客户端、服务器异步通信 视频演示

主要代码

#要发送的数据主体
    class PersonPackage
    {
        public bool IsMale { get; set; }
        public ushort Age { get; set; }
        public string Name { get; set; }


        public PersonPackage(bool male, ushort age, string name)
        {
            IsMale = male;
            Age = age;
            Name = name;
        }


        /// <summary>
        /// 反序列化接收到的字节数组 
        /// </summary>
        public PersonPackage(byte[] data)
        {//字节数组data 反序列化为 性别bool ,年龄uint16,名称长度int32。共需要1+2+4=7字节。data后面的是名字   
            IsMale = BitConverter.ToBoolean(data, 0);
            Age = BitConverter.ToUInt16(data, 1);
            int nameLength = BitConverter.ToInt32(data, 3);
            Name = Encoding.ASCII.GetString(data, 7, nameLength);
        }


        /// <summary>
        ///  将此人信息序列化为字节数组。
        /// </summary>
        /// <remarks>
        /// Use the <see cref="Buffer"/> or <see cref="Array"/> class for better performance.
        /// </remarks>
        public byte[] ToByteArray()
        {
            List<byte> byteList = new List<byte>();//字节列表
            byteList.AddRange(BitConverter.GetBytes(IsMale));//bool->字节
            byteList.AddRange(BitConverter.GetBytes(Age));//ushort / uint16->字节
            byteList.AddRange(BitConverter.GetBytes(Name.Length));//int32->字节
            byteList.AddRange(Encoding.ASCII.GetBytes(Name));//string->字节
            return byteList.ToArray();//字节列表->字节数组
        }
    }
#客户端窗体类代码
    public partial class ClientForm : Form
    {
        private Socket clientSocket;
        private byte[] buffer;


        public ClientForm()
        {
            InitializeComponent();
        }
        //显示错误对话框
        private static void ShowErrorDialog(string message)
        {
            MessageBox.Show(message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        //接收回调
        private void ReceiveCallback(IAsyncResult AR)
        {
            try
            {
                int received = clientSocket.EndReceive(AR);


                if (received == 0)
                {
                    return;
                }


                string message = Encoding.ASCII.GetString(buffer);


                Invoke((Action) delegate
                {
                    Text = "Server says: " + message;
                });


                // 再次开始接收数据。
                clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, null);
            }
            //在这种情况下避免 Pokemon 异常处理。
            catch (SocketException ex)
            {
                ShowErrorDialog(ex.Message);
            }
            catch (ObjectDisposedException ex)
            {
                ShowErrorDialog(ex.Message);
            }
        }
        //连接服务器回调
        private void ConnectCallback(IAsyncResult AR)
        {
            try
            {
                clientSocket.EndConnect(AR);//结束挂起的异步连接请求。
                UpdateControlStates(true);//发送按钮可用,连接按钮不可用,label不可见
                buffer = new byte[clientSocket.ReceiveBufferSize];
                clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, null);
            }
            catch (SocketException ex)
            {
                ShowErrorDialog(ex.Message);
            }
            catch (ObjectDisposedException ex)
            {
                ShowErrorDialog(ex.Message);
            }
        }
        //发送字节数组给服务器的回调函数
        private void SendCallback(IAsyncResult AR)
        {
            try
            {
                clientSocket.EndSend(AR);//结束挂起的异步发送
            }
            catch (SocketException ex)
            {
                ShowErrorDialog(ex.Message);
            }
            catch (ObjectDisposedException ex)
            {
                ShowErrorDialog(ex.Message);
            }
        }


        /// <summary>
        /// 启用发送按钮的线程安全方式。
        /// </summary>
        private void UpdateControlStates(bool toggle)//toggle==fasle时
        {
            Invoke((Action)delegate
            {
                buttonSend.Enabled = toggle;//发送按钮不可用
                buttonConnect.Enabled = !toggle;//连接按钮可用
                labelIP.Visible = textBoxAddress.Visible = !toggle;//label可见
            });
        }
        //发送人的信息 字节数据
        private void buttonSend_Click(object sender, EventArgs e)
        {
            try
            {
                // 在发送之前序列化 textBoxes 文本。
                PersonPackage person = new PersonPackage(checkBoxMale.Checked, (ushort)numberBoxAge.Value, textBoxEmployee.Text);
                byte[] buffer = person.ToByteArray();//序列化为字节数组
                clientSocket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, SendCallback, null);//发送字节数组给服务器
            }
            catch (SocketException ex)//主机断开后,再次发送引发异常
            {
                ShowErrorDialog(ex.Message);
                UpdateControlStates(false);//IP可见
            }
            catch (ObjectDisposedException ex)
            {
                ShowErrorDialog(ex.Message);
                UpdateControlStates(false);
            }
        }
        //连接服务器
        private void buttonConnect_Click(object sender, EventArgs e)
        {
            try
            {//使用指定的地址族、套接字类型和协议初始化 System.Net.Sockets.Socket 类的新实例
                clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //连接到指定的主机。
                var endPoint = new IPEndPoint(IPAddress.Parse(textBoxAddress.Text), 3333);
                clientSocket.BeginConnect(endPoint, ConnectCallback, null);//开始一个对远程主机连接的异步请求
            }
            catch (SocketException ex)//发生套接字错误时引发的异常。
            {
                ShowErrorDialog(ex.Message);
            }
            catch (ObjectDisposedException ex)//对已释放的对象执行操作时所引发的异常。
            {
                ShowErrorDialog(ex.Message);
            }
        }
    }
#服务器端窗体类代码
    public partial class ServerForm : Form
    {
        private Socket serverSocket;  //服务端套接字
        private Socket clientSocket; // 我们将只接收一个客户端
        private byte[] buffer;//接收缓存


        public ServerForm()
        {
            InitializeComponent();
            StartServer();
        }
        //显示错误对话框
        private static void ShowErrorDialog(string message)
        {
            MessageBox.Show(message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }


        /// <summary>
        /// Construct server socket and bind socket to all local network interfaces, then listen for connections
        /// with a backlog of 10. Which means there can only be 10 pending connections lined up in the TCP stack
        /// at a time. This does not mean the server can handle only 10 connections. The we begin accepting connections.
        /// Meaning if there are connections queued, then we should process them.
        /// 构造服务器套接字并将套接字绑定到所有本地网络接口,然后侦听积压为 10 的连接。这意味着 TCP 堆栈中一次只能有 10 个未决连接排队。
        /// 这并不意味着服务器只能处理 10 个连接。我们开始接受连接。这意味着如果有连接排队,那么我们应该处理它们。
        /// </summary>
        private void StartServer()
        {
            try
            {
                serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                serverSocket.Bind(new IPEndPoint(IPAddress.Any, 3333));
                serverSocket.Listen(10);//将 System.Net.Sockets.Socket 置于侦听状态。
                serverSocket.BeginAccept(AcceptCallback, null);//开始一个异步操作来接受一个传入的连接尝试。
            }
            catch (SocketException ex)
            {
                ShowErrorDialog(ex.Message);
            }
            catch (ObjectDisposedException ex)
            {
                ShowErrorDialog(ex.Message);
            }
        }


        private void AcceptCallback(IAsyncResult AR)
        {
            try
            {
                clientSocket = serverSocket.EndAccept(AR);// 异步接受传入的连接尝试,并创建新的 System.Net.Sockets.Socket 来处理远程主机通信。
                buffer = new byte[clientSocket.ReceiveBufferSize];


                // 向新连接的客户端发送消息。
                var sendData = Encoding.ASCII.GetBytes("Hello");
                clientSocket.BeginSend(sendData, 0, sendData.Length, SocketFlags.None, SendCallback, null);
                // 监听客户端数据。
                clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, null);
                // 继续倾听客户。
                serverSocket.BeginAccept(AcceptCallback, null);
            }
            catch (SocketException ex)
            {
                ShowErrorDialog(ex.Message);
            }
            catch (ObjectDisposedException ex)
            {
                ShowErrorDialog(ex.Message);
            }
        }
        //发送回调
        private void SendCallback(IAsyncResult AR)
        {
            try
            {
                clientSocket.EndSend(AR);
            }
            catch (SocketException ex)
            {
                ShowErrorDialog(ex.Message);
            }
            catch (ObjectDisposedException ex)
            {
                ShowErrorDialog(ex.Message);
            }
        }
        //接收回调
        private void ReceiveCallback(IAsyncResult AR)
        {
            try
            {
                // Socket exception will raise here when client closes, as this sample does not
                // demonstrate graceful disconnects for the sake of simplicity.
                //当客户端关闭时,此处会引发套接字异常,因为为了简单起见,此示例没有演示优雅的断开连接。
                int received = clientSocket.EndReceive(AR);


                if (received == 0)
                {
                    return;
                }


                // 接收到的数据在 PersonPackage ctor 中反序列化。
                PersonPackage person = new PersonPackage(buffer);
                SubmitPersonToDataGrid(person);


                //再次开始接收数据。
                clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, null);
            }
            // 在这种情况下避免 Pokemon 异常处理。
            catch (SocketException ex)
            {
                ShowErrorDialog(ex.Message);
            }
            catch (ObjectDisposedException ex)
            {
                ShowErrorDialog(ex.Message);
            }
        }


        /// <summary>
        /// 提供一种线程安全的方式将行添加到datagrid。
        /// </summary>
        private void SubmitPersonToDataGrid(PersonPackage person)
        {
            Invoke((Action)delegate
            {
                dataGridView.Rows.Add(person.Name, person.Age, person.IsMale);
            });
        }
    }


190b8398076b588c93ce71c58556f5ba.png

多客户端 异步通信 

多客户端 异步通信   视频演示

主要代码

#多客户端实例-客户端代码
    class Program
    {
        private static readonly Socket ClientSocket = new Socket
            (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //客户端套接字


        private const int PORT = 100;


        static void Main()
        {
            Console.Title = "Client";
            ConnectToServer(); //连接服务器
            RequestLoop();//请求循环
            Exit();//退出
        }


        private static void ConnectToServer()//
        {
            int attempts = 0;//尝试连接次数


            while (!ClientSocket.Connected)//未连接
            {
                try
                {
                    attempts++;//尝试连接次数++
                    Console.WriteLine("Connection attempt " + attempts);
                    // 将 IPAddress.Loopback 更改为远程 IP 以连接到远程主机。
                    ClientSocket.Connect(IPAddress.Loopback, PORT);
                }
                catch (SocketException) 
                {
                    Console.Clear();
                }
            }


            Console.Clear();
            Console.WriteLine("Connected");//连接成功
        }


        private static void RequestLoop()
        {
            Console.WriteLine(@"<Type ""exit"" to properly disconnect client>");


            while (true)
            {
                SendRequest();  //发送请求
                ReceiveResponse();//接收响应
            }
        }


        /// <summary>
        ///关闭套接字并退出程序。
        /// </summary>
        private static void Exit()
        {
            SendString("exit"); // 告诉服务器我们正在退出
            ClientSocket.Shutdown(SocketShutdown.Both);//禁用客户端套接字上的接收与发送
            ClientSocket.Close();//关闭客户端套接字
            Environment.Exit(0);//关闭程序
        }


        private static void SendRequest()
        {
            Console.Write("Send a request: ");
            string request = Console.ReadLine();//输入要发送的请求内容
            SendString(request);


            if (request.ToLower() == "exit")//如果要发送给服务器的字符串是 exit 命令
            {
                Exit();//退出
            }
        }


        /// <summary>
        /// 使用 ASCII 编码向服务器发送一个字符串。
        /// </summary>
        private static void SendString(string text)
        {
            byte[] buffer = Encoding.ASCII.GetBytes(text);
            ClientSocket.Send(buffer, 0, buffer.Length, SocketFlags.None);
        }


        private static void ReceiveResponse() 
        {
            var buffer = new byte[2048];//接收缓存
            int received = ClientSocket.Receive(buffer, SocketFlags.None);//接收服务器响应
            if (received == 0) return;
            var data = new byte[received];
            Array.Copy(buffer, data, received);//去除空字符
            string text = Encoding.ASCII.GetString(data);
            Console.WriteLine(text);//显示接收的响应文本
        }
    }
#多客户端实例-服务器端代码
    class Program
    {
        private static readonly Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        private static readonly List<Socket> clientSockets = new List<Socket>();  //介入的客户端套接字列表
        private const int BUFFER_SIZE = 2048;
        private const int PORT = 100;
        private static readonly byte[] buffer = new byte[BUFFER_SIZE];


        static void Main()
        {
            Console.Title = "Server";
            SetupServer();//设置服务器套接字 开始侦听
            Console.ReadLine(); //当我们按下回车关闭一切
            CloseAllSockets();
        }
        
        private static void SetupServer()
        {
            Console.WriteLine("Setting up server...");
            serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT));
            serverSocket.Listen(0);
            serverSocket.BeginAccept(AcceptCallback, null);//开启异步操作来接受一个传入的连接尝试
            Console.WriteLine("Server setup complete");
        }


        /// <summary>
        /// Close all connected client (we do not need to shutdown the server socket as its connections
        /// are already closed with the clients).
        /// 关闭所有连接的客户端(我们不需要关闭服务器套接字,因为它的连接已经与客户端 关闭)。
        /// </summary>
        private static void CloseAllSockets()
        {
            foreach (Socket socket in clientSockets)
            {
                socket.Shutdown(SocketShutdown.Both);
                socket.Close();
            }


            serverSocket.Close();
        }


        private static void AcceptCallback(IAsyncResult AR)
        {
            Socket socket;


            try
            {
                socket = serverSocket.EndAccept(AR);
            }
            catch (ObjectDisposedException) //我似乎无法避免这种情况(在正确关闭套接字时退出)
            {
                return;
            }


           clientSockets.Add(socket);
           socket.BeginReceive(buffer, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCallback, socket);//开始异步接收数据
           Console.WriteLine("Client connected, waiting for request...");
           serverSocket.BeginAccept(AcceptCallback, null);
        }


        private static void ReceiveCallback(IAsyncResult AR)
        {
            Socket current = (Socket)AR.AsyncState;
            int received;


            try
            {
                received = current.EndReceive(AR);//结束挂起的异步读取
            }
            catch (SocketException)
            {
                Console.WriteLine("客户端强行断开");
                current.Close(); //关闭该套接字
                clientSockets.Remove(current);//从套接字列表移除
                return;
            }


            byte[] recBuf = new byte[received];
            Array.Copy(buffer, recBuf, received);//去除空格
            string text = Encoding.ASCII.GetString(recBuf);
            Console.WriteLine("Received Text: " + text);


            if (text.ToLower() == "get time") // 客户端请求时间
            {
                Console.WriteLine("Text is a get time request");
                byte[] data = Encoding.ASCII.GetBytes(DateTime.Now.ToLongTimeString());
                current.Send(data);//发送时间给客户端
                Console.WriteLine("Time sent to client");
            }
            else if (text.ToLower() == "exit") // 客户端想要优雅地退出 
            {
                // 关闭前总是关机
                current.Shutdown(SocketShutdown.Both);//禁用current套接字的发送和接收
                current.Close();//关闭current套接字
                clientSockets.Remove(current);//列表中移除套接字
                Console.WriteLine("Client disconnected");
                return;
            }
            else
            {
                Console.WriteLine("Text is an invalid request");
                byte[] data = Encoding.ASCII.GetBytes("Invalid request");//无效请求
                current.Send(data);
                Console.WriteLine("Warning Sent");
            }


            current.BeginReceive(buffer, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCallback, current);//继续从客户端套接字异步接收数据
        }
    }


同步socket通信

#主程序
    class Program
    {
        public const int Port = 3333;


        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("No arguments provided.");
                Console.ReadLine();
                return;
            }


            // 参数 "/c" 将以客户端模式启动该程序。
            // 参数“/s”将以服务器模式启动该程序。
            switch (args[0].ToLower())
            {
                case "/c": 
                    Client client=  new Client();
                    client.StartSendLoop();
                    break;


                case "/s":
                    // 也以客户端模式启动此应用程序。
                    string fileName = System.Reflection.Assembly.GetExecutingAssembly().Location;
                    System.Diagnostics.Process.Start(fileName, "/c");


                    Server server = new Server();
                    server.Start();
                    break;
            }
        }
    }
/// <summary>
    /// 表示客户端应用程序。
    /// </summary>
    class Client
    {
        private Socket clientSocket; //客户端套接字


        public void StartSendLoop()
        {
            try
            {
                clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                // 将 IPAddress.Loopback 更改为远程 IP 以连接到远程主机。
                clientSocket.Connect(IPAddress.Loopback, Program.Port);


                while (true)
                {
                    Console.Write("Enter text to send: ");
                    string input = Console.ReadLine();


                    if (input == "exit")
                    {
                        clientSocket.Close(); //关闭客户端套接字
                        Environment.Exit(0); //退出程序
                    }


                    clientSocket.Send(Encoding.ASCII.GetBytes(input));
                    Console.Clear();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.ReadLine();
            }
        }
    }
#服务器类
    class Server
    {
        private Socket serverSocket, clientSocket;
        private byte[] buffer;


        public void Start()
        {
            // Create server socket and listen on any local interface.
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(new IPEndPoint(IPAddress.Any, 3333));
            serverSocket.Listen(0);//服务端套接字处于监听状态
            // 后跟...表示工作中
            Console.WriteLine("Listening for connections on port " + Program.Port + "...");
            clientSocket = serverSocket.Accept(); //阻塞,等待客户端连接
            Console.WriteLine("Client Connected");
            Console.WriteLine("Waiting for data...");
            buffer = new byte[clientSocket.ReceiveBufferSize];
            ReceiveData();
            Console.WriteLine("Server loop ended. Press enter to exit.");
            Console.ReadLine();
            serverSocket.Close();
        }


        private void ReceiveData()
        {
            while (clientSocket.Connected)
            {
                int received = clientSocket.Receive(buffer);//接收数据写入缓存


                if (received == 0) //假设客户端已断开连接。
                {
                    Console.WriteLine("Client Disconnected");
                    break;
                }


                // 缩小缓冲区,这样我们就不会在文本中得到空字符。
                Array.Resize(ref buffer, received);
                string receivedMsg = Encoding.ASCII.GetString(buffer);
                //重置缓冲区。
                Array.Resize(ref buffer, clientSocket.ReceiveBufferSize);
                Console.WriteLine("Message received: " + receivedMsg);
            }


            //假设客户端已断开连接并再次开始侦听连接。
            Console.WriteLine("\nListening again...");
            clientSocket = serverSocket.Accept();
            Console.WriteLine("Client Connected");
            Console.WriteLine("Waiting for data...");
            ReceiveData();
        }
    }

结语: 

        以上实例仅用于对网络编程概念的理解。工程上一般会使用第三方成熟的通信库。有开发通信库能力的除外。   在专业领域同样也存在这样一个问题,在学校里学了很多理论也做出过很多demo,用到工程上会发现各种问题.做工程和做学术研究最大的区别在于工程上使用的技术要求成熟、稳定、可靠。学术研究的demo可能成功了一次,失败了99次,就可以算作好的研究成果了,这些都是在非常理想的情况下做出的成果。但是在工程上你就是成功99次,有一次失败都不行,工程上有很多现实问题要面对。所以学术上的研究成果要进行工程化是有很长的路要走的。  

The End

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值