天天看點

【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)
}
           

利用死循環、

goroutine

Accept()

傳回多個值。可以有效簡化代碼。

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('\n') // 讀取使用者輸入

		//截取回車與換行
		inputInfo := strings.Trim(input, "\r\n")

		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/

本文版權歸作者所有,未經許可禁止轉載,否則保留追究法律責任的權利,若有需要請聯系[email protected]

微信公衆号

掃描下方二維碼關注個人微信公衆号,實時擷取更多幹貨

【Golang】快速複習指南QuickReview(九)——socket

同步更新至:http://www.randyfield.cn/

出處:http://www.cnblogs.com/RandyField/

本文版權歸作者和部落格園共有,未經許可禁止轉載,否則保留追究法律責任的權利,若有需要請聯系[email protected].