需求描述
PC
能够检测到同一局域网中已连接的手机,并记录连接状态。
实现方式
- 第一时间想到的是
ping
的方式,但是同一局域网内,可能有很多连接着的设备,不能确定能够ping
通的设备是否是需要连接的设备,这里推荐一个开源库 fastping,实现了ping
的功能,简单实用。 - 手机和
PC
建立tcp
连接,通过指定命令进行交互。例:
PC
发送start
至 手机,手机接收到后返回ok
,进行一次简单的交互判断。
问题描述
手机 app
作为服务端,在同一局域网内,PC
连接上手机 tcp
服务后,阻塞读取手机消息(手机一直不回传消息)。
手机断网,PC
的 conn.Read
方法不会直接错误退出(会在大约3分钟后退出,和操作系统有关,源码注释)。
手机主动 close
结束 tcp
连接,则 PC
端会直接错误退出。
代码示例
go
客户端,使用 conn.Read
方法进行连接的判断。
func client() {
conn, err := net.DialTimeout("tcp", "172.16.10.85:3485", time.Second * 3)
if err != nil {
fmt.Println(err)
return
}
defer conn.Close()
fmt.Println("有连接: ", conn.RemoteAddr())
fmt.Println("当前时间: ", time.Now().String())
defer func() {
fmt.Println("结束时间:", time.Now().String())
} ()
buf := make([]byte, 1024)
n, err := conn.Read(buf) // 理想状态下,连接没断开,这边会一直卡住。当连接断开,会直接错误退出
if err != nil {
fmt.Println(err)
return
}
fmt.Println("read: ", string(buf[:n]))
}
解决方式
直接通过 start
ok
的交互来判断连接的存在。这里考虑到了网络波动,会尝试重新建立新的连接。
func (this *AgentService)listenConLeft(con net.Conn, multiId string) {
if con == nil {
common.Log.Info("con is nil")
return
}
addr := con.RemoteAddr().String()
defer func() {
if er := recover(); er != nil {
this.deleteDevice(addr)
}
}()
//con.Read() // 使用read卡住,断网不会立即响应
duration := time.Second * 5
ticker := time.NewTicker(duration)
defer ticker.Stop()
for range ticker.C {
err := CheckConnect(con, "start", duration)
if err != nil {
if con != nil {
con.Close()
}
// 若没查询到手机,则直接尝试建立新的连接
con, err = net.DialTimeout("tcp", addr, duration)
if err != nil {
this.deleteDevice(addr)
return
}
// 正常情况下,手机断网再重新接入网络,ip地址是不会变化的,设备信息也就不会变化。
// 这边考虑ip地址若变化的情况,需要检测下设备信息
curMultiId, err := GetAgentDeviceMultiId(con)
if err != nil {
this.deleteDevice(addr)
return
}
if curMultiId != multiId {
this.deleteDevice(addr)
return
}
}
}
}
总结
以 PC
作为客户端,手机作为服务端,PC
扫描连接手机的方式,由于网络问题,可能会出现各种情况,不能实时的判断手机的在线状态,可以通过其他的设计方式来规避这种技术问题,如:切换一下方式,PC
作为服务端,提供一个二维码(ip),来让手机进行扫码连接。