一、前期準備
- 前期準備
- 需要 import "net"包
- IP 類型,其中一個重要的方法是 IP.ParseIP(ipaddr string)來判斷是否是合法的 IP 位址
- TCP Client
func (c *TCPConn) Write(b []byte) (n int, err os.Error)
用於發送資料,返回傳送的資料長度或者返回錯誤,是TCPConn的方法
func (c *TCPConn) Read(b []byte) (n int, err os.Error)
用於接收資料,返回接收的長度或者返回錯誤,是 TCPConn 的方法
- TCPAddr 類型,儲存 TCP 的地址資訊,包括地址和連接埠
type TCPAddr struct { IP IP Port int }
func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
擷取一個 TCPAddr,參數都是 string 類型,net 是個 const string,包括 tcp4,tcp6,tcp 一般使用 tcp,相容 v4 和 v6,addr 表示 ip 地址,包括連接埠號碼,如www.google.com:80之類的
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
用來串連(connect)到遠程伺服器上,net 表示協議方式,tcp,tcp4 或者 tcp6,laddr 表示本機地址,一般為 nil,raddr 表示遠程地址,這裡的 laddr 和 raddr 都是 TCPAddr 類型的,一般是上一個函數的傳回值。
- 作為一個 TCP 的用戶端,基本的操作流程如下:
service="www.google.com:80" tcpAddr, err := net.ResolveTCPAddr("tcp4", service) conn, err := net.DialTCP("tcp", nil, tcpAddr) _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n")) _, err = conn.Read(b) / result, err := ioutil.ReadAll(conn)
- TCP Server
func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)
用來監聽連接埠,net 表示協議類型,laddr 表示本機地址,是 TCPAddr 類型,注意,此處的 laddr 包括連接埠,返回一個*TCPListener類型或者錯誤
func (l *TCPListener) Accept() (c Conn, err os.Error)
用來返回一個新的串連,進行後續操作,這是 TCPListener 的方法,一般 TCPListener 從上一個函數返回得來。
- 伺服器的基本操作流程為:
service:=":9090" tcpAddr, err := net.ResolveTCPAddr("tcp4", service) l,err := net.ListenTCP("tcp",tcpAddr) conn,err := l.Accept() go Handler(conn) //此處使用go關鍵字建立線程處理串連,實現並發
二、聊天室需求
實現一個公用聊天伺服器。
- 伺服器接收用戶端的資訊
- 接收完以後將用戶端的資訊發送到所有的用戶端上
- 用戶端使用
/quit
退出聊天
- 只使用一套代碼,通過命令列參數啟動伺服器還是用戶端
主要知識點如下:
代碼中包括了伺服器和用戶端的內容,如果是伺服器,直接輸入go run main.go server 9090
即可,用戶端也很簡單,輸入go run main.go client :9090
就好;
如果是用戶端,其實就包括了兩部分內容,一部分是 chatSend 函數,接受使用者的輸入;另一部分是connect 到 server,接受相關資訊;
如果是 server,稍微複雜一點,有三個部分組成。第一部分就是不停地 accept 各個用戶端;第二個就是為每一個用戶端創立 Handler 函數,接受用戶端發來的資訊;第三個就是 echoHandler 函數,它的作用就是將從某一使用者接受過來的資訊廣播給其他所有的用戶端,就是這麼簡單。
代碼實現:
package mainimport ( "os" "fmt" "net")/**主程式 啟動用戶端和服務端參數說明: 啟動伺服器端: go run main.go server [port] eg: go run main.go server 9090 啟動用戶端: go run main.go [Server Ip Addr]:[Server Port] eg: go run main.go client :9090 */func main() { if len(os.Args) != 3 { fmt.Println("wrong params") os.Exit(0) } if os.Args[1] == "server" { StartServer(os.Args[2]) } if os.Args[1] == "client" { StartClient(os.Args[2]) }}/**啟動伺服器參數:port 連接埠號碼 */func StartServer(port string) { service := ":" + port tcpAddr,err := net.ResolveTCPAddr("tcp4", service) checkError(err, "ResolveTCPAddr") l,err := net.ListenTCP("tcp", tcpAddr) checkError(err ,"ListenTCP") conns := make(map[string]net.Conn) messages := make(chan string, 10) //啟動伺服器廣播線程 :向所有用戶端發送訊息 go echoHandler(&conns, messages) for { fmt.Println("Listening ...") conn,err := l.Accept()//返回一個新的串連 checkError(err , "l.Accept") fmt.Println("Accepting ...") conns[conn.RemoteAddr().String()] = conn //啟動一個接受用戶端發送訊息的線程 go Handler(conn, messages) }}/**伺服器發送資料的線程:向所有用戶端發送訊息參數 串連字典 conns 資料通道 messages */func echoHandler(conns *map[string]net.Conn, messages chan string) { for { msg := <-messages fmt.Println(msg) for key,con := range *conns { fmt.Println("connection is connected from ...", key) _,err := con.Write([]byte(msg)) if err != nil { fmt.Println(err) delete(*conns, key) } } }}/**伺服器端接收用戶端資料線程參數: 據串連 conn 通訊通道 messages */func Handler(conn net.Conn, messages chan string) { fmt.Println("connection is connected from ...", conn.RemoteAddr().String()) buf := make([]byte, 1024) for { lenght,err := conn.Read(buf) if checkError(err, "Connection") == false { conn.Close() break } if lenght >0 { buf[lenght] = 0 } reciveStr := string(buf[0:lenght]) messages <- reciveStr }}/**用戶端啟動函數參數: 程ip地址和連接埠 tcpaddr */func StartClient(tcpaddr string) { tcpAddr,err := net.ResolveTCPAddr("tcp4", tcpaddr) checkError(err, "ResolveTCPAddr") conn,err := net.DialTCP("tcp",nil,tcpAddr) checkError(err, "DialTCP") //啟動用戶端發送資料線程 go chatSend(conn) //接收服務端發送來的訊息 buf := make([]byte, 1024) for { lenght,err := conn.Read(buf) if checkError(err, "Connection") == false { conn.Close() fmt.Println("Server is dead ...ByeBye") os.Exit(0) } fmt.Println(string(buf[0:lenght])) }}/**用戶端發送資料線程參數: 發送串連 conn */func chatSend(conn net.Conn) { var input string username := conn.LocalAddr().String() for { fmt.Scanln(&input) if input == "/quit" { fmt.Println("ByeBye..") conn.Close() os.Exit(0) } lens,err := conn.Write([]byte(username + "say ::: " + input)) fmt.Println(lens) if err != nil { fmt.Println(err.Error()) conn.Close() break } }}//錯誤資訊func checkError(err error, info string) (res bool) { if err != nil { fmt.Println(info + ",err:" + err.Error()) return false } return true}