最近看了一篇文章觉得非常好:
https://www.zhihu.com/question/21546408/answer/2303205686
为了加深印象,特写了一个程序来模拟一下其中的传输
传送门:github
因为代码和现实的一些差异,可能显得有些地方设计的有些丑陋了,有想法的可以多提提意见~
首先是主机层
package model
import "fmt"
//主机
type Computer struct {
Ip string//ip
Mac string//mac地址
SubnetMask string//子网掩码
DefaultGateway string//默认网关
Arp map[string]string//arp 用于缓存ip到mac的关系
ExchangeBoard *ExchangeBoard//所属的交换机
Port int//所属的交换机的端口
MsgCh chan Message //监听消息
MsgList []Message//消息存储下来
}
func (c *Computer)IsSubnet(ip string)bool{
return isIpSubNet(c.Ip,ip,c.SubnetMask)
}
func (c *Computer)NewMessage(ip,msg string)Message{
mac:=""
if c.IsSubnet(ip){//同一个子网走正常ip
mac=c.Arp[ip]
}else{
mac=c.Arp[c.DefaultGateway]//不是同一个子网走默认网关
}
return Message{
Head: MessageHead{
FromMac: c.Mac,
ToMac: mac,
FromIp: c.Ip,
ToIp: ip,
FromPort: c.Port,
},
Body: msg,
}
}
func (c *Computer)SendMessage(message Message){//只知道目标ip
c.ExchangeBoard.SendMessage(message)//通过交换机发送消息
}
func (c *Computer)Wait(){
go func() {
for{
select {
case v:=<-c.MsgCh:
if v.Head.ToMac==""{//第一次,不知道我的mac,需要通过ip验证
if v.Head.ToIp==c.Ip{
if v.Head.IsArpReq{//arp请求
c.Arp[v.Head.FromIp]=v.Head.FromMac//更新自己的arp表
message:=Message{
Head: MessageHead{
FromMac: c.Mac,
ToMac: v.Head.FromMac,
FromIp: c.Ip,
ToIp: v.Head.FromIp,
IsArpRes: true,
FromPort: c.Port,
},
}
c.MsgList=append(c.MsgList,v)//不确定要不要发送
c.ExchangeBoard.SendMessage(message)//通过交换机响应
}
}
} else if v.Head.ToMac==c.Mac{//收到消息,确实是自己的mac
if v.Head.IsArpRes{
wait=true
c.Arp[v.Head.FromIp]=v.Head.FromMac//只更新自己的arp表即可
}else{
fmt.Println(fmt.Sprintf("[%s]电脑收到[%s]的消息啦:%s",c.Mac,v.Head.FromMac,v.Body))
//正常请求,接收即可
c.MsgList=append(c.MsgList,v)
}
}
}
}
}()
}
然后是交换机:
package model
import (
"fmt"
"time"
)
//交换机
type ExchangeBoard struct {
MacPort map[string]int//mac与端口映射表
PortList PortSenderM//所有的端口 端口后可能是电脑,也可能是交换机
//ExchangeBoardPortM ExchangeBoardPortM//交换机与别的交换机连接的端口,比如A的端口上是B 就是A.ExchangeBoardPortM= [B]1
Router *Router//路由器
Port int //交换机与路由器连接的端口
}
func NewExchangeBoard()*ExchangeBoard{
return &ExchangeBoard{
MacPort: map[string]int{},
PortList: PortSenderM{},
}
}
func (e *ExchangeBoard)NewMessage(ip,msg string)Message{//交换机本身没有自己的初始消息
return Message{}
}
func (exchangeBoard *ExchangeBoard)SetSender(portSenderM PortSenderM){
for k,v:=range portSenderM{
exchangeBoard.PortList[k]=v
}
}
func (exchangeBoard *ExchangeBoard)GetSender(port int)Sender{
return exchangeBoard.PortList[port]
}
var wait bool
func (e *ExchangeBoard)SendMessage(message Message){
e.MacPort[message.Head.FromMac]=message.Head.FromPort//更新mac与端口映射表,每次都更新,防止机器换端口
if message.Head.ToMac==""{//目标mac为空,发送广播
message.Head.IsArpReq=true
for k,v:=range e.PortList{
if k==message.Head.FromPort && !message.Head.IsFromRouter{//发送方端口不对其发送消息 且不是从路由器来的消息
continue
}
e.commonSendMessage(v,message)
}
//这一段主要是为了解决同一个交换机下的机器不用通过路由器广播,如果同交换机下找不到,再走路由器
time.Sleep(1*time.Second)
if wait{
wait=false
return
}
if !message.Head.IsFromRouter{
message.Head.ToMac=e.Router.PortMacM[e.Port].Mac
e.Router.SendMessage(message)//给路由器也发
}
}else{//有mac
if port,ok:=e.MacPort[message.Head.ToMac];ok{
if port==-1{
e.Router.SendMessage(message)
}else{
e.commonSendMessage(e.PortList[port],message)
}
}else{//广播,更新mac地址表,不存在了
fmt.Println("?")
}
}
}
//给指定端口机器发送消息
func (e *ExchangeBoard)commonSendMessage(v interface{},message Message){
switch v.(type) {
case *Computer:
v.(*Computer).MsgCh<-message
//case *ExchangeBoard:
// message.Head.FromPort=v.(*ExchangeBoard).ExchangeBoardPortM[e]//交换机的的fromport需要替换
// v.(*ExchangeBoard).SendMessage(message)
}
}
然后是路由器:
package model
import (
"strings"
)
//路由器
type Router struct {
IpPort IpPort//ip网段与端口映射表 路由表 分为静态和动态
Arp map[string]string//arp 用于缓存ip到mac的关系
PortExchangeBoardM PortExchangeBoardM //路由器端口列表
PortMacM PortMacM//保存端口和mac,ip的关系 初始化就有
}
type NextPort struct {
Next *Router//下一跳
Port int//端口
}
type MacAndIp struct {
Mac string
Ip string
}
func (r *Router)SendMessage(message Message){
if message.Head.IsArpReq || message.Head.IsArpRes{
r.Arp[message.Head.FromIp]=message.Head.FromMac
}
port:=r.GetPortFromIp(message.Head.ToIp)//根据路由表找到ip对应的端口
message.Head.FromMac=r.PortMacM[port].Mac//来源mac置为路由器发送端口的mac
message.Head.ToMac=r.Arp[message.Head.ToIp]//需要根据arp得到ip对应的mac
message.Head.IsFromRouter=true
message.Head.FromPort=-1//-1代表在交换机上 路由器没有插在正常的lan口上,而是wan口,这里用-1表示
r.PortExchangeBoardM[port].SendMessage(message)//向指定端口发送消息
}
func (r *Router)GetPortFromIp(ip string)int{
for k,v:=range r.IpPort{
ipList:=strings.Split(k,"/")
if len(ipList)==0{
panic("error")
}
if isIpSubNet(ip,ipList[0],SubNetM[ipList[1]]){
if v.Next!=nil{
return v.Next.GetPortFromIp(ip)
}
return v.Port
}
}
panic("数据不可达")
}
还有一些其他的方法先不展示了,直接初始化各个设备,插上设备:
package main
import (
"mac-ip/model"
"time"
)
var exchangeBoard1,exchangeBoard2,exchangeBoard3 *model.ExchangeBoard
var senderM1=model.PortSenderM{
0:&model.Computer{
Ip: "192.168.0.1",
Mac: "AAAA",
SubnetMask: "255.255.255.0",
DefaultGateway: "192.168.0.254",
ExchangeBoard:exchangeBoard1,
Port: 0,
},1:&model.Computer{
Ip: "192.168.0.2",
Mac: "BBBB",
SubnetMask: "255.255.255.0",
DefaultGateway: "192.168.0.254",
ExchangeBoard: exchangeBoard1,
Port: 1,
},
}
var senderM2=model.PortSenderM{
0:&model.Computer{
Ip: "192.168.1.1",
Mac: "CCCC",
SubnetMask: "255.255.255.0",
DefaultGateway: "192.168.1.254",
ExchangeBoard: exchangeBoard2,
Port: 0,
},1:&model.Computer{
Ip: "192.168.1.2",
Mac: "DDDD",
SubnetMask: "255.255.255.0",
DefaultGateway: "192.168.1.254",
ExchangeBoard: exchangeBoard2,
Port: 1,
}}
var senderM3=model.PortSenderM{
0:&model.Computer{
Ip: "192.168.2.1",
Mac: "EEEE",
SubnetMask: "255.255.255.0",
DefaultGateway: "192.168.2.254",
ExchangeBoard: exchangeBoard3,
Port: 0,
},1:&model.Computer{
Ip: "192.168.2.2",
Mac: "FFFF",
SubnetMask: "255.255.255.0",
DefaultGateway: "192.168.2.254",
ExchangeBoard: exchangeBoard3,
Port: 1,
}}
var router=&model.Router{
IpPort: model.IpPort{
"192.168.0.0/24":&model.NextPort{
Next: nil,
Port: 0,
},
"192.168.1.0/24":&model.NextPort{
Next: nil,
Port: 1,
},
},
Arp: map[string]string{},
PortExchangeBoardM: nil,
PortMacM: model.PortMacM{
0:model.MacAndIp{
Mac: "ABAB",
Ip: "192.168.0.254",
},1:model.MacAndIp{
Mac: "CDCD",
Ip: "192.168.1.254",
},
},
}
func init(){
exchangeBoard1,exchangeBoard2,exchangeBoard3=model.NewExchangeBoard(),model.NewExchangeBoard(),model.NewExchangeBoard()
exchangeBoard1.SetSender(senderM1)
exchangeBoard2.SetSender(senderM2)
exchangeBoard3.SetSender(senderM3)
router.PortExchangeBoardM=model.PortExchangeBoardM{
0:exchangeBoard1,
1:exchangeBoard2,
}
exchangeBoard1.Router=router
exchangeBoard1.Port=0
exchangeBoard2.Router=router
exchangeBoard2.Port=1
initComputer(exchangeBoard1,exchangeBoard2,exchangeBoard3)
wait(exchangeBoard1,exchangeBoard2,exchangeBoard3)//监听消息
}
func main(){
computer,_,computer3,_:=exchangeBoard1.GetSender(0),exchangeBoard1.GetSender(1),exchangeBoard2.GetSender(0),exchangeBoard3.GetSender(0)
computer.SendMessage(computer.NewMessage("192.168.0.2","arp"))
computer.SendMessage(computer.NewMessage("192.168.1.1","arp"))
//computer.SendMessage(computer.NewMessage("192.168.2.1","arp"))
time.Sleep(1*time.Second)
computer.SendMessage(computer.NewMessage("192.168.0.2","hello"))
//computer.SendMessage(computer.NewMessage("192.168.0.2","hello2"))
//computer2.SendMessage(computer2.NewMessage("192.168.0.1","hello2"))
computer.SendMessage(computer.NewMessage("192.168.1.1","hello3"))
computer3.SendMessage(computer3.NewMessage("192.168.0.1","hello4"))
for{
}
}
func wait(ExchangeBoardList ...*model.ExchangeBoard){
for i,_:=range ExchangeBoardList{
go func(i int) {
for _,v:=range ExchangeBoardList[i].PortList{
if _,ok:=v.(*model.Computer);ok{
v.(*model.Computer).Wait()
}
}
}(i)
}
}
func initComputer(exchangeBoardList ...*model.ExchangeBoard){
for _,exchangeBoard:=range exchangeBoardList{
for _,v:=range exchangeBoard.PortList{
if computer,ok:=v.(*model.Computer);ok{
computer.ExchangeBoard=exchangeBoard
computer.MsgCh=make(chan model.Message,20)
computer.Arp=map[string]string{}
}
}
}
}
跑起来:
没毛病
目录结构是这样的: