一.Socket简介
- 在标准库的net包中可供了可移植的网络I/O接口,其中就包含了Socket
- Socket在TCP/IP网络分层中并不存在,是对TCP或UDP封装
Socket可以简单理解为:
- 实现网络上双向通讯连接的一套API
- 常称Socket为"套接字"
Socket分类:
- 按照连接时间可以分为:短连接、长连接
- 按照客户端和服务器端数量可分为:点对点、点对多、多对点。
注意:网络通信包含客户端和服务端。其中服务端运行在服务器中,而客户端运行在客户端中,客户端可以是浏览器,可以是桌面程序,也可以是手机App.客户端和服务端进行数据交互必须遵守特定的协议.
二.Go语言对Socket的支持
提供了结构体
Go语言中提供了三个结构体:TCPAddr、TCPConn、TCPListener
TCPAddr结构体表示服务器IP和端口
TCPConn结构体表示连接,封装了数据读写操作
TCPListener负责监听服务器端特定端口
接下来,将通过代码使用这些结构体,并展示客户端与服务端的简易实现
客户端与服务端的连接
不多说,直接上代码
package main
import "net"
func main() {
//1.创建服务器端地址
addr,_:=net.ResolveTCPAddr("tcp4","localhost:8899")
//2.创建连接 //当前本机客户端地址(可以为nil)
conn,_:=net.DialTCP("tcp4",nil,addr)
//3.发送数据
conn.Write([]byte("客户端发送的信息"))
//4.关闭连接
conn.Close()
}
服务端
package main
import (
"fmt"
"net"
)
func main() {
//1.创建服务区地址
addr,_:=net.ResolveTCPAddr("tcp4","localhost:8899")
//2.创建监听器
lis,_:=net.ListenTCP("tcp4",addr)
fmt.Println("服务器已启动")
//3.通过监听器获取客户端传递过来的数据
//阻塞式
conn,_:=lis.Accept()
//4.转换数据
b:=make([]byte,1024)
n,_:=conn.Read(b)
fmt.Println("获取到的数据为",string(b[:n]))
//5.关闭连接
conn.Close()
}
先运行服务端代码。
运行结果为
服务器已启动
这个时候,服务端的运行还未结束。
再运行客户端代码
服务端的运行结果为
服务器已启动
获取到的数据为 客户端发送的信息
服务端运行结束
服务端接收数据并返回数据
可以在服务端添加循环,不停接收客户端发送来的数据,服务端代码修改如下
客户端
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("客户端结束")
}
服务端
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("服务器结束")
}
并发访问
上面的代码都使用了阻塞式的访问,于是服务器一次只能服务一个客户端对象,而服务器获取到客户端对象后,如果客户端什么也没有输入,其他客户端无法访问.
这个问题可以通过结合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中,所以没有进行持久化操作.
示例如下:
服务端代码
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)))
}
}
}()
}
}
客户端代码
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()
}
以上就是Go语言和Socket的一点简易介绍。