zoukankan      html  css  js  c++  java
  • 【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

  • 相关阅读:
    Windows JScript 在 游览器 中运行 调试 Shell 文件系统
    autohotkey 符号链接 软连接 symbolink
    软链接 硬链接 测试
    SolidWorks 修改 基准面 标准坐标系
    手机 路径 WebDAV 映射 驱动器
    Win10上手机路径
    explorer 命令行
    单位公司 网络 封锁 屏蔽 深信 AC
    cobbler自动化部署原理篇
    Docker四种网络模式
  • 原文地址:https://www.cnblogs.com/RandyField/p/14121371.html
Copyright © 2011-2022 走看看