当前位置:网站首页>UDP servers and clients in go
UDP servers and clients in go
2022-06-30 00:45:00 【51CTO】

from Golang Of net Package to send UDP Called when the message is sent Linux Kernel method .
Although in Golang see TCP Server implementations are common , But in UDP It is not common to see the same implementation in .
except UDP and TCP Beyond the many differences between , Use Go They feel very similar , In addition to the small details generated by each protocol detail .
If you feel something Golang UDP Knowledge is valuable , Please make sure you stick to it .
in addition , This article also introduces TCP and UDP stay Golang Potential differences in system calls used in the background , And some analysis of the kernel when calling these system calls .
Stay tuned !
The overview
As the goal of the blog post , The final implementation should look like a “ Echo channel ”, No matter what the client writes to the server , The server will echo .
As UDP agreement , It does not guarantee reliable delivery , The server may receive messages , It may also be that the client receives an echo from the server .
This process may be successful ( Or not ) complete .
Not connection oriented , The client will not be like TCP That's really “ Establishing a connection ”; Whenever a message arrives at the server , It won't “ Write the response back to the connection ”, It simply directs the message to the address where it was written .
Consider this , The process should be as follows :
TIME DESCRIPTION
t0 client and server exist
client server
10.0.0.1 10.0.0.2
t1 client sends a message to the server
client ------------> server
10.0.0.1 msg 10.0.0.2
(from:10.0.0.1)
(to: 10.0.0.2)
t2 server receives the message, then it takes
the address of the sender and then prepares
another message with the same contents and
then writes it back to the client
client <------------ server
10.0.0.1 msg2 10.0.0.2
(from:10.0.0.1)
(to: 10.0.0.2)
t3 client receives the message
client server
10.0.0.1 10.0.0.2
thxx!! :D :D
ps.: ports omitted for brevity
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
in other words , Let's see how this story is in Go In .
Besides , If you want to really understand , Please be sure to consider the following books :
- computer network : A top-down approach
- Unix Network programming , The first 1 volume : Socket network API( The first 3 edition )
- Linux Programming interface (API)
The first book is from the network stack ( application layer ) Start with the very advanced part of , Then go down to the bottom , Explain the details of the agreement —— If you need an excellent person to review network concepts without delving into implementation details , Look at this. !
The other two are more about Linux and Unix Of —— If you focus more on Implementation , It's worth it . I wish you a happy reading !
Use Go send out UDP Data packets
Start the entire implementation now ( Full of notes ), We can begin to describe it , Understand one by one , Until we can understand every interaction that happens behind the scenes .
// client wraps the whole functionality of a UDP client that sends
// a message and waits for a response coming back from the server
// that it initially targetted.
func client(ctx context.Context, address string, reader io.Reader) (err error) {
// Resolve the UDP address so that we can make use of DialUDP
// with an actual IP and port instead of a name (in case a
// hostname is specified).
raddr, err := net.ResolveUDPAddr("udp", address)
if err != nil {
return
}
// Although we're not in a connection-oriented transport,
// the act of `dialing` is analogous to the act of performing
// a `connect(2)` syscall for a socket of type SOCK_DGRAM:
// - it forces the underlying socket to only read and write
// to and from a specific remote address.
conn, err := net.DialUDP("udp", nil, raddr)
if err != nil {
return
}
// Closes the underlying file descriptor associated with the,
// socket so that it no longer refers to any file.
defer conn.Close()
doneChan := make(chan error, 1)
go func() {
// It is possible that this action blocks, although this
// should only occur in very resource-intensive situations:
// - when you've filled up the socket buffer and the OS
// can't dequeue the queue fast enough.
n, err := io.Copy(conn, reader)
if err != nil {
doneChan <- err
return
}
fmt.Printf("packet-written: bytes=%d\n", n)
buffer := make([]byte, maxBufferSize)
// Set a deadline for the ReadOperation so that we don't
// wait forever for a server that might not respond on
// a resonable amount of time.
deadline := time.Now().Add(*timeout)
err = conn.SetReadDeadline(deadline)
if err != nil {
doneChan <- err
return
}
nRead, addr, err := conn.ReadFrom(buffer)
if err != nil {
doneChan <- err
return
}
fmt.Printf("packet-received: bytes=%d from=%s\n",
nRead, addr.String())
doneChan <- nil
}()
select {
case <-ctx.Done():
fmt.Println("cancelled")
err = ctx.Err()
case err = <-doneChan:
}
return
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
With the client code , We can now describe it , Explore every nuance of it .
Address resolution
Before we start creating sockets and sending information to the server , The first thing that happens is to give the name ( Such as google.com) Into a group of IP Address ( Such as 8.8.8.8) Name resolution of ).
The way we do this in our code is to call net.ResolveUDPAddr, stay Unix Environment , It is always executed through the following stack DNS analysis :
(in a given goroutine ...)
>> 0 0x00000000004e5dc5 in net.(*Resolver).goLookupIPCNAMEOrder
at /usr/local/go/src/net/dnsclient_unix.go:553
>> 1 0x00000000004fbe69 in net.(*Resolver).lookupIP
at /usr/local/go/src/net/lookup_unix.go:101
2 0x0000000000514948 in net.(*Resolver).lookupIP-fm
at /usr/local/go/src/net/lookup.go:207
3 0x000000000050faca in net.glob..func1
at /usr/local/go/src/net/hook.go:19
>> 4 0x000000000051156c in net.(*Resolver).LookupIPAddr.func1
at /usr/local/go/src/net/lookup.go:221
5 0x00000000004d4f7c in internal/singleflight.(*Group).doCall
at /usr/local/go/src/internal/singleflight/singleflight.go:95
6 0x000000000045d9c1 in runtime.goexit
at /usr/local/go/src/runtime/asm_amd64.s:1333
(in another goroutine...)
0 0x0000000000431a74 in runtime.gopark
at /usr/local/go/src/runtime/proc.go:303
1 0x00000000004416dd in runtime.selectgo
at /usr/local/go/src/runtime/select.go:313
2 0x00000000004fa3f6 in net.(*Resolver).LookupIPAddr <<
at /usr/local/go/src/net/lookup.go:227
3 0x00000000004f6ae9 in net.(*Resolver).internetAddrList <<
at /usr/local/go/src/net/ipsock.go:279
4 0x000000000050807d in net.ResolveUDPAddr <<
at /usr/local/go/src/net/udpsock.go:82
5 0x000000000051e63b in main.main
at ./resolve.go:14
6 0x0000000000431695 in runtime.main
at /usr/local/go/src/runtime/proc.go:201
7 0x000000000045d9c1 in runtime.goexit
at /usr/local/go/src/runtime/asm_amd64.s:1333
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
If I remember correctly , The whole process is like this :
- It checks whether we provide IP Address or host name ; If it is a hostname , be
- According to the search order specified by the system , Find the host using the local parser ; then ,
- Finally, an actual DNS request , Request records for this domain ; then ,
- If all this succeeds , Search for IP Address list , And then according to RFC Sort ; This gives us the highest priority in the list IP.
Follow the stack trace above , You should be able to see for yourself “ Magic ” The source code that occurs ( This is an interesting thing !).
choice IP After the address , We can go on .
Be careful : This process is very important for TCP There is no difference .
《 computer network : A top-down approach 》 There is a book about DNS The highlight of . I really suggest you read it carefully for more information . I also wrote an article about using Go Start from scratch analysis A Recorded blog posts : Use Go Write from scratch DNS news .
TCP Dialing and UDP dial
For our UDP client , We didn't use TCP Common conventions Dial, Instead, a different approach is used :DialUDP.
The reason for this is that we can force the address type to be passed , And receive a dedicated connection :“ The specific type ”UDPConn Not universal Conn Interface .
Even though Dial and DialUDP It may sound the same ( Even when it comes to system calls used when talking to the kernel ), But they end up being very different in terms of network stack implementation .
for example , We can check that both methods are used at the bottom connect(2):
TCP
// TCP - performs an actual `connect` under the hood,
// trying to establish an actual connection with the
// other side.
net.Dial("tcp", "1.1.1.1:53")
// strace -f -e trace=network ./main
// [pid 4891] socket(
AF_INET,
-----> SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK,
IPPROTO_IP) = 3
// [pid 4891] connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("1.1.1.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
...
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
UDP
// UDP - calls `connect` just like TCP, but given that
// the arguments are different (it's not SOCK_STREAM),
// the semantics differ - it constrains the socket
// regarding to whom it might communicate with.
net.Dial("udp", "1.1.1.1:53")
// strace -f -e trace=network ./main
// [pid 5517] socket(
AF_INET,
-----> SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK,
IPPROTO_IP) = 3
// [pid 5517] connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("1.1.1.1")}, 16) = 0
...
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
Although they are almost the same , But from the documents we can see how they are semantically different , It depends on our configuration through connect(2) What happened before socket(2) How to call the socket created :
If the socket sockfd is of type SOCK_DGRAM, then addr is the address to which datagrams are sent by default, and the only address from which datagrams are received.
If socket sockfd The type of SOCK_DGRAM, be addr Is the default address for sending datagrams , It is also the only address to receive datagrams .
If the socket is of type SOCK_STREAM or SOCK_SEQ‐PACKET, this call attempts to make a connection to the socket that is bound to the address specified by addr.
If the socket is SOCK_STREAM or SOCK_SEQ-PACKET type , Then this call attempts to bind to addr A socket with a specified address establishes a connection .
Can we pass TCP Transmission verification Dial Method will perform the behavior of actually connecting to the other end ? Of course !
./tools/funccount -p $(pidof main) 'tcp_*'
Tracing 316 functions for "tcp_*"... Hit Ctrl-C to end.
^C
FUNC COUNT
tcp_small_queue_check.isra.28 1
tcp_current_mss 1
tcp_schedule_loss_probe 1
tcp_mss_to_mtu 1
tcp_write_queue_purge 1
tcp_write_xmit 1
tcp_select_initial_window 1
tcp_fastopen_defer_connect 1
tcp_mtup_init 1
tcp_v4_connect 1
tcp_v4_init_sock 1
tcp_rearm_rto.part.61 1
tcp_close 1
tcp_connect 1
tcp_send_fin 1
tcp_rearm_rto 1
tcp_tso_segs 1
tcp_event_new_data_sent 1
tcp_check_oom 1
tcp_clear_retrans 1
tcp_init_xmit_timers 1
tcp_init_sock 1
tcp_initialize_rcv_mss 1
tcp_assign_congestion_control 1
tcp_sync_mss 1
tcp_init_tso_segs 1
tcp_stream_memory_free 1
tcp_setsockopt 1
tcp_chrono_stop 2
tcp_rbtree_insert 2
tcp_set_state 2
tcp_established_options 2
tcp_transmit_skb 2
tcp_v4_send_check 2
tcp_rate_skb_sent 2
tcp_options_write 2
tcp_poll 2
tcp_release_cb 4
tcp_v4_md5_lookup 4
tcp_md5_do_lookup 4
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
however , stay UDP Under the circumstances , Theoretically , It is only responsible for marking the socket to read and write to the specified address .
Through us for TCP Perform the same process ( Take a closer look at the system call interface ), We can track DialUDP and Dial The underlying kernel methods used , See how they are different :
./tools/funccount -p $(pidof main) 'udp_*'
Tracing 57 functions for "udp_*"... Hit Ctrl-C to end.
^C
FUNC COUNT
udp_v4_rehash 1
udp_poll 1
udp_v4_get_port 1
udp_lib_close 1
udp_lib_lport_inuse 1
udp_init_sock 1
udp_lib_unhash 1
udp_lib_rehash 1
udp_lib_get_port 1
udp_destroy_sock 1
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
many …… much less .
If we go further , Try to explore what happens in each call , We can notice in TCP Under the circumstances connect(2) How to finally transfer data ( for example , Establish an execution handshake ):
And in the UDP Under the circumstances , Nothing is transmitted , Just some settings :
If you are not sure that the two are really different ( In a sense ,TCP Send packets to establish a connection , and UDP No, ), We can set some triggers in the network stack to tell us when packets flow :
# By creating a rule that will only match
# packets destined at `1.1.1.1` and that
# match a specific protocol, we're able
# to see what happens at the time that
# `connect(2)` happens with a given protocol
# or another.
iptables \
--table filter \
--insert OUTPUT \
--jump LOG \
--protocol udp \
--destination 1.1.1.1 \
--log-prefix="[UDP] "
iptables \
--table filter \
--insert OUTPUT \
--jump LOG \
--protocol tcp \
--destination 1.1.1.1 \
--log-prefix="[TCP] "
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
Now? , in the light of TCP Objectives and DialUDP Target operation Dial And compare the differences .
You should only see [TCP] journal :
If you are not familiar with it dmesg How it works inside , Please check out my other blog post - dmesg under the hood. By the way ,《 Linux Programming interface (API) 》 It's a book about socket And other related topics !
Write UDP“ Connect ”
Create and configure correctly for a specific address UDP After socket , We can now pass on time “ write in ” route - When we actually get some data and write it from net.DialUDP Received UDPConn Object time .
Only to a given UDP The sample program for the server to send a little data is as follows :
// Perform the address resolution and also
// specialize the socket to only be able
// to read and write to and from such
// resolved address.
conn, err := net.Dial("udp", *addr)
if err != nil {
panic(err)
}
defer conn.Close()
// Call the `Write()` method of the implementor
// of the `io.Writer` interface.
n, err = fmt.Fprintf(conn, "something")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
Whereas Dial Back to conn Realized io.Writer Interface , We can use similar fmt.Fprintf ( take io.Writer As its first parameter ) Things like that , Let it use the message call we passed to it Write().
If you don't know the interface and other Golang Concept , Please be sure to check Kernighan The book of :The Go Programming Language.
Yes , From and Dennis Ritchie Write together C Programming language people .
In the underlying ,UDPConn Realized io.Writer Interface Write() Method , It is conn The combination of ,conn It's a structure , It implements the most basic method for writing and reading a given file descriptor :
type conn struct {
fd *netFD
}
// Write implements the Conn Write method.
func (c *conn) Write(b []byte) (int, error) {
if !c.ok() {
return 0, syscall.EINVAL
}
n, err := c.fd.Write(b)
if err != nil {
err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
}
return n, err
}
// UDPConn is the implementation of the Conn
// and PacketConn interfaces for UDP network
// connections.
type UDPConn struct {
conn
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
边栏推荐
- DL:深度学习模型优化之模型训练技巧总结之适时自动调整学习率实现代码
- MySQL primary key constraint deletion
- 太卷了~ 八股文,面试最强王者!
- [mrctf2020]ezpop-1 | PHP serialization
- The SQL statement concat cannot find the result
- MySQL basics 1
- Le module twincat 3 el7211 contrôle les servocommandes baffle
- IDEA工具快捷键的使用
- ML:置信区间的简介(精密度/准确度/精确度的三者区别及其关系)、使用方法、案例应用之详细攻略
- Common settings in idea
猜你喜欢

Crmeb SMS for program configuration of knowledge payment system

如何在IDEA中自定义模板、快速生成完整的代码?

Citation of Dissertation

What is the essential difference between get and post requests?

间歇采样转发干扰

Interviewer: why does database connection consume resources? I can't even answer.. I was stunned!

Traffic, but no sales? 6 steps to increase website sales

月薪没到30K的程序员必须要背的面试八股,我先啃为敬!

传统微服务框架如何无缝过渡到服务网格 ASM

HDCP Paring
随机推荐
MySQL foundation 3
Too voluminous ~ eight part essay, the strongest king of interview!
Common interview questions for network workers: Telnet, TTL, router and switch
HDCP Paring
Which securities company is better and which platform is safer for stock speculation account opening
I / o initial et son fonctionnement de base
DataGridView上移 下移行
如何在IDEA中自定義模板、快速生成完整的代碼?
Botu V16 changes the model and firmware version of PLC
TwinCAT 3 EL7211模塊控制倍福伺服
Yunna | advantages of fixed assets system management, what are the characteristics of fixed assets management system
证券开户有优惠吗究竟网上开户是否安全么?
玉米地里的小鸟
网工常见面试题分享:Telnet、TTL、路由器与交换机
Yunna | fixed assets information system management, information-based fixed assets management
出门在外保护好自己
阿四的情绪波动
数据中台咋就从“小甜甜”变成了“牛夫人”?
MySQL基础篇1
股票网上开户及开户流程怎样?还有,在线开户安全么?