• 【Golang】快速复习指南QuickReview(九)——socket


    Socket网路编程对于B/S项目来说,几乎不会涉及;但是如果涉及游戏服务器开发,或者上位机服务器开发,自定义通信协议,Socket网络编程就变得常见了。

    Socket编程

    1.C#的socket

    • 1.创建Socket对象,指定传输层协议TCP或者UDP - Socket
    //创建一个负责监听IP地址跟端口号的Socket
    serverSocket = new Socket(AddressFamily.InterNetwork,
                              SocketType.Stream,
                              ProtocolType.Tcp);
    
    • 2.绑定端口 - Bind()
    serverSocket.Bind(new IPEndPoint(IPAddress.Any, 9999));
    
    • 3.监听
    serverSocket.Listen();
    
    • 4.阻塞,等待客户端连接 - Accept
    Socket client = serverSocket.Accept();
    
    • 5.客户端连接 - Connect() 与发送信息 - Send()
    clientSocket.Connect(ip, port);
    
    • 6.服务端解除阻塞,接收消息 - Receive()
    byte[] msg = new byte[1024 * 1024 * 2];
    int msgLen = client.Receive(msg);
    

    后面便是周而复始的,接收、发送的戏份。

    在整个过程中,有以下步骤需要多线程处理:

    • Accept():由于服务端Accept()操作会阻塞线程,所以需要多线程,使其每接收一个客户端连接,就开一个线程进行独立处理。
    • Receive():由于Receive()操作也会阻塞线程,所以也需要开启线程,才能进行与客户端或服务器的交互操作。

    1.1 服务端

    class Program
    {
        static void Main(string[] args)
        {
            Server server = new Server();
            server.Start();
            Console.ReadKey();
        }
    }
    
    public class Server
    {
        private Socket serverSocket;
        //泛型集合 或者 字典
        private List<Socket> clientList;
        Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
    
        public Server()
        {
            //创建一个负责监听IP地址跟端口号的Socket
            serverSocket = new Socket(AddressFamily.InterNetwork,
                                      SocketType.Stream,
                                      ProtocolType.Tcp);
    
            clientList = new List<Socket>();
        }
    
        public void Start()
        {
            //监听 telnet 192.168.11.78 9999
            serverSocket.Bind(new IPEndPoint(IPAddress.Any, 9999));
    
            serverSocket.Listen(10);
            Console.WriteLine("Server Start...");
    
            Thread threadAccept = new Thread(Accept);
            threadAccept.IsBackground = true;
            threadAccept.Start();
        }
    
        /// <summary>
        /// serverSocket可以作为参数  object
        /// </summary>
        private void Accept()
        {
            //等待客户端的连接,会挂起当前线程(如果是winfrom wpf 主线程里使用这个方法会卡死) 接收客户端请求,并为之创建通信的socket---负责通信
            Socket client = serverSocket.Accept();//等待连接 所以要开启线程
    
            //拿到远程客户端的IP地址和端口号
            IPEndPoint clientDetail = client.RemoteEndPoint as IPEndPoint;
            Console.WriteLine($"ip{clientDetail.Address} {clientDetail.Port} connecting");
    
            //存储客户端List
            clientList.Add(client);
            dicSocket.Add(clientDetail.ToString(), client);
    
            Thread receiveAccept = new Thread(Receive);
            receiveAccept.IsBackground = true;
            receiveAccept.Start(client);
    
            //按顺序执行,尾递归便于理解  假死,一个客户端
            Accept();//如果有一个连接了  就会依次执行 接收好客户端后的处理,所以要加上一个尾递归
    
            ////或者使用循环
            //while (true)
            //{
            //    //上面所有的代码,排除尾递归
            //}
        }
    
    
        public void Receive(object obj)
        {
            Socket client = obj as Socket;
            IPEndPoint clientDetail = client.RemoteEndPoint as IPEndPoint;
    
            try
            {
                byte[] msg = new byte[1024 * 1024 * 2];
    
                //实际接收到到的有效字节数 远程客户端一关  就接收不到 msgLen=0
                int msgLen = client.Receive(msg);
    
                Console.WriteLine($"ip{clientDetail.Address} {clientDetail.Port} say:{Encoding.UTF8.GetString(msg, 0, msgLen)}");
    
                if (msgLen != 0)
                {
                    //client.Send(Encoding.UTF8.GetBytes("楼上说的对"));
                    //改造后
                    Broadcast(client, $"服务器时间{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")},ip{clientDetail.Address} {clientDetail.Port} say:{Encoding.UTF8.GetString(msg, 0, msgLen)}");
    
                    //尾递归 不停的接收客户端消息 同上可使用循环
                    Receive(client);
                }
            }
            catch
            {
                Console.WriteLine($"ip{clientDetail.Address} {clientDetail.Port} 断开");
                clientList.Remove(client);
            }
        }
    
        private void Broadcast(Socket socketOther, string msg)
        {
            //遍历客户端
            foreach (var client in clientList)
            {
                if (client != socketOther)
                {
                    client.Send(Encoding.UTF8.GetBytes(msg));
                }
            }
        }
    }
    

    1.2 客户端

    class Program
    {
        static void Main(string[] args)
        {
            Client client = new Client();
            client.Connect("127.0.0.1", 9999);
    
            Console.WriteLine("请输入聊天内容,输入quit退出:");
            string msg = Console.ReadLine();
    
            while (msg != "quit")
            {
                client.Send(msg);
                msg = Console.ReadLine();
            }
            Console.ReadKey();
        }
    }
    
    public class Client
    {
        private Socket clientSocket;
    
        public Client()
        {
            //创建负责通信的socket
            this.clientSocket = new Socket(AddressFamily.InterNetwork,
                                           SocketType.Stream,
                                           ProtocolType.Tcp);
        }
    
    
        public void Connect(string ip, int port)
        {
            clientSocket.Connect(ip, port);
            Console.WriteLine("Client connect success...");
    
            Thread receiveAccept = new Thread(Receive);
            receiveAccept.IsBackground = true;
            receiveAccept.Start();
            //Receive();
        }
    
    
        private void Receive()
        {
            try
            {
                byte[] msg = new byte[1024*1024*2];
                int msgLen = clientSocket.Receive(msg);
                Console.WriteLine($"Server say:{Encoding.UTF8.GetString(msg, 0, msgLen)} ");
            }
            catch (Exception)
            {
                Console.WriteLine("服务器断开");
            }
            Receive();
        }
    
        public void Send(string msg)
        {
            clientSocket.Send(Encoding.UTF8.GetBytes(msg));
        }
    }
    

    2.Golang的socket

    2.1 服务端

    Golang创建服务端省略了些步骤,直接从监听Listen开始,博主开始把goroutine作线程类比C#的写法,也是没问题的。后面参考了包中的示例

    ln, err := net.Listen("tcp", ":8080")
    if err != nil {
        // handle error
    }
    for {
        conn, err := ln.Accept()
        if err != nil {
            // handle error
        }
        go handleConnection(conn)
    }
    

    利用死循环、goroutineAccept()返回多个值。可以有效简化代码。

    package main
    
    import (
    	"bufio"
    	// "encoding/binary"
    	// "encoding/json"
    	"fmt"
    	"net"
    )
    
    func handleConnection(conn net.Conn) {
    	defer conn.Close() // 关闭连接
    	for {
    		reader := bufio.NewReader(conn)
    		var buf [128]byte
    		n, err := reader.Read(buf[:]) // 读取数据
    		if err != nil {
    			fmt.Println("read from client failed, err:", err)
    			break
    		}
    		recvStr := string(buf[:n])
    		fmt.Println("收到client端发来的数据:", recvStr)
    		conn.Write([]byte(recvStr)) // 发送数据
    	}
    }
    
    func main() {
    	
    	listen, err := net.Listen("tcp", "127.0.0.1:9999")
    	if err != nil {
    		// handle error
    		fmt.Println("listen failed, err:", err)
    		return
    	}
    	for {
    		conn, err := listen.Accept() // 建立连接
    		if err != nil {
    			// handle error
    			fmt.Println("accept failed, err:", err)
    			continue
    		}
    		go handleConnection(conn)
    	}
    }
    
    

    22 客户端

    客户端方面有一点不同,net包里有单独方法Dial(),大概翻译了一下叫: 拨号

    package main
    
    import (
    	"bufio"
    	// "encoding/binary"
    	// "encoding/json"
    	"fmt"
    	"net"
    	"os"
    	"strings"
    )
    
    func main() {
    	conn, err := net.Dial("tcp", "127.0.0.1:9999")
    	if err != nil {
    		fmt.Println("err :", err)
    		return
    	}
    	defer conn.Close() // 关闭连接
    	inputReader := bufio.NewReader(os.Stdin)
    	fmt.Println("请输入内容,按回车发送,按q键退出...")
    	for {
    		//读取输入流,直到换行符出现为止
    		input, _ := inputReader.ReadString('
    ') // 读取用户输入
    
    		//截取回车与换行
    		inputInfo := strings.Trim(input, "
    ")
    
    		if strings.ToUpper(inputInfo) == "Q" { // 如果输入q就退出
    			return
    		}
    		_, err = conn.Write([]byte(inputInfo)) // 发送数据
    		if err != nil {
                fmt.Println(err)
    			return
    		}
    		buf := [512]byte{}
    		n, err := conn.Read(buf[:])
    		if err != nil {
    			fmt.Println("recv failed, err:", err)
    			return
    		}
    		fmt.Println(string(buf[:n]))
    	}
    }
    

    再次强调:这个系列并不是教程,如果想系统的学习,博主可推荐学习资源。


    作者:Garfield

    同步更新至个人博客:http://www.randyfield.cn/

    本文版权归作者所有,未经许可禁止转载,否则保留追究法律责任的权利,若有需要请联系287572291@qq.com

  • 相关阅读:
    网页快捷键
    2016年5月3日15:55:23笔记
    【编程的乐趣-用python解算法谜题系列】谜题一 保持一致
    重温离散系列②之良序原理
    重温离散系列①之什么是证明
    浅谈栈和队列
    [leetcode]16-最接近的三数之和
    [leetcode] 4-寻找两个有序数组的中位数
    英语句子的基本结构【转】
    [leetcode] 11-盛最多水的容器
  • 原文地址:https://www.cnblogs.com/RandyField/p/14121371.html
Copyright © 2020-2023  润新知