一、概述
在计算机世界,两个或多个计算机之间的通信是基于OSI(Open System Interconnection)开放式系统互联协议进行网络通信。该通信协议分为7层,然而实际上在现实世界中OSI并没有大规模使用,现实世界常用的是tcp/ip协议,而tcp/ip协议可划分为4或5层。
- 应用层,表示层,会话层,传输层,网络层,数据链路层,物理层
- 应用层,传输层,网络层,数据链路层,物理层
- 应用层,传输层,网络层,网络接口层
关系如图:
可以看出4层模型是由5层合并得到,5层模型是由7层归纳合并得到。
在5层模型结构中:
应用层(http,ftp协议,一些自定义协议:数据的加密,规定应用程序的数据格式)
传输层(tcp,udp协议:建立port到port的通信协议)
网络层(ip协议:包装本,目标网ip信息协议)
数据链路层(Ethernet以太网协议:包装了本地MAC和目标MAC地址相关信息协议)
物理层(将以上各层的数据打包层二进制发送给目标网络,目标网络反向逐层解析数据)
二、Go中的网络编程
在上述的模型中,在应用层和传输层中,基于传输层抽象了一些列接口而此接口即用于我们的实际开发中为socket(套接字)。
socket也叫套接字, 是为了方便程序员进行网络开发而被设计出来的编程接口. socket在七层模型中是属于传输层(TCP,UDP协议)之上 的一个抽象接口.
Go语言的标准库net包里对socket封装了一些列实现网络通信的api
1、基于tcp
tcp协议:两网络通信之前需要建立链接,其处于OSI 7层模型中的传输层。其建立端到端的通信协议,其链接和断开有3次握手和4次挥手的特点。
相关api:
server端:
socket,_ := net.Listen("tcp","127.0.0.1:8080")
socket.Accept()
conn.Read([]byte)
conn.Write([]byte)
defer -> socket.Close() conn.Close()
client端:
conn,_ := net.Dial("tcp","127.0.0.1:8080")
conn.Read([]byte)
conn.Write(data)
defer -> conn.Close()
server端通过net.Listen方法获取到socket对象,通过此socket的Accept()阻塞等待一个client端的一次链接。当链接建立成功后拿到conn通道对象,通过其Read(),Write()方法即可向该链接通道里读写数据,继而实现网络通信。在方法结束或对方结束通信后应该关闭socket和链接conn通道。
client端通过net.Dai()方法建立一个通信链接conn,通过其Read(),Write()方法实现其个server之间的通信。
注意当通道conn里没有数据时Read()方法为一阻塞状态,故可以通过for循环来实现不断的获取对方发送来的数据。
server:
package main
import (
"fmt"
"io"
"net"
"strings"
)
/**
结论:
0.listener.Accept()在client未Dial请求链接前为阻塞
1.无论server还是clien端只要conn.Close()关闭了,自己的conn.Read()方法就会在循环里不断调用,读取的字节大小n为0,err为EOF.故要做判断。
2.conn.Read()方法在conn链接未关闭前,没接收到数据是阻塞的。
3.conn.Write()方法向对方发送二进制流数据。
*/
//服务端
func main(){
socket,err := net.Listen("tcp","127.0.0.1:8080")
checkError(err)
defer socket.Close()
fmt.Println("socket server start ...")
for{
conn,err := socket.Accept()//阻塞于此,等待client端的链接请求
go handlerConn(conn,err)//开启协程独立处理每条client的链接请求与响应
}
}
func checkError(err error){
if err!=nil {
panic(err)
}
}
func handlerConn(conn net.Conn,err error){
defer conn.Close()
checkError(err)
for{
//读取客户端发来的信息
receiveData := make([]byte,4)
n,resErr:=conn.Read(receiveData) //阻塞等待client端发送到conn链接通道数据。n为每次读取到的字节数,将每次读取的数据最大以len(receiveData)字节长度存储到切片中,若超过则再遍历一次进行读取后段数据
data := receiveData[0:n] //切片切取到实际数据量
if strings.ToUpper(strings.TrimSpace(string(data))) == "EXIT" {
conn.Close()
fmt.Println("client结束了聊天!")
return
}
if n==0 && resErr == io.EOF {
conn.Close()
return
}
fmt.Printf("收到client信息:%d %v %s",n,resErr,string(receiveData))
}
}
client:
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
//客户端
func main(){
conn,err := net.Dial("tcp","127.0.0.1:8080")
checkError(err)
defer conn.Close()//关闭链接
for{
//监听输入,将输入发送给服务端
fmt.Println("input>>")
r := bufio.NewReader(os.Stdin)
data,_,_ := r.ReadLine()
conn.Write(data)
if strings.ToUpper(string(data)) == "EXIT" {
break //跳出循环,结束程序
}
}
}
func checkError(err error){
if err!=nil {
panic(err)
}
}
- 1v1: 以上为server To Client端1v1的方式实现通信
- nvn: 可以想到server端在和多个client建立链接conn后我们可以对这些链接进行一些列的处理,将2. client之间发送来到信息进行指定链接通道的写入。从而实现多个client之间的通信。此时的server就相当于一个信息中转站的角色。
2、基于udp
udp协议:是一种不需要建立链接,故存在数据可能会丢失的情况,不可靠。但其速度快,UCP不维护连接状态,也不跟踪这些参数,开销小。空间和时间上都具有优势。UDP也常用于多媒体应用(如IP电话,实时视频会议,直播,流媒体等)数据的可靠传输对他们而言并不重要,TCP的拥塞控制会使他们有较大的延迟,也是不可容忍的。
相关api:
server:
udpAddr,err := net.ResolveUDPAddr("udp","127.0.0.1:8080") //创建server端地址结构
socket,err := net.ListenUDP("udp",udpAddr) //创建socket
socket.ReadFromUDP(data) //读取client端数据
socket.WriteToUDP() // 向client发送数据
defer -> socket.Close()
client:
conn,_ := net.Dial("udp","127.0.0.1:8080")
conn.Read([]byte)
conn.Write(data)
defer -> conn.Close()
server:
package main
import (
"fmt"
"io"
"net"
)
func main(){
udpAddr,err := net.ResolveUDPAddr("udp","127.0.0.1:8080")
checkError(err)
socket,err := net.ListenUDP("udp",udpAddr)
checkError(err)
//读取client端信息
for{
data := make([]byte,1024)
n,_,err:=socket.ReadFromUDP(data)
if n == 0 && err == io.EOF {
break
}
fmt.Println("收到client端信息:",string(data[0:n]))
}
//向client写数据
//udpConn.WriteToUDP()
defer socket.Close()
}
func checkError(err error){
if err!=nil {
panic(err)
}
}
client:
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
//客户端
func main(){
conn,err := net.Dial("udp","127.0.0.1:8080")
checkError(err)
defer conn.Close()//关闭链接
for{
//监听输入,将输入发送给服务端
fmt.Println("input>>")
r := bufio.NewReader(os.Stdin)
data,_,_ := r.ReadLine()
conn.Write(data)
if strings.ToUpper(string(data)) == "EXIT" {
break //跳出循环,结束程序
}
}
}
func checkError(err error){
if err!=nil {
panic(err)
}
}