GO网络编程(二):客户端与服务端通信【重要】

本节是新知识,偏应用,需要反复练习才能掌握。

目录

    • 1.C/S通信示意图
    • 2.服务端通信
    • 3.客户端通信
    • 4.通信测试
    • 5.进阶练习:客户端之间通信

1.C/S通信示意图

客户端与服务端通信的模式也称作C/S模式,流程图如下
在这里插入图片描述
其中P是协程调度器。可以看到,客户端都是通过一个共同的端口和服务端通信的,且服务端开了两个协程并发处理两个客户端。注意,协程和P之间,协程和端口之间,端口和客户端之间的链接都是双向的。

2.服务端通信

服务端功能要求如下:

(1)编写一个服务器端程序,在8888端口监听

fmt.Println("服务器开始监听....")
//net.Listen("tcp", "0.0.0.0:8888")
//1. tcp 表示使用网络协议是tcp
//2. 0.0.0.0:8888 表示在本地监听 8888端口
listen, err := net.Listen("tcp", "0.0.0.0:8888")

(2)可以和多个客户端创建链接

//循环等待客户端来链接我
for {
	//等待客户端链接
	fmt.Println("等待客户端来链接....")
	conn, err := listen.Accept()
	//这里准备其一个协程,为客户端服务
	go process(conn)
}

(3)链接成功后,客户端可以发送数据,服务器端接受数据,并显示在终端上

完整代码如下:

package main
import (
	"fmt"
	"net" //做网络socket开发时,net包含有我们需要所有的方法和函数
	_"io"
)

func process(conn net.Conn) {

	//这里我们循环的接收客户端发送的数据
	defer conn.Close() //关闭conn

	for {
		//创建一个新的切片
		buf := make([]byte, 1024)
		//conn.Read(buf)
		//1. 等待客户端通过conn发送信息
		//2. 如果客户端没有wrtie[发送],那么协程就阻塞在这里
		//fmt.Printf("服务器在等待客户端%s 发送信息\n", conn.RemoteAddr().String())
		n , err := conn.Read(buf) //从conn读取
		if err != nil {
			
			fmt.Printf("客户端退出 err=%v", err)
			return //!!!
		}
		//3. 显示客户端发送的内容到服务器的终端
		fmt.Print(string(buf[:n])) 
	}

}

func main() {

	fmt.Println("服务器开始监听....")
	//net.Listen("tcp", "0.0.0.0:8888")
	//1. tcp 表示使用网络协议是tcp
	//2. 0.0.0.0:8888 表示在本地监听 8888端口
	listen, err := net.Listen("tcp", "0.0.0.0:8888")
	if err != nil {
		fmt.Println("listen err=", err)
		return 
	}
	defer listen.Close() //延时关闭listen

	//循环等待客户端来链接我
	for {
		//等待客户端链接
		fmt.Println("等待客户端来链接....")
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("Accept() err=", err)
			
		} else {
			fmt.Printf("Accept() suc con=%v 客户端ip=%v\n", conn, conn.RemoteAddr().String())
		}
		//这里准备其一个协程,为客户端服务
		go process(conn)
	}
	
	//fmt.Printf("listen suc=%v\n", listen)
}

流程总结:
1.使用net.Listen()初始化监听端口,把初始信息存在变量listen中
2.循环使用listen.Accept(),接收监听到的基本信息,存在变量conn中
3.每次循环用conn.Read(buf)读取客户端发送的内容,将这些操作封装到一个协程中,并发执行

3.客户端通信

客户端功能:
(1)编写一个客户端程序,能链接到服务器端的8888端口

conn, err := net.Dial("tcp", "localhost:8888")

(2)客户端可以发送单行数据,然后就退出

reader := bufio.NewReader(os.Stdin) //os.Stdin 代表标准输入[终端]

(3)能通过终端输入数据(输入一行发送一行),并发送给服务器端

line, err := reader.ReadString('\n')
if err != nil {
	fmt.Println("readString err=", err)
}

其中ReadString(‘\n’)表示以换行符为截止符号,读取包括换行符在内的之前的字符,所以之后还需要去除line中的换行符。
(4)在终端输入exit,表示退出程序

	line = strings.Trim(line, " \r\n")
	if line == "exit" {
		fmt.Println("客户端退出..")
		break
	}

完整代码如下:

package main
import (
	"fmt"
	"net"
	"bufio"
	"os"
	"strings"
)

func main() {
	conn, err := net.Dial("tcp", "localhost:8888")
	if err != nil {
		fmt.Println("client dial err=", err)
		return 
	}
	//功能一:客户端可以发送单行数据,然后就退出
	reader := bufio.NewReader(os.Stdin) //os.Stdin 代表标准输入[终端]

	for {

		//从终端读取一行用户输入,并准备发送给服务器
		line, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("readString err=", err)
		}
		//如果用户输入的是 exit就退出
		line = strings.Trim(line, " \r\n")
		if line == "exit" {
			fmt.Println("客户端退出..")
			break
		}

		//再将line 发送给 服务器
		_, err = conn.Write([]byte(line + "\n"))
		if err != nil {
			fmt.Println("conn.Write err=", err)	
		}
	}
}

注:localhost是本地ip地址,默认ipv6。
流程总结:
1.使用net.Dial()建立与服务端的链接,把初始信息存在变量conn中
2.循环使用bufio.NewReader(os.Stdin)创建缓冲区,读入用户输入的信息并存在reader中
3.使用reader.ReadString('\n')提取每行字符
4.判断输入是否为exit,若是则退出循环
4.若输入步是exit,则用conn.Write()将每行内容发送到服务端

4.通信测试

先启动服务端,内容如下:

服务器开始监听....
等待客户端来链接....

注意,如果出现类似下图的提示框请点击允许
在这里插入图片描述

再打开当前目录的命令行,启动客户端,命令行会显示如下语句

Accept() suc con=&{{0xc00008a508}} 客户端ip=[::1]:端口号

注意:这个端口号不是8888,而是客户端用来连接服务器时的本地端口。每个客户端在连接到服务器时,操作系统会为它分配一个临时的随机端口
客户端输入几行语句

D:\code\golang\尚硅谷golang\代码\chapter18\tcpdemo\client>go run "client(1).go"
123
你好

服务端内容

服务器开始监听....
等待客户端来链接....
Accept() suc con=&{{0xc00008a508}} 客户端ip=[::1]:端口号
等待客户端来链接....
123
你好

客户端输入exit即可结束

客户端退出..

D:\code\golang\尚硅谷golang\代码\chapter18\tcpdemo\client>

服务端内容

客户端退出 err=read tcp [::1]:8888->[::1]:端口号: wsarecv: An existing connection was forcibly closed by the remote host.

注意,此时服务端仍在循环监听,按ctrl+c即可结束程序。

5.进阶练习:客户端之间通信

为了使两个客户端能够相互通信,需要一个中介服务器(通常称为“中转服务器”),它负责转发每个客户端的消息给另一个客户端。服务器在两个客户端之间保持连接,接收其中一个客户端的消息,然后将其转发给另一个客户端。
实现步骤:
服务器端:
1.服务器负责接收客户端 A 和客户端 B 的连接。
2.服务器读取每个客户端发送的消息,然后将消息转发给另一个客户端。

package main

import (
	"fmt"
	"net"
	"sync"
)

var (
	clients = make(map[net.Conn]bool) // 存储连接的客户端
	mu      sync.Mutex                // 保护 clients 的并发访问
)

func main() {
	listener, err := net.Listen("tcp", ":8888")
	if err != nil {
		fmt.Println("服务器启动错误:", err)
		return
	}
	defer listener.Close()

	fmt.Println("服务器正在监听8888端口...")

	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("接受连接时发生错误:", err)
			continue
		}

		// 在服务端提示有新的客户端连接
		fmt.Printf("新的客户端已连接: %v\n", conn.RemoteAddr())

		// 将客户端添加到客户端列表
		mu.Lock()
		clients[conn] = true
		mu.Unlock()

		go handleConnection(conn) // 处理每个连接
	}
}

func handleConnection(conn net.Conn) {
	defer func() {
		conn.Close()
		mu.Lock()
		delete(clients, conn) // 从列表中删除客户端
		mu.Unlock()
	}()

	buf := make([]byte, 1024)

	for {
		n, err := conn.Read(buf) // 读取客户端消息
		if err != nil {
			fmt.Printf("客户端 %v 断开连接: %v\n", conn.RemoteAddr(), err)
			return
		}

		message := string(buf[:n])

		// 判断是否是退出消息
		if message == "exit\n" || message == "exit" {
			fmt.Printf("客户端 %v 退出。\n", conn.RemoteAddr())
			return
		}

		// 转发消息给其他客户端
		mu.Lock()
		for client := range clients {
			if client != conn { // 不给发送者发送消息
				_, _ = client.Write(buf[:n]) // 将消息发送到其他客户端
			}
		}
		mu.Unlock()
	}
}

客户端:
1.客户端连接到服务器,通过服务器发送和接收消息。
2.客户端之间不会直接通信,而是通过服务器中转消息。

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)

func main() {
	// 使用 localhost 连接到服务器
	conn, err := net.Dial("tcp", "localhost:8888")
	if err != nil {
		fmt.Println("连接服务器失败:", err)
		return
	}
	defer conn.Close() // 确保在退出时关闭连接

	// 启动一个 goroutine 负责接收服务器的消息
	go func() {
		buf := make([]byte, 1024)
		for {
			n, err := conn.Read(buf) // 从服务器读取数据
			if err != nil {
				fmt.Println("与服务器断开连接:", err)
				return
			}
			// 显示来自服务器的消息
			fmt.Print("收到消息: ", string(buf[:n]))
		}
	}()

	// 主协程负责发送用户输入的消息
	reader := bufio.NewReader(os.Stdin)

	fmt.Println("连接到服务器,输入消息发送,输入 'exit' 退出...")

	for {
		// 从终端读取一行用户输入
		line, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("读取输入时发生错误:", err)
			continue
		}

		// 去掉输入行的换行符和空格go ru
		line = strings.TrimSpace(line)

		// 如果用户输入的是 exit,退出程序
		if line == "exit" {
			fmt.Println("客户端退出...")
			break
		}

		// 将用户输入发送给服务器
		_, err = conn.Write([]byte(line + "\n"))
		if err != nil {
			fmt.Println("发送消息时发生错误:", err)
		}
	}
}

效果截图
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/887734.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《CUDA编程》5.获得GPU加速的关键

从本章起&#xff0c;将关注CDUA程序的性能&#xff0c;即执行速度 1 用CUDA事件计时 在前几章中&#xff0c;使用的是C的<time.h>库进行程序运行计时&#xff0c;CUDA也提供了一种基于CUDA event的计时方式&#xff0c;用来给一段CUDA代码进行计时&#xff0c;这里只介…

系统架构设计师-下午案例题(2021年下半年)

1.试题一(共25分) 阅读以下关于软件架构设计与评估的叙述在答题纸上回答问题1和问题2。 【说明】某公司拟开发一套机器学习应用开发平台支持用户使用浏览器在线进行基于机器学习的智能应用开发活动。该平台的核心应用场景是用户通过拖拽算法组件灵活定义机器学习流程&#xf…

【含开题报告+文档+PPT+源码】基于SSM + Vue的养老院管理系统【包运行成功】

开题报告 随着社会的发展和经济的进步&#xff0c;人口老龄化问题逐渐凸显。统计数据显示&#xff0c;全球范围内的老龄人口比例正在逐年上升&#xff0c;养老需求也随之增长。养老院作为提供专业养老服务的机构&#xff0c;承担着照料老人、提供医疗保健和社交活动等责任。传…

什么是pip? -- Python 包管理工具

前言 不同的编程语言通常都有自己的包管理工具&#xff0c;这些工具旨在简化项目的依赖管理、构建过程和开发效率&#xff0c;同时促进代码的复用和共享。每个包管理工具都有其独特的特点和优势&#xff0c;开发者可以根据自己的编程语言和项目需求选择合适的包管理工具。 pip是…

车辆重识别(2021ICML改进的去噪扩散概率模型)论文阅读2024/9/29

所谓改进的去噪扩散概率模型主要改进在哪些方面&#xff1a; ①对数似然值的改进 通过对噪声的那个方差和T进行调参&#xff0c;来实现改进。 ②学习 这个参数也就是后验概率的方差。通过数据分析&#xff0c;发现在T非常大的情况下对样本质量几乎没有影响&#xff0c;也就是说…

TIM的PWM模式

定时器的工作流程: 定时器对时钟传来的脉冲次数计数&#xff0c;并且在次数到达范围值时触发中断。如向下计数模式时为0&#xff0c;向上计数为达到自动重装载计时器的值时触发中断。 四个输出比较单元 更改占空比的函数 STM32里面的定时器有多个定时器。 如TIM1、TIM2…

k8s 之安装metrics-server

作者&#xff1a;程序那点事儿 日期&#xff1a;2024/01/29 18:25 metrics-server可帮助我们查看pod的cpu和内存占用情况 kubectl top po nginx-deploy-56696fbb5-mzsgg # 报错&#xff0c;需要Metrics API 下载 Metrics 解决 wget https://github.com/kubernetes-sigs/metri…

nginx 负载均衡1

遇到的问题 大型网站都要面对庞大的用户量&#xff0c;高并发&#xff0c;海量数据等挑战。为了提升系统整体的性能&#xff0c;可以采用垂直扩展和水平扩展两种方式。 垂直扩展&#xff1a;在网站发展早期&#xff0c;可以从单机的角度通过增加硬件处理能力&#xff0c;比如 C…

LeetCode讲解篇之239. 滑动窗口最大值

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们维护一个长度为k的窗口&#xff0c;然后窗口从数组最左边一直移动到最右边&#xff0c;记录过程中窗口中的最大值&#xff0c;就是答案 我们每次查询长度为k的窗口最大值是什么时间复杂度是O(k)的&#xff0…

黑神话:仙童,数据库自动反射魔法棒

黑神话&#xff1a;仙童&#xff0c;数据库自动反射魔法棒 Golang 通用代码生成器仙童发布了最新版本电音仙女尝鲜版十一及其介绍视频&#xff0c;视频请见&#xff1a;https://www.bilibili.com/video/BV1ET4wecEBk/ 此视频介绍了使用最新版的仙童代码生成器&#xff0c;将 …

使用 Python 遍历文件夹

要解决这个问题&#xff0c;使用 Python 的标准库可以很好地完成。我们要做的是遍历目录树&#xff0c;找到所有的 text 文件&#xff0c;读取内容&#xff0c;处理空行和空格&#xff0c;并将处理后的内容合并到一个新的文件中。 整体思路&#xff1a; 遍历子目录&#xff1…

计算机毕业设计 基于Hadoop的智慧校园数据共享平台的设计与实现 Python 数据分析 可视化大屏 附源码 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

国外电商系统开发-运维系统拓扑布局

点击列表中设备字段&#xff0c;然后定位到【拓扑布局】中&#xff0c;可以看到拓扑发生了变化 再回头&#xff0c;您再次添加一个服务器到系统中&#xff0c;并且选择该服务器的连接节点为您刚才创建的“SDN路由器”&#xff0c;保存后&#xff0c;您可以看到这个服务器连接着…

RabbbitMQ篇(环境搭建 - 下载 安装)(持续更新迭代)

目录 一、Windows 1. 下载安装程序 2. 安装配置erlang 3. 安装rabbitMQ 4. 验证 二、Linux 1. 下载rpm包 1.1. 下载Erlang的rpm包 1.2. 下载socat的rpm包 1.3. 下载RabbitMQ的rpm包 2. 安装 2.1. 安装Erlang 2.2. 安装socat 2.3. 安装RabbitMQ 3. 启动RabbitMQ服…

小程序原生-利用setData()对不同类型的数据进行增删改

1. 声明和绑定数据 wxml文件 <view> {{school}} </view> <view>{{obj.name}}</view> <view id"{{id}}" > 绑定属性值 </view> <checkbox checked"{{isChecked}}"/> <!--算数运算--> <view>{{ id …

数理统计(第1章第2节:一些常用的抽样分布)

目录 统计量的概率分布称为“抽样分布” 1. 正态母体的子样平均数的抽样分布 正态分布 2. 卡方分布 3. t分布 4. F分布 5. 例题 6. 总结 统计量的概率分布称为“抽样分布” 1. 正态母体的子样平均数的抽样分布 正态分布 若随机变量X的概率密度为&#xff1a; 则称X服…

Qt开发技巧(九)去掉切换按钮,直接传样式文件,字体设置,QImage超强,巧用Qt的全局对象,信号槽断连,低量数据就用sqlite

继续讲一些Qt开发中的技巧操作&#xff1a; 1.去掉切换按钮 QTabWidget选项卡有个自动生成按钮切换选项卡的机制&#xff0c;有时候不想看到这个烦人的切换按钮&#xff0c;可以设置usesScrollButtons为假&#xff0c;其实QTabWidget的usesScrollButtons属性最终是应用到QTabWi…

重学SpringBoot3-集成Redis(三)之注解缓存策略设置

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Redis&#xff08;三&#xff09;之注解缓存策略设置 1. 引入 Redis 依赖2. 配置 RedisCacheManager 及自定义过期策略2.1 示例代码&#xff1a;自定…

Vue - 路由用法

前端路由就是URL中的hash与组件之间的对应关系。Vue Router是Vue的官方路由。 组成&#xff1a; VueRouter&#xff1a;路由器类&#xff0c;根据路由请求在路由视图中动态渲染选中的组件。<router-link>&#xff1a;请求链接组件&#xff0c;浏览器会解析成<a>。…

【易上手快捷开发新框架技术】nicegui组件button用法庖丁解牛深度解读源代码IDE运行和调试通过截图为证

传奇开心果微博文系列 前言一、button 组件基本用法1. 最基本用法示例2. 创建带图标按钮 二、button按钮组件样式定制1. 按钮的尺寸调整2. 改变颜色示例3. 按钮的自定义字体大小4. 圆角形状示例5. 自定义边框6. 添加阴影7. 复合按钮8. 浮动按钮9. 可扩展浮动操作按钮QFAB10. 按…