Golang聊天室

liuzhongchao · · 877 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一、前期准备

  • 前期准备
  • 需要 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 main

import (
    "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
}

 


有疑问加站长微信联系(非本文作者)

本文来自:博客园

感谢作者:liuzhongchao

查看原文:Golang聊天室

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

877 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传