Golang-Web(Socket通信)

一.Socket简介

  • 在标准库的net包中可供了可移植的网络I/O接口,其中就包含了Socket

  • Socket在TCP/IP网络分层中并不存在,是对TCP或UDP封装

  • 如果非要给Socket一个解释

    • 实现网络上双向通讯连接的一套API

    • 常称Socket为"套接字"

  • Socket分类:

    • 按照连接时间

      • 短连接

      • 长连接(HTTP 1.1开始也支持长连接,Socket替换方案)

    • 按照客户端和服务器端数量

      • 点对点

      • 点对多

      • 多对多

  • 网络通信内容都是包含客户端和服务端,服务端运行在服务器中,而客户端运行在客户端中,可以是浏览器,可以是桌面程序,也可以是手机App.客户端和服务端进行数据交互遵守特定的协议.

二.Go语言对Socket的支持

  • TCPAddr结构体表示服务器IP和端口

    • IP是type IP []byte

    • Port是服务器监听的接口

// TCPAddr represents the address of a TCP end point.
type TCPAddr struct {
    IP   IP
    Port int
    Zone string // IPv6 scoped addressing zone
}
  • TCPConn结构体表示连接,封装了数据读写操作

// TCPConn is an implementation of the Conn interface for TCP network
// connections.
type TCPConn struct {
    conn
}
  • TCPListener负责监听服务器端特定端口

// TCPListener is a TCP network listener. Clients should typically
// use variables of type Listener instead of assuming TCP.
type TCPListener struct {
    fd *netFD
}

三.客户端向服务端发送消息

  • 服务端代码

package main
​
import (
   "net"
   "fmt"
)
​
func main() {
   //创建TCPAddress变量,指定协议tcp4,监听本机8899端口
   addr, _ := net.ResolveTCPAddr("tcp4", "localhost:8899")
​
   //监听TCPAddress设定的地址
   lis, _ := net.ListenTCP("tcp4", addr)
​
   fmt.Println("服务器已启动")
​
   //阻塞式等待客户端消息,返回连接对象,用于接收客户端消息或向客户端发送消息
   conn, _ := lis.Accept()
​
   //把数据读取到切片中
   b := make([]byte, 256)
   fmt.Println("read之前")
   //客户端没有发送数据且客户端对象没有关闭,Read()将会阻塞,一旦接收到数据就不阻塞
   count, _ := conn.Read(b)
   fmt.Println("接收到的数据:", string(b[:count]))
   //关闭连接
   conn.Close()
   fmt.Println("服务器结束")
}
  • 客户端代码

package main
​
import (
   "net"
   "fmt"
)
​
func main() {
   //服务器端ip和端口
   addr, _ := net.ResolveTCPAddr("tcp4", "localhost:8899")
   //申请连接客户端
//第二个参数:本地地址  第三个参数:远程地址
   conn, _ := net.DialTCP("tcp4", nil, addr)
   //向服务端发送数据
   count, _ := conn.Write([]byte("客户端传递的数据"))
   fmt.Println("客户端向服务端发送的数据量为:", count)
   //通过休眠测试客户端对象不关闭,服务器是否能接收到对象
   //time.Sleep(10 * time.Second)
   关闭连接
   conn.Close()
   //fmt.Println("客户端结束")
​
}

四. 服务端接收数据并返回数据

  • 服务端代码

package main
​
import (
   "net"
   "fmt"
)
​
func main() {
   //创建TCPAddress变量,指定协议tcp4,监听本机8899端口
   addr, _ := net.ResolveTCPAddr("tcp4", "localhost:8899")
​
   //监听TCPAddress设定的地址
   lis, _ := net.ListenTCP("tcp4", addr)
​
   fmt.Println("服务器已启动")
​
   //阻塞式等待客户端消息,返回连接对象,用于接收客户端消息或向客户端发送消息
   conn, _ := lis.Accept()
​
   //把数据读取到切片中
   b := make([]byte, 256)
   fmt.Println("read之前")
   //客户端没有发送数据且客户端对象没有关闭,Read()将会阻塞,一旦接收到数据就不阻塞
   count, _ := conn.Read(b)
   fmt.Println("接收到的数据:", string(b[:count]))
​
   /*
   向客户端发送数据
    */
   conn.Write([]byte("这是服务器传递的数据"))
​
   //关闭连接
   conn.Close()
   fmt.Println("服务器结束")
}
  • 客户端代码

package main
​
import (
   "net"
   "fmt"
)
​
func main() {
   //服务器端ip和端口
   addr, _ := net.ResolveTCPAddr("tcp4", "localhost:8899")
   //申请连接客户端
   conn, _ := net.DialTCP("tcp4", nil, addr)
   //向服务端发送数据
   count, _ := conn.Write([]byte("客户端传递的数据"))
   fmt.Println("客户端向服务端发送的数据量为:", count)
​
   /*
   接收服务器传递回来的数据
    */
   b := make([]byte, 256)
   c, _ := conn.Read(b)
   fmt.Println(string(b[:c]))
​
   关闭连接
   conn.Close()
   fmt.Println("客户端结束")
​
}
  • 可以在服务端添加循环,不停接收客户端发送来的数据,服务端代码修改如下

package main
​
import (
   "net"
   "fmt"
)
​
func main() {
   addr, _ := net.ResolveTCPAddr("tcp4", "localhost:8899")
   lis, _ := net.ListenTCP("tcp4", addr)
   fmt.Println("服务器已启动")
​
   /*
   服务器端添加死循环,不停的接收客户端对象
    */
   for {
      conn, _ := lis.Accept()
      b := make([]byte, 256)
      count, _ := conn.Read(b)
      nc := string(b[:count])
      fmt.Println("接收到的数据:", nc)
      conn.Write([]byte("服务器:" + nc))
      conn.Close()
   }
   fmt.Println("服务器结束")
}
  • 客户端代码修改如下

package main
​
import (
   "net"
   "fmt"
   "strconv"
)
​
func main() {
   //服务器端ip和端口
   addr, _ := net.ResolveTCPAddr("tcp4", "localhost:8899")
​
   //通过循环,模拟发送五次客户端请求
   for i := 1; i <= 5; i++ {
      conn, _ := net.DialTCP("tcp4", nil, addr)
      conn.Write([]byte("客户端数据" + strconv.Itoa(i)))
      b := make([]byte, 256)
      c, _ := conn.Read(b)
      fmt.Println("第", i, "次服务器返回的数据:", string(b[:c]))
      conn.Close()
   }
​
   fmt.Println("客户端结束")
​
}

并发访问

  • 上面代码的问题是服务器获取到客户端对象后,如果客户端什么也没有输入,其他客户端无法连接.可以通过结合goroutine完成并发访问

  • 只需要修改server.go,在里面添加goroutine

package main
​
import (
   "net"
   "fmt"
)
​
func main() {
   addr, _ := net.ResolveTCPAddr("tcp4", "localhost:8899")
   lis, _ := net.ListenTCP("tcp4", addr)
   fmt.Println("服务器已启动")
​
   /*
   服务器端添加死循环,不停的接收客户端对象
    */
   for {
      conn, _ := lis.Accept()
      go func() { //在此处添加创建go func()即可
         b := make([]byte, 256)
         count, _ := conn.Read(b)
         nc := string(b[:count])
         fmt.Println("接收到的数据:", nc)
         conn.Write([]byte("服务器:" + nc))
         conn.Close()
      }()
   }
   fmt.Println("服务器结束")
}

五.点对点通信概述

  • 点对点通信就是客户端A发送消息给服务端,再由服务端把消息传递给客户端B.相同道理客户端B想给客户端A发送消息也需要把消息传递给服务端,再由服务端把消息传递给A

  • 正常情况下客户端A和客户端B可以通过用户名、IP等唯一身份标识区分每个用户.在本功能练习中要求客户端先注册用户名,然后告诉服务端给谁发消息.如果用户名存在不允许注册

  • 在本功能练习中用户信息存储到临时容器map中,没有进行持久化操作.

代码实现

  • 项目结构如下

--项目名
    --src
        --client
            client.go
        --server
            server.go
    main.go
  • 在server.go中编写代码

package main
​
import (
    "net"
    "fmt"
    "strings"
)
​
type User struct {
    Username      string
    OtherUsername string
    Msg           string
    ServerMsg     string
}
​
var (
    userMap = make(map[string]net.Conn)
    user    = new(User)
)
​
func main() {
    addr, _ := net.ResolveTCPAddr("tcp4", ":9999")
    lis, _ := net.ListenTCP("tcp4", addr)
​
    for {
        conn, _ := lis.Accept()
        go func() {
            for {
                b := make([]byte, 512)
                //读取数据
                count, _ := conn.Read(b)
​
                arrStr := strings.Split(string(b[:count]), "-")
                user.Username = arrStr[0]
                user.OtherUsername = arrStr[1]
                user.Msg = arrStr[2]
                user.ServerMsg = arrStr[3]
                userMap[user.Username] = conn
                if v, ok := userMap[user.OtherUsername]; ok && v != nil {
                    user.ServerMsg = ""
                    n, e := v.Write([]byte(fmt.Sprintf("%s-%s-%s-%s", user.Username, user.OtherUsername, user.Msg, user.ServerMsg)))
                    if n == 0 || e != nil {
                        conn.Close()
                        delete(userMap, user.OtherUsername)
                        break
                    }
                } else {
                    user.ServerMsg = "对方不在线"
                    n, e := conn.Write([]byte(fmt.Sprintf("%s-%s-%s-%s", user.Username, user.OtherUsername, user.Msg, user.ServerMsg)))
                }
            }
        }()
    }
}
  • 在client.go中编写代码

package main
​
import (
    "net"
    "fmt"
    "sync"
    "os"
    "strings"
)
​
type User struct {
    Username      string
    OtherUsername string
    Msg           string
    ServerMsg     string
}
​
var (
    user = new(User)
    wg   sync.WaitGroup
)
​
func main() {
    wg.Add(1)
    fmt.Println("请登录,输入用户名:")
    fmt.Scanln(&user.Username)
    fmt.Println("请输入要给谁发送消息")
    fmt.Scanln(&user.OtherUsername)
    addr, _ := net.ResolveTCPAddr("tcp4", ":9999")
    conn, _ := net.DialTCP("tcp4", nil, addr)
    go func() {
        fmt.Print("请输入:(只提示一次,以后直接输入即可)")
        for {
            fmt.Scanln(&user.Msg)
            if user.Msg == "exit" {
                conn.Close()
                wg.Done()
                os.Exit(0)
            }
            conn.Write([]byte(fmt.Sprintf("%s-%s-%s-%s", user.Username, user.OtherUsername, user.Msg, user.ServerMsg)))
        }
    }()
    go func() {
        for {
            rb := make([]byte, 512)
            c, _ := conn.Read(rb)
            user2 := new(User)
            arrStr := strings.Split(string(rb[:c]), "-")
            user2.Username = arrStr[0]
            user2.OtherUsername = arrStr[1]
            user2.Msg = arrStr[2]
            user2.ServerMsg = arrStr[3]
            if user2.ServerMsg != "" {
                fmt.Println("\t\t\t服务器消息:", user2.ServerMsg)
            } else {
                fmt.Println("\t\t\t", user2.Username, ":", user2.Msg)
            }
        }
    }()
    wg.Wait()
}
​

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值