目录:
一: TCP Server
- 监听端口(获取链接请求)
- 有链接请求创建套接字
- 对套接字进行处理
二: TCP Client
- 创建socket套接字
- 用户输入消息数据
- 将消息写入套接字
- 销毁socket
三: 抓包分析
四: 消息格式的重要性
一、TCP Server
func process(conn net.Conn) {
defer conn.Close() // 不关闭的话,服务端会处于CLOSE—WAIT状态,最后被当做异常发送R包(Reset)
for {
var buf [128]byte
n, err := conn.Read(buf[:])
if err != nil {
fmt.Println(err)
break
}
conn.Write([]byte(fmt.Sprintf("received data: %s", string(buf[:n]))))
}
time.Sleep(time.Second * 5) // 睡眠一定时间,触发四次挥手包(为了抓包数据更好看)。否则关闭太快会把Ack包和Fin包并一起发送
}
func main() {
// 1. 监听端口
listen, err := net.Listen("tcp", "0.0.0.0:4000")
if err != nil {
log.Fatal(err)
}
defer listen.Close()
for {
socket, err := listen.Accept() // 2. 接收请求返回套接字(阻塞)
if err != nil {
log.Fatal(err)
}
go process(socket) // 3.处理请求
}
}
二、TCP Client
func client() {
// 1. 创建socket进行连接
socket, err := net.Dial("tcp", "172.?.?.249:4000")
if err != nil {
log.Fatal("3000 ", err)
}
defer socket.Close()
// 2.用户命令行输入消息
userInput := bufio.NewReader(os.Stdin)
for {
// 3.一次数据以\n为止, 读到Q则关闭
input, _:= userInput.ReadString('\n')
if strings.TrimSpace(input) == "Q" {
break
}
// 4.往socket写入数据
if _, err := socket.Write([]byte(input)); err != nil {
fmt.Println("3002", err)
}
// 5.接收返回数据
var data [1024]byte
n, err := socket.Read(data[:])
if err != nil {
fmt.Println("3003", err)
}
fmt.Println(string(data[:n]))
}
}
func main() {
client()
fmt.Println("client close")
time.Sleep(time.Second * 30) // 若主进程关闭,其子进程不管有没释放连接都被强制关闭
}
三、抓包分析
TCP Server的 process函数的必须睡眠一定时间才能触发四次分手包,否则包太小,给网卡合并一起发出去了。
为甚么主动关闭方长时间处于FIN-WAIT2(服务端长时间为CLOSE-WAIT)?
若主动方离开FIN-WAIT2进入TIME-WAIT只需被动方发送FIN包。
所以被动方只发生了ACK包,没有发送FIN包(服务端没关闭连接)
四、消息格式的重要性
上面服务处理消息就是声明一个规格载体去接收数据,如果数据大于载体规格,就需要读两次以上。
所以当数据长度大于byte数组长度时,数据不完整,打印出来的数据也即不完整。
所有需要标明消息大小,以此知道接收多少字节才算完整的消息。