学习golang(1) 初探:写一个简单的TCP服务器

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 学习golang(1) 初探:写一个简单的TCP服务器

网络通信的方式有哪些


我们对于网络通信方式而言,大概可以分为以下三类方式


  • 客户端/服务器结构
  • P2P结构
  • 混合结构




客户端/服务器结构


客户端/服务器通信方式通常称为C/S(client/server)通信方式,它作为网络中最典型、最基本的通信结构。


在通信过程中,服务器先启动服务,分配套接字,由客户端连接服务器,进行通信,在此过程中,客户端为主动方,服务器为被动方。客户端和客户端不允许直接通信。


这里提一点,很多文章说的B/S通信,本质上也是C/S通信哈。



P2P结构

在P2P通信中,通信双方都是对等关系,也就是说,P2P结构中,通信双方既可以作为服务器,又可以作为客户端。


混合结构

顾名思义,混合结构就是既有服务器存在,又有对等网存在,不过一般是先和服务器建立连接后,进行获取其他客户端套接字,然后进行通信。


总结

我们虽然区分了三种网络通信方式,但是从另一点来看,如果想要提供服务器,那么就需要提前开启服务并且分配一个套接字,等待别的客户端通过这个套接字连接上来,所以说C/S结构才是最基本的通信方式。



编写一个简单的TCP服务器/客户端


服务器

image.png

image.png


如上所述,我们使用net.Listen("tcp", "0.0.0.0:8881")来监听所有网卡的8881端口,这就是套接字

我们使用 listen.Accept()该函数会等待一直到客户端连上服务器,若连接后,我们就得到了一条连接

conn.Read(buf)表示从连接中读取数据,并且写入我们新建的byte数组中

使用 conn.Write(append([]byte("hello juejin pdudo "), buf...))将数据写入该连接

最后使用 conn.Close()来关闭该连接。

写完我们可以运行起来,然后使用telnet 来简单测试一下

使用telnet 127.0.0.1 8881可以连接进我们新写好的服务器,我们输入字符hello juejin,服务器返回了我们hello juejin pdudo hello juejin

image.png

我们查看服务器日志

image.png



客户端

我们再写个客户端在看看

image.png


测试

我们先启动服务器

启动服务器后并启动客户度

image.png


客户端日志

image.png

由此,我们一个简单的服务器和客户端便写好了




什么是粘包


模拟粘包

我们常说,tcp是流,建立握手连接后,该TCP连接就像水一样,源源不断的通信,我们没办法区分哪儿是数据的头,哪儿是数据的尾,所以才会出现粘包的东西

那我们可以尝试一下粘包

我们修改一下客户端

image.png


我们只发送,不接受,看看服务器收到的日志

先启动服务器,然后启动客户端,我们得到的日志是这样的?

image.png

揉成一团来发了。本来我们发送的条数是5条,但是服务器接受到的就这一条,这就是粘包。



如何避免粘包


如上所述,我们出现粘包的原因是: 没有办法确定需要发送/需要接受数据的长度,那如果我们能够确认发送/接受的数据,不就可以避免粘包了么。


那我们可以在发送的数据前面,加一个数据,这个数据就是字符的长度,例如,

我们想发送一个字符串: "hello juejin pdudo",按照我们之前设想的,模拟出来应该是这样的


image.png

我们如何写入18这个数据呢? 这就引入了我们下面介绍的,什么是大端序和小端序




什么是大端序/小端序


早些时候,计算机 比 网络要出现的早,各自搞各自的,其中和字节顺序有关的是,如何存储这类垮字节的数据,出现了2个极端,一个认为最低有效字节在前,一种认为最高有效字节在前,所以出现了 大端序 和 小端序

我们将低字节放在前面的方法,我们称之为 小端法

反之,我们将高字节放在前面的方法,称之为 大端法


后面网络出现了,使得这2种计算机通信起来有问题,未必避免这种情况,那么就会规定一下该操作使用什么大端序或者小端序,例如我们之前看的dns协议,就是使用的大端序,所以我们需要拿到数据包后,解一下字节序。


具体什么意思呢?

我们来看下,假设我申请了一个 无符号32位整形数据 ,我们为其赋值为 8

那么它的字节图应该是这样的

image.png

我们按照大端和小端来看一下

小端序

image.png

大端序

image.png


为什么我们上面定义的简单服务器中发送字符为什么没有使用大端或者小端呢?

如前面所讲,大端序和小端序对应的是多个字节,而我们使用的字符是一个字节,不管怎么排,都没有问题。



解决粘包问题


我们了解了如何避免粘包,以及字节排列顺序,所以,我们实现一下如何避免粘包

我们规定有4个字节来存储字符的长度嘛

我们在golang中使用encoding/binary来实现相应的字节序

我们使用

// 读取前4个字节
recv_len := 0
for recv_len < 4 {
    n, err := conn.Read(headBuf[recv_len:])
    if err != nil {
        conn.Close()
        return
    }
    recv_len = recv_len + n
}


来确保读取这么长的字节

我们看看修改后的服务器

image.png

image.png



修改后的客户端

image.png

image.png



编译服务器 和 客户端并且执行

先开启服务器,然后再启动客户端

查看服务器日志

image.png




思考


我们网络通信的方式,不管是客户端/服务器、P2P 还是 混合 , 其本质还是 客户端/服务器 通信,只不过后两者,在前者基础上做了改变而已。


我们实现通信使用的是套接字,套接字就是 ip+ 端口

而后我们实验了最简单的TCP服务器,引出了“粘包”的问题。而粘包的问题本质是: TCP连接是流,无法判断数据开头和结尾,从而导致粘包,而解决粘包的方式是,我们发送数据的时候,先获取待发送数据的长度,然后将长度和数据一起发过去,接收数据的时候,先接收长度,然后根据长度,接收实际数据,这样就能避免粘包。



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
网络协议 Java API
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
66 2
|
2月前
|
存储 网络协议 Java
【网络】UDP和TCP之间的差别和回显服务器
【网络】UDP和TCP之间的差别和回显服务器
72 1
|
2月前
|
前端开发 Java
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
文章介绍了如何使用SpringBoot创建简单的后端服务器来处理HTTP请求,包括建立连接、编写Controller处理请求,并返回响应给前端或网址。
58 0
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
|
2月前
|
安全 Java Go
【Golang入门】简介与基本语法学习
Golang语言入门教程,介绍了Go语言的简介、基本语法、程序结构、变量和常量、控制结构、函数、并发编程、接口和类型、导入包、作用域以及错误处理等关键概念,为初学者提供了一个全面的学习起点。
32 0
|
2月前
|
网络协议 Python
Python创建一个TCP服务器
Python创建一个TCP服务器
22 0
|
4月前
|
网络协议 安全 Unix
6! 用Python脚本演示TCP 服务器与客户端通信过程!
6! 用Python脚本演示TCP 服务器与客户端通信过程!
|
3月前
|
网络协议 数据处理 C语言
利用C语言基于poll实现TCP回声服务器的多路复用模型
此代码仅为示例,展示了如何基于 `poll`实现多路复用的TCP回声服务器的基本框架。在实际应用中,你可能需要对其进行扩展或修改,以满足具体的需求。
93 0
|
4月前
|
网络协议 安全 架构师
详解 | 一台服务器最大能支持多少条TCP连接?
详解 | 一台服务器最大能支持多少条TCP连接?
|
3月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
136 4
Golang语言之管道channel快速入门篇
|
3月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
71 4
Golang语言文件操作快速入门篇