udp编程的那些事与golang udp的实践

udp编程的那些事与golang udp的实践

tcp/ip大协议中,tcp编程大家应该比较熟,应用的场景也很多,但是udp在现实中,应用也不少,而在大部分博文中,都很少对udp的编程进行研究,最近研究了一下udp编程,正好做个记录。
sheepbao 2017.06.15

tcp Vs udp

tcp和udp都是著名的传输协议,他们都是基于ip协议,都在OSI模型中传输层。tcp我们都很清楚,它提供了可靠的数据传输,而udp我们也知道,它不提供数据传输的可靠性,只是尽力传输。 他们的特性决定了它们很大的不同,tcp提供可靠性传输,有三次握手,4次分手,相当于具有逻辑上的连接,可以知道这个tcp连接的状态,所以我们都说tcp是面向连接的socket,而udp没有握手,没有分手,也不存在逻辑上的连接,所以我们也都说udp是非面向连接的socket。
我们都畏惧不知道状态的东西,所以即使tcp的协议比udp复杂很多,但对于系统应用层的编程来说,tcp编程其实比udp编程容易。而udp相对比较灵活,所以对于udp编程反而没那么容易,但其实掌握后udp编程也并不难。

udp协议

udp的首部

2               2       (byte)
+---+---+---+---+---+---+---+---+      -
|    src port   |    dst port   |      |
+---+---+---+---+---+---+---+---+      8(bytes)
|     length    |   check sum   |      |
+---+---+---+---+---+---+---+---+      -
|                               |
+              data             +      
|                               |
+---+---+---+---+---+---+---+---+

udp的首部真的很简单,头2个字节表示的是原端口,后2个字节表示的是目的端口,端口是系统层区分进程的标识。接着是udp长度,最后就是校验和,这个其实很重要,现在的系统都是默认开启udp校验和的,所以我们才能确保udp消息传输的完整性。如果这个校验和关闭了,那会让我们绝对会很忧伤,因为udp不仅不能保证数据一定到达,还不能保证即使数据到了,这个数据是否是正确的。比如:我在发送端发送了“hello”,而接收端却接收到了“hell”。如果真的是这样,我们就必须自己去校验数据的正确性。还好udp默认开发了校验,我们可以保证udp的数据完整性。

udp数据的封装

+---------+
                                    | 应用数据 |
                                    +---------+             
                                    |         |
                                    v         v
                          +---------+---------+
                          | udp首部  | 应用数据 |
                          +---------+---------+
                          |                   |
                          v     UDP数据报      v
                +---------+---------+---------+
                | ip首部   | udp首部  | 应用数据 |
                +---------+---------+---------+
                |                             |
                v           IP数据报           v
      +---------+---------+---------+---------+---------+
      |以太网首部 | ip首部   | udp首部 | 应用数据 |以太网尾部 |
      +---------+---------+---------+---------+---------+
      |   14        20         8                   4    |
      |                  -> 以太网帧 <-                  |

数据的封装和tcp是一样,应用层的数据加上udp首部,构成udp数据报,再加上ip首部构成ip数据报,最后加上以太网首部和尾部构成以太网帧,经过网卡发送出去。

Golang udp实践

实践出真知,编程就需要多实践,才能体会其中的奥妙。

echo客户端和服务端

echo服务,实现数据包的回显,这是很多人网络编程起点,因为这个服务足够简单,但又把网络的数据流都过了一遍,这里也用go udp实现一个echo服务。
实现客户端发送一个“hello”,服务端接收消息并原封不动的返回给客户度。

server.go

package main

import (
	"flag"
	"fmt"
	"log"
	"net"
)

var addr = flag.String("addr",":10000","udp server bing address")

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	flag.Parse()
}

func main() {
	//Resolving address
	udpAddr,err := net.ResolveUDPAddr("udp",*addr)
	if err != nil {
		log.Fatalln("Error: ",err)
	}

	// Build listining connections
	conn,err := net.ListenUDP("udp",udpAddr)
	if err != nil {
		log.Fatalln("Error: ",err)
	}
	defer conn.Close()

	// Interacting with one client at a time
	recvBuff := make([]byte,1024)
	for {
		log.Println("Ready to receive packets!")
		// Receiving a message
		rn,rmAddr,err := conn.ReadFromUDP(recvBuff)
		if err != nil {
			log.Println("Error:",err)
			return
		}

		fmt.Printf("<<< Packet received from: %s,data: %s\n",rmAddr.String(),string(recvBuff[:rn]))
		// Sending the same message back to current client
		_,err = conn.WriteToUDP(recvBuff[:rn],rmAddr)
		if err != nil {
			log.Println("Error:",err)
			return
		}
		fmt.Println(">>> Sent packet to: ",rmAddr.String())
	}
}

client1.go

package main

import (
	"flag"
	"fmt"
	"log"
	"net"
)

var raddr = flag.String("raddr","127.0.0.1:10000","remote server address")

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	flag.Parse()
}

func main() {
	// Resolving Address
	remoteAddr,*raddr)
	if err != nil {
		log.Fatalln("Error: ",err)
	}

	// Make a connection
	tmpAddr := &net.UDPAddr{
		IP:   net.ParseIP("127.0.0.1"),Port: 0,}

	conn,err := net.DialUDP("udp",tmpAddr,remoteAddr)
	// Exit if some error occured
	if err != nil {
		log.Fatalln("Error: ",err)
	}
	defer conn.Close()

	// write a message to server
	_,err = conn.Write([]byte("hello"))
	if err != nil {
		log.Println(err)
	} else {
		fmt.Println(">>> Packet sent to: ",*raddr)
	}

	// Receive response from server
	buf := make([]byte,1024)
	rn,err := conn.ReadFromUDP(buf)
	if err != nil {
		log.Println(err)
	} else {
		fmt.Printf("<<<  %d bytes received from: %v,rn,string(buf[:rn]))
	}
}

client2.go

package main

import (
	"flag"
	"fmt"
	"log"
	"net"
)

var (
	laddr = flag.String("laddr","127.0.0.1:9000","local server address")
	raddr = flag.String("raddr","remote server address")
)

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	flag.Parse()
}

func main() {
	// Resolving Address
	localAddr,*laddr)
	if err != nil {
		log.Fatalln("Error: ",err)
	}

	remoteAddr,err)
	}

	// Build listening connections
	conn,localAddr)
	// Exit if some error occured
	if err != nil {
		log.Fatalln("Error: ",err = conn.WriteToUDP([]byte("hello"),remoteAddr)
	if err != nil {
		log.Println(err)
	} else {
		fmt.Println(">>> Packet sent to: ",remAddr,string(buf[:rn]))
	}
}

这里实现echo的服务端和客户端,和tcp的差不多,但是有一些小细节需要注意。
对于server端,先net.ListenUDP建立udp一个监听,返回一个udp连接,这里需要注意udp不像tcp,建立tcp监听后返回的是一个Listener,然后阻塞等待接收一个新的连接,这样区别是因为udp一个非面向连接的协议,它没有会话管理。同时也因为udp是非面向连接的协议,当接收到消息后,想把消息返回给当前的客户端时,是不能像tcp一样,直接往conn里写的,而是需要指定远端地址。
对于client端,类似tcp先Dial,返回一个连接,对于发送消息用Write,接收消息用Read,当然udp也可以用ReadFromUDP,这样可以知道从哪得到的消息。但其实client也可以用另一种方式写,如client2.go程序,先建立一个监听,返回一个连接,用这个连接发送消息给服务端和从服务器接收消息,这种方式和tcp倒是有很大的不同。

参考

golang doc

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Golang的文档和社区资源:为什么它可以帮助开发人员快速上手?
Golang:AI 开发者的实用工具
Golang的标准库:为什么它可以大幅度提高开发效率?
Golang的部署和运维:如何将应用程序部署到生产环境中?
高性能AI开发:Golang的优势所在
本篇文章和大家了解一下go语言开发优雅得关闭协程的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1.简介本文将介绍首先为什么需要主...
这篇文章主要介绍了Go关闭goroutine协程的方法,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。1.简介本文将介绍首先为什么需要主动关闭gor...
本篇文章和大家了解一下go关闭GracefulShutdown服务的几种方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。目录Shutdown方法Regi...
这篇文章主要介绍了Go语言如何实现LRU算法的核心思想和实现过程,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。GO实现Redis的LRU例子常
今天小编给大家分享的是Go简单实现多租户数据库隔离的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会...
这篇“Linux系统中怎么安装NSQ的Go语言客户端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希
本文小编为大家详细介绍“怎么在Go语言中实现锁机制”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在Go语言中实现锁机制”文章能帮助大家解决疑惑,下面...
今天小编给大家分享一下Go语言中interface类型怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考
这篇文章主要介绍“怎么以正确的方式替换Go语言程序自身”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希
本文小编为大家详细介绍“Go语言中除法运算的效率怎么提高”,内容详细,步骤清晰,细节处理妥当,希望这篇“Go语言中除法运算的效率怎么提高”文章能帮助大家解...
本文小编为大家详细介绍“Go语言中的next()方法怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Go语言中的next()方法怎么使用”文章能帮助大家解决疑...
这篇文章主要介绍了Go语言中slice的反转方法怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Go语言中slice的反转方法怎...
这篇文章主要介绍“怎么使用Go语言实现数据转发功能”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“怎么使用Go语
这篇文章主要讲解了“Go语言中怎么实现代码跳转”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究
这篇文章主要讲解了“Go语言如何多开协程”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Go语言如何多开协...