go网络编程

go网络编程

我自己写的思维导图
go网络编程主要是使用net包下面的东西
我参考下面的教程

1. 服务端定时发送消息给客户端(时间同步)

1.1 时序图

在这里插入图片描述

1.2 代码

  • 服务端
package main

import (
	"fmt"
	"log"
	"net"
	"time"
)

func main() {
	//监听端口
	port := ":7777"
	//构建一个基于tcp4的 端口为7777的监听地址对象
	tcpAddr, err := net.ResolveTCPAddr("tcp4", port)
	checkErr(err)
	//采用tcp连接
	listener, err := net.ListenTCP("tcp", tcpAddr)
	//for循环一个监听客户端的连接
	for {
		//接受客户端的连接,如果没有连接,就堵塞
		con, err := listener.Accept()
		checkErr(err)
		//输出客户端的地址
		log.Println("客户端连接成功:", con.RemoteAddr().String())
		handleConnection(con)
	}
}
//输出消息给客户端
func handleConnection(conn net.Conn) {
	for {
		//格式化当前时间,很抱歉,好像go格式化时间 不支持 YYYYMMdd 这样的写法
		format := time.Now().Format("2006/01/02 15:04:05")
		//输出到客户端
		fmt.Fprintln(conn, format)
		//线程睡眠一秒
		time.Sleep(time.Second * 1)
	}
}
func checkErr(err error) {
	if err != nil {
		log.Fatalln(err)
	}
}
  • 客户端
func startClient()  {
	log.Print("start client ....")
	//连接端口
	port := ":7777"
	//基于tcp4 端口为7777的地址对象
	tcpAddr, err := net.ResolveTCPAddr("tcp4", port)
	checkErr(err)
	// 基于tcp的连接,
	// 第一个参数  是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only)、TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个)
	// 第二个参数  表示本机地址,一般设置为nil
	// 第三个参数 表示远程的服务地址
	conn, err := net.DialTCP("tcp", nil, tcpAddr)
	//go 提供的流拷贝,将conn的值拷贝到stdout里面,标准输出,也就是控制台
	io.Copy(os.Stdout,conn)
}

还有一个客户端 可以用 telnet localhost 7777 来充当客户端来使用。
但是这样有一个很大的问题,连接不是并发的,不支持多个连接,。主线程一直是被第一个来的连接占用,只支持单个连接

1.3 时间同步支持多线程

在这里插入图片描述
只需要在这里增加一个go关键字就能支持多个客户端。这样就变成了下面的结构
在这里插入图片描述
即就是每一个客户端都有一个对应的线程来处理。

2. 客户端发送消息给服务端,服务端应答

2.1 时序图

在这里插入图片描述

2.2 代码

服务端基本还是之前那样,就多了一个字符串处理。
客户端多了一个从控制台输入

  • 服务端
//开启服务端
func startServer() {
	log.Print("start server ....")
	port := ":7777"
	tcpAddr, err := net.ResolveTCPAddr("tcp4", port)
	checkErr(err)
	listener, err := net.ListenTCP("tcp", tcpAddr)
	for {
		con, err := listener.Accept()
		checkErr(err)
		log.Println("客户端连接成功:", con.RemoteAddr().String())
		go handleConnection(con)
	}
}
func handleConnection(conn net.Conn) {
	//将 con 的数据读取到 buffer的数据,
	// 没有数据就一直堵塞。
	input := bufio.NewScanner(conn)
	// 不断读取 buffer的数据
	for input.Scan() {
		//将客户端的输入大写返回
		fmt.Fprintln(conn, "\t", strings.ToUpper(input.Text()))
		//睡眠1秒
		time.Sleep(time.Second * 1)
		// 原封不动的写出
		fmt.Fprintln(conn, "\t", input.Text())
		time.Sleep(time.Second * 1)
		//将客户端的输入小写返回
		fmt.Fprintln(conn, "\t", strings.ToLower(input.Text()))
	}
}
func checkErr(err error) {
	if err != nil {
		log.Fatalln(err)
	}
}
func main() {
//服务端开启
	go startServer()
	time.Sleep(time.Hour * 1)
}
  • 客户端
//开启客户端
func startClient() {
	log.Print("start client ....")
	port := ":7777"
	tcpAddr, err := net.ResolveTCPAddr("tcp4", port)
	checkErr(err)
	conn, err := net.DialTCP("tcp", nil, tcpAddr)
	//开启一个线程,输入读取到con,此线程要一直获取输入的值
	go io.Copy(conn,os.Stdin)
	//接受con的输出
	io.Copy(os.Stdout,conn)
}
func main() {
	go startClient()
	time.Sleep(time.Hour * 1)
}

3. 聊天室

主要分为客户端和服务端

  • 服务端
    定义不同的chan,用于监听不同的事件。
    • leaving。客户端离开
    • entering。客户端连接
    • messages 存放消息
  1. 主线程启动的时候,开启一个广播线程,该线程里面维护了一个所有连接的map。通过select 不停的监听不同的通道里面的消息。
  2. 主线程监听连接,连接建立,开启一个处理该connection 的线程。该线程里面 ,向客户端发送消息,并且接受客户端的输入。并且往不同的chan里面发送消息。
  • 客户端

3.1 时序图

在这里插入图片描述

3.2 代码

func startServer()  {
	log.Print("start server ....")
	port := ":7777"
	tcpAddr, err := net.ResolveTCPAddr("tcp4", port)
	checkErr(err)
	listener, err := net.ListenTCP("tcp", tcpAddr)
	//开启广播。
	go broadcasting()
	for {
		clientConnection, err := listener.Accept()
		checkErr(err)
		log.Println("客户端连接成功:", clientConnection.RemoteAddr().String())
		go handleConnection(clientConnection)
	}
}
func handleConnection(conn net.Conn) {
	ch := make(chan string) // 对外发送客户消息的通道
	go clientWriter(conn, ch)
	who := conn.RemoteAddr().String()
	ch <- "You are " + who           // 这条单发给自己
	messages <- who + " has arrived" // 这条进行进行广播,但是自己还没加到广播列表中
	cli := clientInfo{who, ch}
	entering <- cli // 然后把自己加到广播列表中

	done := make(chan struct{}, 2) // 等待下面两个 goroutine 其中一个执行完成。使用缓冲通道防止 goroutine 泄漏
	// 计算超时的goroutine
	inputSignal := make(chan struct{}) // 有任何输入,就发送一个信号
	timeout := 15 * time.Second        // 客户端空闲的超时时间
	go func() {
		timer := time.NewTimer(timeout)
		for {
			select {
			case <-inputSignal:
				timer.Reset(timeout)
			//定时器,时间到了之后给就会有消息
			case <-timer.C:
				// 超时,断开连接
				done <- struct{}{}
				return
			}
		}
	}()

	go func() {
		input := bufio.NewScanner(conn)
		for input.Scan() {
			inputSignal <- struct{}{}
			if len(strings.TrimSpace(input.Text())) == 0 { // 禁止发送纯空白字符
				continue
			}
			messages <- who + ": " + input.Text()
		}
		// 注意,忽略input.Err()中可能的错误
		done <- struct{}{}
	}()

	// 注意,忽略input.Err()中可能的错误
	<-done
	leaving <- cli
	messages <- who + " has left"
	conn.Close()
}
func clientWriter(conn net.Conn, ch <-chan string) {
	for msg := range ch {
		// 在消息结尾使用 \r\n ,提升平台兼容
		fmt.Fprintf(conn, "%s\r\n", msg) // 注意,忽略网络层面的错误
	}
}
func broadcasting() {
	//维护一个客户端的集合
	mapClients := make(map[clientInfo]bool)
	//通过select给所有的通道发消息
	for {
		select {
		case msg := <-messages:
			// 把所有接收的消息广播给所有的客户
			// 发送消息通道
			for cli := range mapClients {
				cli.ch <- msg
			}
		case cli := <-entering:
			// 在每一个新用户到来的时候,通知当前存在的用户
			var users []string
			for cli := range mapClients {
				users = append(users, cli.name)
			}
			if len(users) > 0 {
				cli.ch <- fmt.Sprintf("Other users in room: %s", strings.Join(users, "; "))
			} else {
				cli.ch <- "You are the only user in this room."
			}

			mapClients[cli] = true
		case cli := <-leaving:
			delete(mapClients, cli)
			close(cli.ch)
		}
	}
}
func checkErr(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值