Go语言6-网络编程

Go语言标准库里提供的net包,支持基于IP层、TCP/UDP层及更高层面(如HTTP、FTP、SMTP)的网络操作,其中用于IP层的称为Raw Socket。

net.Dial()函数对TCP和UDP等协议的建立、绑定、监听socket作了抽象和封装,只需要把协议和地址作为参数传递给它,即可处理。Dial()返回的Conn接口,支持Io.Reader和io.Writer等。

一、TCP 服务端客户端

根据协议Echo Protocol RFC 862,使用GO编写一个简单Echo服务。

package main

import (
	"fmt"
	"net"
	"os"
	"io"
)

const (
	CONN_HOST = "localhost"
	CONN_PORT = "3333"
	CONN_TYPE = "tcp"
)

func main() {
	// 1. 监听地址
	l, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
	if err != nil {
		fmt.Println("Error listening:", err.Error())
		os.Exit(1)
	}
	defer l.Close()
	fmt.Println("Listening on " + CONN_HOST + ":" + CONN_PORT)
	for {
		// 2. 接受连接
		conn, err := l.Accept()
		if err != nil {
			fmt.Println("Error accepting: ", err.Error())
			os.Exit(1)
		}
		fmt.Printf("Received message %s -> %s \n", conn.RemoteAddr(), conn.LocalAddr())
		// 在一个Goroutine中处理conn
		go handleRequest(conn)
	}
}

func handleRequest(conn net.Conn) {
	defer conn.Close()
	for {
		io.Copy(conn, conn)
	}
}

启动main函数后,通过telnet 客户端即可访问:

telnet localhost 3333
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
hello server
hello server

如果是go编写客户端,我们希望通过两个goroutine,一个写数据到服务端,一个读取相应,如下通过sync完成:

package main

import (
	"net"
	"fmt"
	"os"
	"sync"
	"strconv"
	"bufio"
)
const (
	HOST = "localhost"
	PORT = "3333"
	TYPE = "tcp"
)

func main() {
	conn, err := net.Dial(TYPE, HOST+":"+PORT)
	if err != nil {
		fmt.Println("Error connecting:", err)
		os.Exit(1)
	}
	defer conn.Close()
	var wg sync.WaitGroup
	wg.Add(2)
	fmt.Println("Connecting to " + HOST + ":" + PORT)
	go writeData(conn, &wg)
	go getResponse(conn, &wg)
	wg.Wait()
}
func writeData(conn net.Conn, wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 10; i > 0; i-- {
		_, e := conn.Write([]byte("hello " + strconv.Itoa(i) + "\r\n"))
		if e != nil {
			fmt.Println("Error to send message because of ", e.Error())
			break
		}
	}
}
func getResponse(conn net.Conn, wg *sync.WaitGroup) {
	defer wg.Done()
	reader := bufio.NewReader(conn)
	for i := 1; i <= 10; i++ {
		line, err := reader.ReadString(byte('\n'))
		if err != nil {
			fmt.Print("Error to read message because of ", err)
			return
		}
		fmt.Print(line)
	}
}

当然,也可以使用channel。

二、UDP 服务端

服务端简单实现:

package main

import (
	"fmt"
	"net"
	"os"
)
func main() {
	// 1. 监听4444
	udp_addr, err := net.ResolveUDPAddr("udp", ":4444")
	checkError(err)
	//2. 创建监听连接
	conn, err := net.ListenUDP("udp", udp_addr)
	defer conn.Close()
	checkError(err)
	//3. 处理连接
	recvUDPMsg(conn)
}

func recvUDPMsg(conn *net.UDPConn) {
	data := make([]byte, 1024)
	n, addr, err := conn.ReadFromUDP(data)
	if err != nil {
		return
	}
	fmt.Println("udp msg: ", string(data[:n]))
	_, err = conn.WriteToUDP([]byte("nice to see u"), addr)
	checkError(err)
}

func checkError(err error) {
	if err != nil {
		fmt.Printf("Error:%s", err.Error())
		os.Exit(1)
	}
}

可以使用nc -vuz localhost 4444测试,代码实现也很简单:

conn, err := net.Dial("udp", "host:port")

引用列表:
* Go socket编程实践: TCP服务器和客户端实现
* go简易udp socket客户端和服务器
* 深入Go UDP编程
* 在 Go 语言中实现 UDP 和 TCP 网络通讯

CONTENTS