网络编程分层概念:
层与协议
简单的例子:
package main
import (
"fmt"
"net"
)
func main() {
listenner, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("net.Listen err=", err)
return
}
defer listenner.Close()
//阻塞等待用户的连接
conn, err1 := listenner.Accept()
if err1 != nil {
fmt.Println("listenner.Accept err1=", err1)
return
}
defer conn.Close()
//接收客户端的数据
buf := make([]byte, 1024)
n, err2 := conn.Read(buf)
if n == 0 {
fmt.Println("Read err=", err2)
return
}
fmt.Printf("#%v#", string(buf[:n]))
}
TCP 服务器
package main
import (
"fmt"
"net" //网络操作需要net包
)
func main() {
//运行客户端
go clien()
//监听
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("err=", err)
return
}
defer listener.Close()
//阻塞等待用户链接
conn, err := listener.Accept()
if err != nil {
fmt.Println("err=", err)
return
}
//接收用户的请求
buf := make([]byte, 1024)
n, err1 := conn.Read(buf)
if err1 != nil {
fmt.Println("err=", err)
return
}
fmt.Println("服务器端收到的信息:buf=", string(buf[:n]))
defer conn.Close() //关闭当前用户连接
}
// netchat 参数nc 127.0.0.1 8000
// 之后打入内容回车即可
// TCP客户端
// 客户端
package main
import (
"fmt"
"net"
"os"
)
//多任务,读键盘内容,读服务器回复内容
func main() {
//主动连接服务器
conn, err := net.Dial("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("客户端:err=", err)
return
}
//main调用完毕,关闭客户端连接
defer conn.Close()
go func() { //子协程:从键盘输入内容,给服务器发送内容
str := make([]byte, 1024)
for {
//从键盘读取内容,放在str
n, err := os.Stdin.Read(str)
if err != nil {
fmt.Println("客户端:os.Stdin.Read err", err)
return
}
//把输入的内容给服务器发送
conn.Write(str[:n])
}
}()
//主协程,接收服务器回复的数据
//切片缓存
buf := make([]byte, 1024)
for {
//接收服务器的请求
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err", err)
return
}
//打印接收到的内容
addr := conn.RemoteAddr().String()
fmt.Printf("[%s]服务端回复数据: %s", addr, string(buf[:n]))
}
}
简单版并发服务器模型
服务端接收到客户端的数据后,将数据中所有小写字母处理为大写,再发送回客户端
package main
import (
"fmt"
"net" //网络操作需要net包
"strings"
)
//处理用户请求
func HandleConn(conn net.Conn) { //conn属于接口类型
//函数调用完毕自动关闭conn
defer conn.Close()
//获取客户端的网络地址信息
addr := conn.RemoteAddr().String()
fmt.Printf("客户端:[%s] 连接成功\n\n", addr)
for {
//读取用户数据
buf := make([]byte, 2048)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Printf("[%s]\n客户端发送数据: %s\n\n", addr, string(buf[:n]))
//通过nc命令发送过来的数据都带有换行,所以n-1 和在字符串"exit"后加"\n"就是为了杜绝此问题
if "exit" == string(buf[:n-2]) || "exit\r\n" == string(buf[:n]) || "exit" == string(buf[:n-1]) || "exit\n" == string(buf[:n]) || "exit" == string(buf[:n]) {
fmt.Printf("客户端:[%s]下线了\n", addr)
return
}
//把数据转换为大写,再给用户发送
conn.Write([]byte(strings.ToUpper(string(buf[:n]))))
} //防止处理一次就断开
}
func main() {
//运行客户端
go clien()
//监听
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("err=", err)
return
}
defer listener.Close()
//接收多个用户
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("err=", err)
return
}
//处理用户请求,每来一个用户就新建一个协程
go HandleConn(conn)
}
}
func clien() {
//主动连接服务器
conn, err := net.Dial("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("err=", err)
return
}
defer conn.Close()
//发送数据
conn.Write([]byte("exit"))
buf := make([]byte, 1024)
n, err1 := conn.Read(buf)
if err1 != nil {
fmt.Println("err=", err)
return
}
addr := conn.RemoteAddr().String()
fmt.Printf("[%s]\n服务端回复数据: %s\n", addr, string(buf[:n]))
}
文件的传输
1.文件的发送
//send.go
// 发文件
//获取文件属性
package main
import (
"fmt"
"io"
"net"
"os"
)
//发送文件内容
func SendFile(path string, conn net.Conn) {
//以只读方式打开文件
f, err := os.Open(path)
if err != nil {
fmt.Println("os.Open err=", err)
return
}
defer conn.Close()
defer f.Close()
//读文件内容,读多少,发多少
buf := make([]byte, 1024*4)
for {
n, err := f.Read(buf) //从文件读取内容
if err != nil {
if err == io.EOF {
fmt.Println("文件发送完毕")
} else {
fmt.Println("f.Read err=", err)
}
return
}
//发送内容
conn.Write(buf[:n]) //给服务器发送内容
}
}
func main() {
//提示输入文件
fmt.Println("请输入需要传输的文件:")
var path string
fmt.Scan(&path)
// list := os.Args
// if len(list) != 2 {
// fmt.Println("Useage: xxx file")
// return
// }
//获取文件名
//fileName := list[1]
//获取文件名字 info.Name()
info, err := os.Stat(path)
fmt.Println("name=", info.Name())
fmt.Println("size=", info.Size())
if err != nil {
fmt.Println("err=", err)
return
}
//主动连接服务器
conn, err1 := net.Dial("tcp", "127.0.0.1:8000")
if err1 != nil {
fmt.Println("net.Dial err1=", err1)
return
}
defer conn.Close()
//给接收方,先发送文件名
_, err = conn.Write([]byte(info.Name()))
if err != nil {
fmt.Println("coon.Write err=", err)
return
}
//接收对方的回复,如果回复ok,说明对方准备好了,可以发文件
var n int
buf := make([]byte, 1024)
n, err3 := conn.Read(buf)
if err3 != nil {
fmt.Println("conn.Read err3=", err3)
return
}
if "ok" == string(buf[:n]) {
//发送文件
SendFile(path, conn)
}
}
2.文件接收
// 收文件
//recv.go
package main
import (
"fmt"
"io"
"net"
"os"
)
func RecvFile(fileName string, conn net.Conn) {
f, err := os.Create(fileName)
if err != nil {
fmt.Println("os.Create err=", err)
return
}
buf := make([]byte, 1024*4)
//接收多少写多少,一点不差
for {
n, err1 := conn.Read(buf) //接收对方发过来的文件内容
if err1 != nil {
if err1 == io.EOF { //接收完成
fmt.Println("文件接收完毕")
//return
} else {
fmt.Println("conn.Read err1=", err1)
}
return
}
if n == 0 {
fmt.Println("文件接收完毕")
return
}
f.Write(buf[:n]) //往文件写入内容
}
}
func main() {
//监听
Listenner, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("net.Listen err", err)
return
}
defer Listenner.Close()
//阻塞等待用户连接
conn, err1 := Listenner.Accept()
if err1 != nil {
fmt.Println("Listener.Accept err1=", err1)
return
}
defer conn.Close()
buf := make([]byte, 1024)
n, err2 := conn.Read(buf)
if err2 != nil {
fmt.Println("conn.Read err2=", err2)
return
}
fileName := string(buf[:n]) //读取对方发送的文件名
conn.Write([]byte("ok")) //给对方回复ok
RecvFile(fileName, conn) //接收文件内容
}
实现群聊的效果
//并发聊天服务器.go
package main
import (
"fmt"
"net"
"strings"
"time"
)
type Client struct {
C chan string //用于发送数据的管道
Name string //用户名
Addr string //网络地址
}
//保存在线用户,cliAddr====》Client
var onlineMap map[string]Client
var message = make(chan string)
func Manager() {
//给map分配空间
onlineMap = make(map[string]Client)
for {
msg := <-message //没有消息前,这里会阻塞
//遍历map,给map每个成员发消息
for _, cli := range onlineMap {
cli.C <- msg
}
}
}
func WriteMsgToClient(cli Client, conn net.Conn) {
for msg := range cli.C {
//给当前客户端发送信息
conn.Write([]byte(msg + "\n"))
}
}
func MakeMsg(cli Client, msg string) (buf string) {
buf = "[" + cli.Addr + "]" + cli.Name + ": " + msg
return
}
func HandleConn(conn net.Conn) {
defer conn.Close()
//获取客户端的网络地址
cliAddr := conn.RemoteAddr().String()
//创建一个结构体
cli := Client{make(chan string), cliAddr, cliAddr}
//把结构体添加到map
onlineMap[cliAddr] = cli
//新开一个协程,专门给当前客户端发送信息
go WriteMsgToClient(cli, conn)
//广播某个人在线
//message<-"["+cli.Addr+"]"+cli.Name+": login"
message <- MakeMsg(cli, "login")
//提示,我是谁
cli.C <- MakeMsg(cli, "I am here")
isQuit := make(chan bool) //对方是否主动退出
hasData := make(chan bool) //对方是否有数据发送
//新开一个协程接收用户发送过来的数据
go func() {
for {
buf := make([]byte, 1024*2)
n, err := conn.Read(buf)
if n == 0 { //对方断开,或者出问题
isQuit <- true
fmt.Println("conn.Read err=", err)
return
}
msg := string(buf[:n-1]) //通过windows nc测试,多一个换行
if len(msg) == 3 && msg == "who" {
//遍历map,给当前用户发送所有成员
conn.Write([]byte("user list:\n"))
for _, tmp := range onlineMap {
msg := tmp.Addr + ":" + tmp.Name + "\n"
conn.Write([]byte(msg))
}
} else if len(msg) >= 8 && msg[:6] == "rename" {
//raname|mike
name := strings.Split(msg, "|")[1] //要第一个
cli.Name = name
onlineMap[cliAddr] = cli
conn.Write([]byte("rename success !!!\n"))
} else {
//转发此内容
message <- MakeMsg(cli, msg)
}
hasData <- true //代表有数据
}
}()
for {
//通过select检测channel的流动
select {
case <-isQuit:
delete(onlineMap, cli.Addr) //当前用户从map移除
message <- MakeMsg(cli, "login out") //广播谁下线了
case <-hasData:
case <-time.After(30 * time.Second): //60秒后超时了
delete(onlineMap, cliAddr)
message <- MakeMsg(cli, "time out leave out")
return
}
}
}
func main() {
//监听
listenner, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("net.Listen err=", err)
return
}
defer listenner.Close()
//新开一个协程,转发消息,只要有消息来了,就遍历map给map每一个成员都发送消息
go Manager()
//主协程,循环阻塞等待用户连接
for {
conn, err := listenner.Accept()
if err != nil {
fmt.Println("listenner.Accept err=", err)
continue
}
go HandleConn(conn) //处理用户连接
}
}
一张图解决HTTP编程
客户端发送请求包,包体等略过,太繁琐
package main
import(
"fmt"
"net"
)
func main(){
//主动连接服务器
conn,err:=net.Dial("tcp",":8000")
if err!=nil{
fmt.Println("dial err=",err)
return
}
defer conn.Close()
requestBuf:="
// .....
"
//先发请求包,服务器才会回响应包
conn.Write([]byte(requestBuf))
//接收服务器回复的响应包
n,err1:=conn.Read(buf)
if n==0 {
/*fmt.Println("Read err=1",err1)
return
}
fmt.Println("#%v#")
}
服务端解析请求包
package main
import (
"fmt"
"io"
"log"
"net/http"
)
//w,给客户端回复数据
//req,读取客户端发送的数据
func HelloConn(w http.ResponseWriter, req *http.Request) {
fmt.Println(req.Method) //获取用户请求的方法
fmt.Println(req.URL) //获取用户请求的地址
fmt.Println(req.Header) //获取用户请求的包头
fmt.Println(req.Body) //获取用户请求的方法
//包体是空的
io.WriteString(w, "hello go!\n") //给客户端回复数据
}
func main() {
//注册处理函数,用户连接,自动调用指定函数
http.HandleFunc("/go", HelloConn)
//第一个参数时网址/go/mike.html,什么都不写,只写/,客户端就不用写具体哪个地址
//监听绑定
err := http.ListenAndServe(":8000", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
//读取一些baidu.com的信息(请求包)
package main
import (
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("http://www.baidu.com") //必须写http://
if err != nil {
fmt.Println("http.Get err=", err)
return
}
defer resp.Body.Close()
fmt.Println("Status= ", resp.Status)
fmt.Println("StatusCode= ", resp.StatusCode)
fmt.Println("Header= ", resp.Header)
//fmt.Println("Body= ", resp.Body)
buf := make([]byte, 4*1024)
var tmp string
for {
n, err := resp.Body.Read(buf)
if n == 0 {
fmt.Println("read err=", err)
break
}
tmp += string(buf[:n])
}
fmt.Println(tmp)
}
并发编程
百度贴爬虫例子
package main
import (
"fmt"
"net/http"
"regexp"
"os"
"strconv"
)
爬取网页内容
func HttpGet(url string) (result string, err error) {
resp, err1 := http.Get(url)
if err1 != nil {
err = err1
return
}
defer resp.Body.Close()
//读取网页Boday内容
buf := make([]byte, 1024*4)
for {
n, err := resp.Body.Read(buf)
if n == 0 { //读取结束,或者出问题
fmt.Println("resp.Body err=", err)
break
}
result += string(buf[:n])
}
return
}
func SpiderPage(i int, page chan<- int) {
url := "https://tieba.baidu.com/f?kw=%E7%BB%9D%E5%9C%B0%E6%B1%82%E7%94%9F&ie=utf-8&pn=" + strconv.Itoa((i-1)*50)
fmt.Printf("正在爬%d个网页%s", i, url)
//2)爬(将所有的网站的内容全部爬下来)
result, err := HttpGet(url)
if err != nil {
fmt.Println("HttpGet err=", err)
return
}
//把内容写入到文件
fileName := strconv.Itoa(i) + ".html"
f, err1 := os.Create(fileName)
if err1 != nil {
fmt.Println("os.Create errr=", err1)
return
}
f.WriteString(result)
f.Close() //关闭当前文件
page <- i
}
//爬取一个网页
func DoWork(start, end int) {
fmt.Printf("正在爬取%d 到 %d 的页面", start, end)
page := make(chan int)
//明确目标,(要知道你准备在哪个范围或者网站去搜索)
for i := start; i <= end; i++ {
go SpiderPage(i, page)
}
for i := start; i < end; i++ {
fmt.Printf("第%d个页面爬取完成", <-page)
}
}
func main() {
var start, end int
fmt.Printf("请输入起始页( >=1):")
fmt.Scan(&start)
fmt.Printf("请输入终止页(>=起始页):")
fmt.Scan(&end)
DoWork(start, end)
}
捧腹段子爬虫例子
package main
import (
"fmt"
"net/http"
"os"
"regexp"
"strconv"
"strings"
)
func HttpGet(url string) (result string, err error) {
resp, err1 := http.Get(url) //发送get请求
if err != nil {
err = err1
return
}
defer resp.Body.Close()
//读取网页内容
buf := make([]byte, 1024*4)
for {
n, _ := resp.Body.Read(buf)
if n == 0 {
break
}
result += string(buf[:n]) //累加读取内容
}
return
}
//开始爬取每一个段子
func SpiderOneJoy(url string) (title, content string, err error) {
//开始爬取段子信息
result, err1 := HttpGet(url)
if err1 != nil {
//fmt.Println("HttpGet err = ", err1)
err = err1
return
}
//取关键信息
//取标题,标题以<h1>开头,以</h1>结尾,
re1 := regexp.MustCompile(`<h1>(?s:(.*?))</h1>`)
if re1 == nil {
//fmt.Println("regexp.MustCompile err ")
err = fmt.Errorf("%s", "regexp.MustCompile err")
return
}
//取内容
tmpTitle := re1.FindAllStringSubmatch(result, 1) //只过滤第一个
for _, data := range tmpTitle {
title = data[1]
//title = strings.Replace(title, "\r", "", -1)
//title = strings.Replace(title, "\n", "", -1)
//title = strings.Replace(title, " ", "", -1)
title = strings.Replace(title, "\t ", "", -1) //剔除干扰字符换成空字符
break
}
//取内容,内容以<div class="content-txt pt10">开头,以<a id="prev" href="结尾
re2 := regexp.MustCompile(`<div class="content-txt pt10">(?s:(.*?))<a id="prev" href="`)
if re2 == nil {
//fmt.Println("regexp.MustCompile err ")
err = fmt.Errorf("%s", "regexp.MustCompile err")
return
}
//取内容
tmpContent := re2.FindAllStringSubmatch(result, -1)
for _, data := range tmpContent {
content = data[1]
content = strings.Replace(content, "\t", "", -1)//去掉多余信息的测试
content = strings.Replace(content, "\n", "", -1)
content = strings.Replace(content, "\r", "", -1)
content = strings.Replace(content, "<br />", "", -1)
break
}
return
}
//把内容写入到文件
func StoreJoyToFile(i int, fileTitle []string, fileContent []string) {
//新建文件
f, err := os.Create(strconv.Itoa(i) + ".txt")
if err != nil {
fmt.Println("os.Create err = ", err)
return
}
defer f.Close()
//写内容
n := len(fileTitle)
for i := 0; i < n; i++ {
f.WriteString(fileTitle[i] + "\n")//写标题
f.WriteString(fileContent[i] + "\n") //写内容
f.WriteString("\n-----------------------------------\n")
}
}
func SpiderPape(i int, page chan int) {
//明确需要爬取的url
//https://www.pengfu.com/xiaohua_1.html
url := "https://www.pengfu.com/xiaohua_" + strconv.Itoa(i) + ".html"
fmt.Printf("正在爬取网页%d:%s\n", i, url)
//开始爬取主页的链接
result, err := HttpGet(url)
if err != nil {
fmt.Println("HttpGet err = ", err)
return
}
//fmt.Println(result)
//取url链接-正则表达式,以<h1 class="dp-b"><a href="开头,以"结尾
//解释表达式
re := regexp.MustCompile(`<h1 class="dp-b"><a href="(?s:(.*?))"`)
if re == nil {
fmt.Println("regexp.MustCompile err ")
return
}
//取关键信息
joyUrls := re.FindAllStringSubmatch(result, -1) //过滤全部
//fmt.Println(joyUrls)
fileTitle := make([]string, 0)
fileContent := make([]string, 0)
//取网址
//第一个返回下标,第二个返回内容
for _, data := range joyUrls {
//fmt.Println(data[1])
//开始爬取每一个段子
title, content, err := SpiderOneJoy(data[1])
if err != nil {
fmt.Println("SpiderOneJoy err = ", err)
continue
}
//fmt.Printf("title = #%v#", title)
//fmt.Printf("content = #%v#", content)
fileTitle = append(fileTitle, title) //追加内容
fileContent = append(fileContent, content) //追加内容
}
//fmt.Println("fileTitle = ", fileTitle)
//fmt.Println("fileContent = ", fileContent)
//把内容写入到文件
StoreJoyToFile(i, fileTitle, fileContent)
page <- i //向管道内写入内容num
}
func DoWork(start, end int) {
fmt.Printf("正在爬取 %d 到 %d 页面网址\n", start, end)
page := make(chan int)
for i := start; i <= end; i++ {
//定义函数爬主页面
go SpiderPape(i, page)
}
for i := start; i <= end; i++ {
fmt.Printf("%d页面爬取完成\n", <-page)
}
}
func main() {
var start, end int
fmt.Println("请输入起始页(>=1)")
fmt.Scan(&start)
fmt.Println("请输入起始页(>=起始页)")
fmt.Scan(&end)
DoWork(start, end)
}
(本笔记内容整理自网络资源,侵删)