建站費(fèi)用報(bào)價(jià)單崇左網(wǎng)站建設(shè)
本節(jié)是新知識,偏應(yīng)用,需要反復(fù)練習(xí)才能掌握。
目錄
- 1.C/S通信示意圖
- 2.服務(wù)端通信
- 3.客戶端通信
- 4.通信測試
- 5.進(jìn)階練習(xí):客戶端之間通信
1.C/S通信示意圖
客戶端與服務(wù)端通信的模式也稱作C/S模式,流程圖如下
其中P是協(xié)程調(diào)度器??梢钥吹?#xff0c;客戶端都是通過一個(gè)共同的端口和服務(wù)端通信的,且服務(wù)端開了兩個(gè)協(xié)程并發(fā)處理兩個(gè)客戶端。注意,協(xié)程和P之間,協(xié)程和端口之間,端口和客戶端之間的鏈接都是雙向的。
2.服務(wù)端通信
服務(wù)端功能要求如下:
(1)編寫一個(gè)服務(wù)器端程序,在8888端口監(jiān)聽
fmt.Println("服務(wù)器開始監(jiān)聽....")
//net.Listen("tcp", "0.0.0.0:8888")
//1. tcp 表示使用網(wǎng)絡(luò)協(xié)議是tcp
//2. 0.0.0.0:8888 表示在本地監(jiān)聽 8888端口
listen, err := net.Listen("tcp", "0.0.0.0:8888")
(2)可以和多個(gè)客戶端創(chuàng)建鏈接
//循環(huán)等待客戶端來鏈接我
for {//等待客戶端鏈接fmt.Println("等待客戶端來鏈接....")conn, err := listen.Accept()//這里準(zhǔn)備其一個(gè)協(xié)程,為客戶端服務(wù)go process(conn)
}
(3)鏈接成功后,客戶端可以發(fā)送數(shù)據(jù),服務(wù)器端接受數(shù)據(jù),并顯示在終端上
完整代碼如下:
package main
import ("fmt""net" //做網(wǎng)絡(luò)socket開發(fā)時(shí),net包含有我們需要所有的方法和函數(shù)_"io"
)func process(conn net.Conn) {//這里我們循環(huán)的接收客戶端發(fā)送的數(shù)據(jù)defer conn.Close() //關(guān)閉connfor {//創(chuàng)建一個(gè)新的切片buf := make([]byte, 1024)//conn.Read(buf)//1. 等待客戶端通過conn發(fā)送信息//2. 如果客戶端沒有wrtie[發(fā)送],那么協(xié)程就阻塞在這里//fmt.Printf("服務(wù)器在等待客戶端%s 發(fā)送信息\n", conn.RemoteAddr().String())n , err := conn.Read(buf) //從conn讀取if err != nil {fmt.Printf("客戶端退出 err=%v", err)return //!!!}//3. 顯示客戶端發(fā)送的內(nèi)容到服務(wù)器的終端fmt.Print(string(buf[:n])) }}func main() {fmt.Println("服務(wù)器開始監(jiān)聽....")//net.Listen("tcp", "0.0.0.0:8888")//1. tcp 表示使用網(wǎng)絡(luò)協(xié)議是tcp//2. 0.0.0.0:8888 表示在本地監(jiān)聽 8888端口listen, err := net.Listen("tcp", "0.0.0.0:8888")if err != nil {fmt.Println("listen err=", err)return }defer listen.Close() //延時(shí)關(guān)閉listen//循環(huán)等待客戶端來鏈接我for {//等待客戶端鏈接fmt.Println("等待客戶端來鏈接....")conn, err := listen.Accept()if err != nil {fmt.Println("Accept() err=", err)} else {fmt.Printf("Accept() suc con=%v 客戶端ip=%v\n", conn, conn.RemoteAddr().String())}//這里準(zhǔn)備其一個(gè)協(xié)程,為客戶端服務(wù)go process(conn)}//fmt.Printf("listen suc=%v\n", listen)
}
流程總結(jié):
1.使用net.Listen()
初始化監(jiān)聽端口,把初始信息存在變量listen中
2.循環(huán)使用listen.Accept()
,接收監(jiān)聽到的基本信息,存在變量conn中
3.每次循環(huán)用conn.Read(buf)
讀取客戶端發(fā)送的內(nèi)容,將這些操作封裝到一個(gè)協(xié)程中,并發(fā)執(zhí)行
3.客戶端通信
客戶端功能:
(1)編寫一個(gè)客戶端程序,能鏈接到服務(wù)器端的8888端口
conn, err := net.Dial("tcp", "localhost:8888")
(2)客戶端可以發(fā)送單行數(shù)據(jù),然后就退出
reader := bufio.NewReader(os.Stdin) //os.Stdin 代表標(biāo)準(zhǔn)輸入[終端]
(3)能通過終端輸入數(shù)據(jù)(輸入一行發(fā)送一行),并發(fā)送給服務(wù)器端
line, err := reader.ReadString('\n')
if err != nil {fmt.Println("readString err=", err)
}
其中ReadString(‘\n’)表示以換行符為截止符號,讀取包括換行符在內(nèi)的之前的字符,所以之后還需要去除line中的換行符。
(4)在終端輸入exit,表示退出程序
line = strings.Trim(line, " \r\n")if line == "exit" {fmt.Println("客戶端退出..")break}
完整代碼如下:
package main
import ("fmt""net""bufio""os""strings"
)func main() {conn, err := net.Dial("tcp", "localhost:8888")if err != nil {fmt.Println("client dial err=", err)return }//功能一:客戶端可以發(fā)送單行數(shù)據(jù),然后就退出reader := bufio.NewReader(os.Stdin) //os.Stdin 代表標(biāo)準(zhǔn)輸入[終端]for {//從終端讀取一行用戶輸入,并準(zhǔn)備發(fā)送給服務(wù)器line, err := reader.ReadString('\n')if err != nil {fmt.Println("readString err=", err)}//如果用戶輸入的是 exit就退出line = strings.Trim(line, " \r\n")if line == "exit" {fmt.Println("客戶端退出..")break}//再將line 發(fā)送給 服務(wù)器_, err = conn.Write([]byte(line + "\n"))if err != nil {fmt.Println("conn.Write err=", err) }}
}
注:localhost是本地ip地址,默認(rèn)ipv6。
流程總結(jié):
1.使用net.Dial()
建立與服務(wù)端的鏈接,把初始信息存在變量conn中
2.循環(huán)使用bufio.NewReader(os.Stdin)
創(chuàng)建緩沖區(qū),讀入用戶輸入的信息并存在reader中
3.使用reader.ReadString('\n')
提取每行字符
4.判斷輸入是否為exit,若是則退出循環(huán)
4.若輸入步是exit,則用conn.Write()
將每行內(nèi)容發(fā)送到服務(wù)端
4.通信測試
先啟動(dòng)服務(wù)端,內(nèi)容如下:
服務(wù)器開始監(jiān)聽....
等待客戶端來鏈接....
注意,如果出現(xiàn)類似下圖的提示框請點(diǎn)擊允許!
再打開當(dāng)前目錄的命令行,啟動(dòng)客戶端,命令行會顯示如下語句
Accept() suc con=&{{0xc00008a508}} 客戶端ip=[::1]:端口號
注意:這個(gè)端口號不是8888,而是客戶端用來連接服務(wù)器時(shí)的本地端口。每個(gè)客戶端在連接到服務(wù)器時(shí),操作系統(tǒng)會為它分配一個(gè)臨時(shí)的隨機(jī)端口。
客戶端輸入幾行語句
D:\code\golang\尚硅谷golang\代碼\chapter18\tcpdemo\client>go run "client(1).go"
123
你好
服務(wù)端內(nèi)容
服務(wù)器開始監(jiān)聽....
等待客戶端來鏈接....
Accept() suc con=&{{0xc00008a508}} 客戶端ip=[::1]:端口號
等待客戶端來鏈接....
123
你好
客戶端輸入exit即可結(jié)束
客戶端退出..D:\code\golang\尚硅谷golang\代碼\chapter18\tcpdemo\client>
服務(wù)端內(nèi)容
客戶端退出 err=read tcp [::1]:8888->[::1]:端口號: wsarecv: An existing connection was forcibly closed by the remote host.
注意,此時(shí)服務(wù)端仍在循環(huán)監(jiān)聽,按ctrl+c即可結(jié)束程序。
5.進(jìn)階練習(xí):客戶端之間通信
為了使兩個(gè)客戶端能夠相互通信,需要一個(gè)中介服務(wù)器(通常稱為“中轉(zhuǎn)服務(wù)器”),它負(fù)責(zé)轉(zhuǎn)發(fā)每個(gè)客戶端的消息給另一個(gè)客戶端。服務(wù)器在兩個(gè)客戶端之間保持連接,接收其中一個(gè)客戶端的消息,然后將其轉(zhuǎn)發(fā)給另一個(gè)客戶端。
實(shí)現(xiàn)步驟:
服務(wù)器端:
1.服務(wù)器負(fù)責(zé)接收客戶端 A 和客戶端 B 的連接。
2.服務(wù)器讀取每個(gè)客戶端發(fā)送的消息,然后將消息轉(zhuǎn)發(fā)給另一個(gè)客戶端。
package mainimport ("fmt""net""sync"
)var (clients = make(map[net.Conn]bool) // 存儲連接的客戶端mu sync.Mutex // 保護(hù) clients 的并發(fā)訪問
)func main() {listener, err := net.Listen("tcp", ":8888")if err != nil {fmt.Println("服務(wù)器啟動(dòng)錯(cuò)誤:", err)return}defer listener.Close()fmt.Println("服務(wù)器正在監(jiān)聽8888端口...")for {conn, err := listener.Accept()if err != nil {fmt.Println("接受連接時(shí)發(fā)生錯(cuò)誤:", err)continue}// 在服務(wù)端提示有新的客戶端連接fmt.Printf("新的客戶端已連接: %v\n", conn.RemoteAddr())// 將客戶端添加到客戶端列表mu.Lock()clients[conn] = truemu.Unlock()go handleConnection(conn) // 處理每個(gè)連接}
}func handleConnection(conn net.Conn) {defer func() {conn.Close()mu.Lock()delete(clients, conn) // 從列表中刪除客戶端mu.Unlock()}()buf := make([]byte, 1024)for {n, err := conn.Read(buf) // 讀取客戶端消息if err != nil {fmt.Printf("客戶端 %v 斷開連接: %v\n", conn.RemoteAddr(), err)return}message := string(buf[:n])// 判斷是否是退出消息if message == "exit\n" || message == "exit" {fmt.Printf("客戶端 %v 退出。\n", conn.RemoteAddr())return}// 轉(zhuǎn)發(fā)消息給其他客戶端mu.Lock()for client := range clients {if client != conn { // 不給發(fā)送者發(fā)送消息_, _ = client.Write(buf[:n]) // 將消息發(fā)送到其他客戶端}}mu.Unlock()}
}
客戶端:
1.客戶端連接到服務(wù)器,通過服務(wù)器發(fā)送和接收消息。
2.客戶端之間不會直接通信,而是通過服務(wù)器中轉(zhuǎn)消息。
package mainimport ("bufio""fmt""net""os""strings"
)func main() {// 使用 localhost 連接到服務(wù)器conn, err := net.Dial("tcp", "localhost:8888")if err != nil {fmt.Println("連接服務(wù)器失敗:", err)return}defer conn.Close() // 確保在退出時(shí)關(guān)閉連接// 啟動(dòng)一個(gè) goroutine 負(fù)責(zé)接收服務(wù)器的消息go func() {buf := make([]byte, 1024)for {n, err := conn.Read(buf) // 從服務(wù)器讀取數(shù)據(jù)if err != nil {fmt.Println("與服務(wù)器斷開連接:", err)return}// 顯示來自服務(wù)器的消息fmt.Print("收到消息: ", string(buf[:n]))}}()// 主協(xié)程負(fù)責(zé)發(fā)送用戶輸入的消息reader := bufio.NewReader(os.Stdin)fmt.Println("連接到服務(wù)器,輸入消息發(fā)送,輸入 'exit' 退出...")for {// 從終端讀取一行用戶輸入line, err := reader.ReadString('\n')if err != nil {fmt.Println("讀取輸入時(shí)發(fā)生錯(cuò)誤:", err)continue}// 去掉輸入行的換行符和空格go ruline = strings.TrimSpace(line)// 如果用戶輸入的是 exit,退出程序if line == "exit" {fmt.Println("客戶端退出...")break}// 將用戶輸入發(fā)送給服務(wù)器_, err = conn.Write([]byte(line + "\n"))if err != nil {fmt.Println("發(fā)送消息時(shí)發(fā)生錯(cuò)誤:", err)}}
}
效果截圖