Go语言学习笔记 (网络编程)

这篇博客主要介绍了Go语言在网络编程中的应用,包括TCP服务器的创建、简单的并发服务器模型、文件的发送与接收、HTTP编程的概览以及并发编程的示例,如通过爬虫实现百度贴吧和捧腹段子的数据抓取。
摘要由CSDN通过智能技术生成

网络编程分层概念:​

在这里插入图片描述
层与协议
在这里插入图片描述
在这里插入图片描述
简单的例子:

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) 
}

(本笔记内容整理自网络资源,侵删)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值