并发C/S模型通信-最后有练习题

并发C/S模型通信

并发Server

现在已经完成了客户端与服务端的通信,但是服务端只能接收一个用户发送过来的数据,怎样接收多个客户端发送过来的数据,实现一个高效的并发服务器呢?
Accept()函数的作用是等待客户端的链接,如果客户端没有链接,该方法会阻塞。如果有客户端链接,那么该方法返回一个Socket负责与客户端进行通信。所以,每来一个客户端,该方法就应该返回一个Socket与其通信,因此,可以使用一个死循环,将Accept()调用过程包裹起来。
需要注意的是,实现并发处理多个客户端数据的服务器,就需要针对每一个客户端连接,单独产生一个Socket,并创建一个单独的goroutine与之完成通信。

//监听
    listener, err := net.Listen("tcp", "127.0.0.1:8001")    // tcp 不能使用大写
    if err != nil {
            fmt.Println("err = ", err)
            return
    }
    defer listener.Close()
    //接收多个用户
    for {
            conn, err := listener.Accept()
            if err != nil {
                fmt.Println("err = ", err)
                    return
         }
         //处理用户请求, 新建一个go程
         go HandleConn(conn)
}

将客户端的数据处理工作封装到HandleConn方法中,需将Accept()返回的Socket传递给该方法,变量conn的类型为:net.Conn。可以使用conn.RemoteAddr()来获取成功与服务器建立连接的客户端IP地址和端口号:

Conn 接口:

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    LocalAddr() Addr
    RemoteAddr() Addr
    SetDeadline(t time.Time) error
    SetReadDeadline(t time.Time) error
    SetWriteDeadline(t time.Time) error
}
 //获取客户端的网络地址信息
    addr := conn.RemoteAddr().String()
    fmt.Println(addr, " conncet sucessful")

客户端可能持续不断的发送数据,因此接收数据的过程可以放在for循环中,服务端也持续不断的向客户端返回处理后的数据。

添加一个限定,如果客户端发送一个“exit”字符串,表示客户端通知服务器不再向服务端发送数据,此时应该结束HandleConn方法,同时关闭与该客户端关联的Socket。

buf := make([]byte, 2048)    //创建一个切片,存储客户端发送的数据
**for**  {

        //读取用户数据

        n,  err  :=  conn.Read(buf)

        **if**  err  !=  nil  {

            fmt.Println("err  =  ",  err)

            **return**

        }

        fmt.Printf("[%s]:  %s\n",  addr,  string(buf[:n]))

        **if** "exit"  ==  string(buf[:n-2])  { //自己写的客户端测试,  发送时,多了2个字符,  "\r\n"

            fmt.Println(addr,  "  exit")

            **return**

        }

        //服务器处理数据:把客户端数据转大写,再写回给client

        conn.Write([]byte(strings.ToUpper(string(buf[:n]))))

 }

在上面的代码中,Read()方法获取客户端发送过来的数据,填充到切片buf中,返回的是实际填充的数据的长度,所以将客户端发送过来的数据进行打印,打印的是实际接收到的数据。

fmt.Printf("[%s]: %s\n", addr, string(buf[:n])).同时也可以将客户端的网络地址信息打印出来。

在判断客户端数据是否为“exit”字符串时,要注意,客户端会自动的多发送2个字符:“\r\n”(这在windows系统下代表回车、换行) linux系统下是n-1

Server使用Write方法将数据写回给客户端,参数类型是 []byte,需使用strings包下的ToUpper函数来完成大小写转换。转换的对象即为string(buf[:n])

综上,HandleConn方法完整定义如下:

//处理用户请求
func HandleConn(conn net.Conn) {
//函数调用完毕,自动关闭conn
defer conn.Close()

          //获取客户端的网络地址信息
          addr := conn.RemoteAddr().String()
          fmt.Println(addr, " conncet sucessful")

          buf := make([]byte, 2048)

          for {
                  //读取用户数据
                 n, err := conn.Read(buf)
                 if err != nil {
                         fmt.Println("err = ", err)
                         return
                    }
                    fmt.Printf("[%s]: %s\n",  addr,  string(buf[:n]))
                fmt.Println("len = ", len(string(buf[:n]))) 

                    //if "exit" == string(buf[:n-1]) {  // nc测试,发送时,只有 \n
                 if  "exit" == string(buf[:n-2]) {  // 自己写的客户端测试, 发送时,多了2个字符, "\r\n"
                        fmt.Println(addr, " exit")
                        return
                 }

                 //把数据转换为大写,再给用户发送
                 conn.Write([]byte(strings.ToUpper(string(buf[:n]))))
          }
}

练习题

实现类似nc 命令的客户端程序(IP地址和端口号可设为固定),能动态获取用户键盘输入数据,
并将用户输入的实际数据发送给服务器。获取到服务器回发的数据(如大写)后,显示到屏幕。
当用户键入 “exit”时,退出当前程序,并关闭与服务器的连接。

明天聊tcp、udp通信,敬请期待

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~