go語言學(xué)習(xí)日記(8)

go語言的網(wǎng)絡(luò)編程

TCP的cs結(jié)構(gòu)

當(dāng)涉及到 TCP(傳輸控制協(xié)議)的客戶端-服務(wù)器(CS)架構(gòu)時碳锈,它通常包括兩個主要角色:客戶端和服務(wù)器顽冶。這種架構(gòu)允許客戶端與服務(wù)器進(jìn)行通信,服務(wù)器監(jiān)聽并響應(yīng)來自客戶端的請求售碳。

服務(wù)器端

  1. 監(jiān)聽: 服務(wù)器通常在特定端口上監(jiān)聽傳入的連接請求强重。
  2. 接受連接: 當(dāng)客戶端嘗試連接到服務(wù)器的指定端口時,服務(wù)器接受該連接請求并建立連接贸人。
  3. 處理請求: 一旦連接建立间景,服務(wù)器會處理客戶端發(fā)送的請求。這可能涉及處理數(shù)據(jù)艺智、執(zhí)行特定任務(wù)或提供服務(wù)倘要。
  4. 響應(yīng): 服務(wù)器處理請求后,會向客戶端發(fā)送響應(yīng)十拣。響應(yīng)可能是數(shù)據(jù)封拧、狀態(tài)信息或執(zhí)行結(jié)果。

客戶端

  1. 建立連接: 客戶端請求與服務(wù)器的特定端口建立連接夭问。
  2. 發(fā)送請求: 客戶端向服務(wù)器發(fā)送請求泽西,請求可能包含所需的數(shù)據(jù)、服務(wù)或特定操作甲喝。
  3. 等待響應(yīng): 客戶端等待服務(wù)器的響應(yīng)尝苇。
  4. 處理響應(yīng): 一旦收到服務(wù)器的響應(yīng)铛只,客戶端會對其進(jìn)行處理,可能是解析數(shù)據(jù)糠溜、采取相應(yīng)操作或顯示結(jié)果淳玩。
//服務(wù)器端代碼
func dealconnect(conn net.Conn) {
    defer conn.Close()

    ipaddr := conn.RemoteAddr()
    fmt.Println("連接地址為:", ipaddr)

    buf := make([]byte, 1024) //建立緩沖池,等待客戶端輸入

    for {
        n, err := conn.Read(buf)
        if err != nil {
            fmt.Println("writer err = ", err)
            return
        }

        result := string(buf[:n])

        fmt.Println(ipaddr, "發(fā)送數(shù)據(jù)為:", result)
        if result == "exit" {
            fmt.Println(ipaddr, "連接斷開")
            return
        }
        conn.Write([]byte(strings.ToUpper(string(result)))) //將客戶端發(fā)來的數(shù)據(jù)大寫后送回客戶端
    }
}

func main() {
    listener, err := net.Listen("tcp", "127.0.0.1:8000") //建立監(jiān)聽

    if err != nil {
        fmt.Println("listener err = ", err)
        return
    }

    defer listener.Close() //延遲關(guān)閉

    for {
        conn, err := listener.Accept() //處理客戶端發(fā)來的鏈接
        if err != nil {
            fmt.Println("connect err = ", err)
            continue
        }

        go dealconnect(conn) //并發(fā)處理客戶端發(fā)來的請求
    }
}
//客戶端代碼
package main

import (
    "fmt"
    "net"
)

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:8000") //往服務(wù)器端發(fā)送請求鏈接
    if err != nil {
        fmt.Println("connect err = ", err)
        return

    }
    defer conn.Close()

    buf := make([]byte, 1024)
    for {
        fmt.Println("請傳輸內(nèi)容:")
        fmt.Scan(&buf)
        fmt.Println("輸入內(nèi)容為:", buf)
        conn.Write(buf)

        n, err1 := conn.Read(buf)
        if err1 != nil {
            fmt.Println("Client Read err = ", err1)
            return
        }
        result := buf[:n]
        fmt.Printf("接收到數(shù)據(jù)[%d]:%s\n", n, string(result))
    }

}

簡易的網(wǎng)絡(luò)聊天室

網(wǎng)絡(luò)主要流程
package main

import (
    "fmt"
    "net"
    "time"
)

type Client struct {
    C    chan string // 用于發(fā)送數(shù)據(jù)的管道
    Name string      // 用戶名
    Addr string      // 網(wǎng)絡(luò)地址
}

// 保存在線用戶  cliAttr=>Client
var onlineMap map[string]Client;

var message = make(chan string);

// 只要有消息來了非竿,遍歷map蜕着,給map每個成員都發(fā)送此消息
func Manager() {
    // 給map分配空間
    onlineMap = make(map[string]Client);
    for {
        msg := <-message; // 沒有消息時,這里會阻塞

        // 遍歷map红柱,給map每個成員都發(fā)送此消息
        for _, cli := range onlineMap {
            cli.C <- msg
        }
    }
}

// 向客戶端發(fā)送消息
func WriteMsgToClient(cli Client, conn net.Conn) {
    for msg := range cli.C { // 給當(dāng)前客戶端發(fā)送信息
        _, _ = conn.Write([]byte(msg));
    }
}

// 獲取要發(fā)送的消息
func MakeMsg(cli Client, text string) (buf string) {
    return "【" + cli.Addr + "】" + cli.Name + ":" + text + "\n";
}

func HandleConn(conn net.Conn) { // 處理用戶連接(用戶上線了的處理)
    // 獲取客戶端的網(wǎng)絡(luò)地址
    cliAddr := conn.RemoteAddr().String();

    // 創(chuàng)建一個結(jié)構(gòu)體
    cli := Client{make(chan string), cliAddr, cliAddr};

    // 把結(jié)構(gòu)體添加到map
    onlineMap[cliAddr] = cli;

    // 新開一個協(xié)程承匣,專門給客戶端發(fā)送信息
    go WriteMsgToClient(cli, conn);

    // 廣播某個人在線,所有客戶端都能收到消息
    message <- MakeMsg(cli, "已登錄");

    // 提示我是誰锤悄,這個只能自己收到
    cli.C <- MakeMsg(cli, "我在這里");

    //
    isQuit := make(chan bool);  // 對方是否主動退出
    hasData := make(chan bool); // 對方是否有數(shù)據(jù)發(fā)送

    // 接收用戶發(fā)送過來的數(shù)據(jù)
    go func() {
        buf := make([]byte, 2048);
        for {
            n, readErr := conn.Read(buf);
            //if (readErr != nil) {
            //  fmt.Println("conn.Read error =", readErr);
            //}

            if (n == 0) { // 對方斷開or出問題
                isQuit <- true;
                fmt.Println("conn.Read error =", readErr);
                return;
            }

            msg := string(buf[:n-1]); // nc 多一個換行

            if (len(msg) == 3 && msg == "who") { // 當(dāng)收到“who”指令時韧骗,改為發(fā)送用戶列表
                // 遍歷map,給當(dāng)前用戶發(fā)送所有成員
                _, _ = conn.Write([]byte("User List:\n"));
                for _, tmp := range onlineMap {
                    msg = tmp.Addr + ":" + tmp.Name + "\n";
                    _, _ = conn.Write([]byte(msg));
                }
            } else if (len(msg) >= 8 && msg[:7] == "rename ") {
                // 更改用戶名
                oldName := cli.Name;
                newName := msg[7:];
                cli.Name = newName;
                onlineMap[cliAddr] = cli;
                conn.Write([]byte("Client【" + cliAddr + "】的用戶名更改" + oldName + "=>" + newName));
            } else {
                // 轉(zhuǎn)發(fā)此內(nèi)容
                fmt.Printf(MakeMsg(cli, msg));
                message <- MakeMsg(cli, msg);
            }

            hasData <- true; // 代表有數(shù)據(jù)
        }
    }()

    // 做個死循環(huán)零聚,不要讓方法結(jié)束
    for {
        // 通過select來檢測channel的流動
        select {
        case <-isQuit:
            delete(onlineMap, cliAddr);     // 將當(dāng)前用戶從map中移除
            message <- MakeMsg(cli, "已退出"); // 廣播下線
            return;
            //
        case <-hasData:
            break;
        case <-time.After(time.Second * 60):     // 60s后
            delete(onlineMap, cliAddr);          // 將當(dāng)前用戶從map中移除
            message <- MakeMsg(cli, "time out"); // 廣播下線
            break;
        }

    }
}

func main() {
    listener, listenErr := net.Listen("tcp", ":8010");
    if (listenErr != nil) {
        fmt.Println("net.Listen error =", listenErr);
        return;
    }

    defer listener.Close();

    // 該協(xié)程用于轉(zhuǎn)發(fā)消息袍暴,只要有消息來了,遍歷map隶症,給map每個成員都發(fā)送此消息
    go Manager();

    // 主協(xié)程政模,循環(huán)阻塞等待用戶連接
    for {
        conn, connErr := listener.Accept();
        if (connErr != nil) {
            fmt.Println("listener.Accept =", connErr);
            continue;
        }

        go HandleConn(conn); // 處理用戶連接
    }

}

小tips

WriteMsgToClient 函數(shù)中的conn變量是客戶端連接的表示。當(dāng)新消息到達(dá)時蚂会,服務(wù)器會通過這個連接將消息發(fā)送回客戶端淋样。這里的 cli 是一個客戶端的數(shù)據(jù)結(jié)構(gòu),它包含有關(guān)客戶端的其他信息胁住,并充當(dāng)一個接收消息并將消息發(fā)送到客戶端連接的中介趁猴。
想象一下你在一個餐廳里。服務(wù)員(cli)正等待你的點(diǎn)餐措嵌,這個服務(wù)員知道你的桌號(網(wǎng)絡(luò)地址)躲叼。每當(dāng)你準(zhǔn)備好下單時芦缰,服務(wù)員會將你的點(diǎn)餐信息(msg)傳達(dá)給餐廳后廚(conn)企巢。在這種情況下,服務(wù)員就像一個中介让蕾,他負(fù)責(zé)獲取你的點(diǎn)餐信息并將其傳送到廚房(網(wǎng)絡(luò)連接)中浪规。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市探孝,隨后出現(xiàn)的幾起案子笋婿,更是在濱河造成了極大的恐慌,老刑警劉巖顿颅,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缸濒,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)庇配,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門斩跌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捞慌,你說我怎么就攤上這事耀鸦。” “怎么了啸澡?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵袖订,是天一觀的道長。 經(jīng)常有香客問我嗅虏,道長洛姑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任皮服,我火速辦了婚禮吏口,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冰更。我一直安慰自己产徊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布蜀细。 她就那樣靜靜地躺著舟铜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奠衔。 梳的紋絲不亂的頭發(fā)上谆刨,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機(jī)與錄音归斤,去河邊找鬼痊夭。 笑死,一個胖子當(dāng)著我的面吹牛脏里,可吹牛的內(nèi)容都是我干的她我。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼迫横,長吁一口氣:“原來是場噩夢啊……” “哼番舆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起矾踱,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤恨狈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后呛讲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體禾怠,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡返奉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吗氏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衡瓶。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖牲证,靈堂內(nèi)的尸體忽然破棺而出哮针,到底是詐尸還是另有隱情,我是刑警寧澤坦袍,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布十厢,位于F島的核電站,受9級特大地震影響捂齐,放射性物質(zhì)發(fā)生泄漏蛮放。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一奠宜、第九天 我趴在偏房一處隱蔽的房頂上張望包颁。 院中可真熱鬧,春花似錦压真、人聲如沸娩嚼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岳悟。三九已至,卻和暖如春泼差,著一層夾襖步出監(jiān)牢的瞬間贵少,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工堆缘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留滔灶,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓吼肥,卻偏偏與公主長得像录平,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子潜沦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容