一.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()
}