Golang 的主要设计目标之一就是面向大规模后端服务程序,网络通信这块是服务端程序必不可少也是至关重要的一部分.
网络编程有两种户
(1).TCP Socket 编程,是网络编程的主流。之所以叫TCP Socket 编程,是因为底层是基于 TCP/IP 协议的,比如:QQ聊天
(2).B/S结构的 http编程,我们使用浏览器去访问服务器时,使用的就是 http协议,而 http底层依旧是用TCP Socket 实现的, 比如:京东商城 【 这属于 go web 开发范畴 】
计算机间相互通讯必须要求网线,网卡,或者无线网卡
(1).网线
网线是连接计算机与计算机、计算机与其它网络设备的连接线。常用的网线有双绞线、同轴细缆和光纤.
使用同轴细缆进行网络连接比较简单,只要将每台计算机或网络设备串联起来就可以了,但是维护起来比较麻烦,如果某处出了故障,整个网络就有可能瘫痪。从网络中添加或者去掉一台计算机,就必须停止网络的工作,等到添加或去掉计算机后,才能继续使用。同时,这种方式必须采用总线型拓扑结构,这种结构现在很少使用.
双绞线是由四对外覆绝缘材料的互相绞叠的铜质导线组成,并包裹在一个绝缘外皮内。它可以减少杂波造成的干扰,并抑制电缆内信号的衰减。如果使用双绞线,我们可以方便地在网络中添加或去掉一台计算机而不必中断网络的工作,网络的维护也比较简单,如果某处网线出现故障,只会影响到该条双绞线连接的计算机或设备,并不会造成网络的瘫痪。但是使用双绞线就必须在网络中添加集线器或交换机,增加了网络的成本
(2).网卡,无线网卡
网卡是一块被设计用来允许计算机在计算机网络上进行通讯的计算机硬件。由于其拥有MAC地址,因此属于OSI模型的第1层和2层之间。它使得用户可以通过电缆或无线相互连接。
每一个网卡都有一个被称为MAC地址的独一无二的48位串行号,它被写在卡上的一块ROM中。在网络上的每一个计算机都必须拥有一个独一无二的MAC地址。
没有任何两块被生产出来的网卡拥有同样的地址。这是因为电气电子工程师协会(IEEE)负责为网络接口控制器(网卡)销售商分配唯一的MAC地址
无线网卡(英文名称:Wireless network interface controller,缩写为WNIC)是一种终端无线网络设备,它能够帮助计算机连接到无线网络上,例如WiFi或者蓝牙。换句话说无线网卡就是帮助你的电脑连接到无线网的一个装置,但是有了无线网卡也还需要一个可以连接的无线网络,因此就需要配合无线路由器或者无线 AP 使用
TCP/IP传输协议,即传输控制/网络协议,也叫作网络通讯协议。它是在网络的使用中的最基本的通信协议。TCP/IP传输协议对互联网中各部分进行通信的标准和方法进行了规定。并且,TCP/IP传输协议是保证网络数据信息及时、完整传输的两个重要的协议。TCP/IP传输协议是严格来说是一个四层的体系结构,应用层、传输层、网络层和数据链路层都包含其中。
TCP/IP协议是Internet最基本的协议,其中应用层的主要协议有Telnet、FTP、SMTP等,是用来接收来自传输层的数据或者按不同应用要求与方式将数据传输至传输层;传输层的主要协议有UDP、TCP,是使用者使用平台和计算机信息网内部数据结合的通道,可以实现数据传输与数据共享;网络层的主要协议有ICMP、IP、IGMP,主要负责网络中数据包的传送等;而网络访问层,也叫网络接口层或数据链路层,主要协议有ARP、RARP,主要功能是提供链路管理错误检测、对不同通信媒介有关信息细节问题进行有效处理等
每个Internet上的主机和路由器都有一个ip地址,包括网络号和主机号,ip地址有ipv4(32位)或者ipv6(64位),可以通过ipconfig或者ifconfig查看
(1).端口的介绍
这里的端口不是指物理意义上的端口,而是特指 TCP/IP 协议中的端口,是逻辑意义上的端口。
如果把 IP 地址比作一间房子,端口就是出入这间房子的门。真正的房子只有几个门 ,但是一个 IP 地址的端口可以有 65536 (即: 256 x 256 )个之多. 端口是通过端口号来标记的,端口号只有整数,范围是从0到 65535 ( 256x256 - 1 )
0号是保留端口
1 ~ 1024 是固定端口,又叫有名端口,即被某些程序固定使用,一般程序员不使用
7:echo 服务
21: ftp 使用
22:SSH远程登录协议
23:telnet 使用
25:smtp 服务使用
80:iis使用
1025 一 65535 是动态端端口,这些端口,程序员可以使用
(3).端口的使用注意事项
1).在计算机(尤其是做服务器)要尽可能的少开端口
2).一个端口只能被一个程序监听
3).如果使用netstat -an可以查看本机有哪些端口在监听
4).可以使用netstat -anb来查看监听端口的pid,在结合任务管理器关闭不安全的端口
(1).服务端的处理流程
1).监听端口8888
2).接收客户端的tcp链接,建立客户端和服务器端的链接
3).创建goroutine,处理该链接的请求(通常客户端会通过链接发送请求包)
(2).客户端的处理流程
1).建立与服务端的链接
2).发送请求数据[终端],接收服务器端返回的结果数据
3).关闭链接
简单示意图
服务端功能:
编写一个服务器端程序,在8888端口监听
可以和多个客户端创建链接
链接成功后,客户端可以发送数据,服务器端接受数据,并显示在终端上
先使用telnet 来测试,然后编写客户端程序来测试
服务端的代码如下:
package main
import (
"fmt"
"net" //做网络socket开发时,net包含需要的所有方法和函数
"io"
)
func process(conn net.Conn) {
//循环接收客户端发送的消息
defer conn.Close() //延时关闭conn
for {
//创建一个新的切片
buf := make([]byte, 1024)
fmt.Printf("服务器等待客户端%s发送信息\n", conn.RemoteAddr().String())
// conn.Read(buf)
//1.等待客户端通过conn发送信息
//2.如果客户端没有Write[发送],那么协程将会阻塞在这里
n, err := conn.Read(buf) // 从conn读取
if err != nil {
fmt.Printf("服务器Read err = %v\n", err)
return
}
// if err == io.EOF {
// fmt.Println("远程客户端已退出")
// return
// }
//3.显示客户端发送的内容到服务器的终端
fmt.Print(string(buf[:n]))
}
}
func main() {
fmt.Println("服务器开始监听...")
//"tcp": 表示使用的网络协议是tcp
//"0.0.0.0:8888": 表示在本地进行监听端口8888
//127.0.0.1 => ipv4, 0.0.0.0 => ipv4/ipv6
listen, err := net.Listen("tcp", "0.0.0.0:8888")
if err != nil {
fmt.Printf("listen err, err = %v\n", err)
return
}
fmt.Printf("listen success, success = %v\n", listen)
defer listen.Close() // 延时关闭listen
//循环等待客户端来连接
for {
//等待客户端连接
fmt.Println("等待客户端连接...")
conn, err := listen.Accept()
if err != nil {
fmt.Printf("listen accept err, err=%v\n", err)
} else {
fmt.Printf("listen success, success=%v, client addr = %v\n", conn, conn.RemoteAddr().String())
}
//准备一个协程,为客户端服务
go process(conn)
}
}
客户端功能:
1.编写一个客户端端程序,能链接到服务器端的8888端口
2.客户端可以发送单行数据,然后就退出
3.能通过终端输入数据(输入一行发送一行),并发送给服务器端口
4.在终端输入exit,表示退出程序
package main
import (
"fmt"
"net" //做网络socket开发时,net包含需要的所有方法和函数
"bufio"
"os"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8888")
if err != nil {
fmt.Printf("dial err, err = %v\n", err)
return
}
fmt.Printf("dial success, success = %v\n", conn)
//功能一: 客户端发送单行数据,然后退出
reader := bufio.NewReader(os.Stdin) // os.Stdin 表示标准输入[终端]
//从终端读取一行用户输入,并发送给服务器
line, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("readString err = %v\n", err)
}
//将line发送给服务器
n, err := conn.Write([]byte(line))
if err != nil {
fmt.Printf("conn err =%v\n", err)
}
fmt.Printf("客户端发送了%d字节给服务器,并退出", n)
}
对客户端代码进行改进,如下:
package main
import (
"fmt"
"net" //做网络socket开发时,net包含需要的所有方法和函数
"bufio"
"os"
"strings"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8888")
if err != nil {
fmt.Printf("dial err, err = %v\n", err)
return
}
fmt.Printf("dial success, success = %v\n", conn)
//功能一: 客户端发送单行数据,然后退出
reader := bufio.NewReader(os.Stdin) // os.Stdin 表示标准输入[终端]
for {
//从终端读取一行用户输入,并发送给服务器
line, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("readString err = %v\n", err)
}
//如果输入的是exit,则退出
line = strings.Trim(line, "\r\n")
if line == "exit" {
fmt.Println("客户端退出")
break
}
//将line发送给服务器
n, err := conn.Write([]byte(line + "\n"))
if err != nil {
fmt.Printf("conn err =%v\n", err)
}
fmt.Printf("客户端发送了%d字节给服务器,并退出", n)
}
}
[上一节][go学习笔记.第十五章.反射,常量] 2.常量
[下一节]go学习笔记.第十六章.TCP编程] 2.项目-海量用户即时通讯系统