7、网络编程

网络编程

网络编程的本质是两个设备之间的数据交换,在计算机网络中,设备主要指计算机。数据传递本身没有多大的难度,不就是把一个设备中的数据发送给另外一个设备,然后接受另外一个设备反馈的数据。

现在的网络编程基本上都是基于请求/响应方式的,也就是一个设备发送请求数据给另外一个设备,然后接收这个设备的反馈。

在网络编程中,发起连接程序,也就是发送第一次请求的程序,被称作客户端(Client),等待其他程序连接的程序被称作服务器(Server)。客户端程序可以在需要的时候启动,而服务器为了能够时刻相应连接,则需要一直启动。

连接一旦建立以后,就客户端和服务器端就可以进行数据传递了,而且两者的身份是等价的。

端口

为了在一台设备上可以运行多个程序,人为的设计了端口(Port)的概念,类似的例子是公司内部的分机号码。

每个网络程序,无论是客户端还是服务器端,都对应一个或多个特定的端口号。由于 0-1024 之间多被操作系统占用,所以实际编程时一般采用1024 以后的端口号。

使用端口号,可以找到一台设备上唯一的一个程序。所以如果需要和某台计算机建立连接的话,只需要知道IP地址或域名即可,但是如果想和该台计算机上的某个程序交换数据的话,还必须知道该程序使用的端口号。

网络编程分类

Socket编程

Socket是应用层与TCP/IP协议族通信的中间软件抽象层。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket后面,对用户来说只需要调用Socket规定的相关函数,让Socket去组织符合指定的协议数据然后进行通信。

golang中标准库提供了net包,用来进行Socket编程

img

UDP

服务端

package main

import (
	"fmt"
	"net"
)

const (
	IP   = "127.0.0.1"
	PORT = 8080
)

func main() {
	// 设置监听地址
	addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", IP, PORT))
	if err != nil {
		fmt.Println(err)
	}
	// 开启udp监听
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		fmt.Println(err)
	}
	defer conn.Close()
	for {
		data := make([]byte, 1024)
		_, clientAddr, err := conn.ReadFromUDP(data)
		if err != nil {
			fmt.Println(err)
		}

		if clientAddr != nil {
			fmt.Printf("msg from client:%s,msg:%s", clientAddr.IP.String(), string(data))
			conn.WriteToUDP([]byte("accept ok"), clientAddr)
		}
	}
}

客户端

package main

import (
	"fmt"
	"net"
)

const (
	IP   = "127.0.0.1"
	PORT = 8080
)

func main() {
	// 连接udp server
	conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", IP, PORT))
	if err != nil {
		fmt.Println(err)
	}
	defer conn.Close()
	var msg string
	for {
		fmt.Scanln(&msg)
		if msg == "exit" {
			break
		}
		_, err = conn.Write([]byte(msg))
		if err != nil {
			fmt.Println(err)
		}
		data := make([]byte, 1024)
		_, err := conn.Read(data)
		if err != nil {
			fmt.Println(err)
		}
		fmt.Println(string(data))
	}
}

TCP

服务端

Go 语言TcpServer 教程的步骤可以总结为:定义通信的地址和端口、使用 Listen 函数监听 TCP 的地址和端口信息并得到连接信息、使用连接信息的 Accept 函数等待连接、每来一个连接得到一个连接,使用连接进行读写数据。

一个TCP服务端可以同时连接很多个客户端。因为Go语言中创建多个goroutine实现并发非常方便和高效,所以我们可以每建立一次链接就创建一个goroutine去处理。

package main

import (
	"fmt"
	"net"
)

const (
	SERVER_IP   = "127.0.0.1"
	SERVER_PORT = 8080
)

//处理tcp连接
func handler(conn net.Conn) {
	defer conn.Close()
	clientAddr := conn.RemoteAddr().String()
	fmt.Println("新客户端连接:", clientAddr)
	for {
		data := make([]byte, 1024)
		i, err := conn.Read(data)
		if err != nil {
			fmt.Println("客户端断开连接:", clientAddr)
			return
		}
		fmt.Println(string(data[:i]))
		conn.Write([]byte("服务器成功接收"))
	}
}
func main() {
	//开启监听
	li, err := net.Listen("tcp", fmt.Sprintf("%s:%d", SERVER_IP, SERVER_PORT))
	if err != nil {
		fmt.Println(err)
	}
	defer li.Close()
	for {
		//接收并建立链接
		conn, err := li.Accept()
		if err != nil {
			fmt.Println(err)
		}

		go handler(conn)
	}
}

客户端

package main

import (
	"fmt"
	"net"
)

const (
	CLIENT_IP   = "127.0.0.1"
	CLIENT_PORT = 8080
)

func main() {
	//连接服务端
	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", CLIENT_IP, CLIENT_PORT))
	if err != nil {
		fmt.Println(err)
	}
	defer conn.Close()
	for {
		var msg string
		fmt.Scanln(&msg)
		if msg == "exit" {
			break
		}
		_, err := conn.Write([]byte(msg))
		if err != nil {
			fmt.Println(err)
		}
		data := make([]byte, 1024)
		i, err := conn.Read(data)
		if err != nil {
			fmt.Println(err)
		}
		fmt.Println(string(data[:i]))
	}
}