go语言网络web编程基础 -- TCP/IP和UDP

2 篇文章 0 订阅
13 篇文章 0 订阅

首先我们先复习一下 TCP的内容,众所周知TCP为有链接的传输,而链接本质就是socket的链接 socket就是ip地址+端口号。
socket 编程
在网络通信过程中,socket一定是成对出现
通信过程:
1.mac地址(不需要用户指定) (ARP协议)Ip —> mac
2.IP地址 (需要用户指定) — 确定主机
3.port端口号(需要用户指定) — 确定程序
一、不能使用系统占用的默认端口。 5000+的端口供我们使用(8080)
二、65535为端口上限

网络应用设计模式分为以下两种:
c/s:优:数据传输效率高、协议选择灵活。缺:工作量打、安全性构成威胁。
b/s:优:开发工作较小、不受平台限制、安全威胁小 。 缺:缓存数据差、协议选择不灵活。

由于现代社会互联网企业蛀牙
TCP-CS服务器:

服务器:

  1. 创建监听socket listener := net.Listen(“TCP”,“IP+port”) ---- 服务器自己的IP和port
  2. 启动监听 conn := listener.Accept() conn 用于通信的socket
  3. conn.Read(buf)
  4. 处理使用 数据
  5. conn.Write()
  6. 关闭 listener
    下面为代码实例
func main01(){
	//指定服务器 通信协议、IP地址、port。创建一个用于监听的socket
	listen,err := net.Listen("tcp","127.0.0.1:8000")
	if err!= nil{
		fmt.Println("net.Listen err:", err)
		return
	}
	defer listen.Close()
	fmt.Println("服务器等待客户端建立链接。。。。")
	//阻塞监听客户端链接请求,成功建立连接,返回用于通信的socket
	conn,err := listen.Accept()
	if err!= nil{
		fmt.Println("listen.Accept() err:", err)
		return
	}
	defer conn.Close()
	fmt.Println("服务器和客户端成功建立链接!")
	//读取客户端发送的数据
	buf := make([]byte,4096)
	n,err:= conn.Read(buf)
	if err!=nil{
		fmt.Println("conn.Read() err:",err)
		return
	}

	//处理数据--打印
	fmt.Println("服务器读取到数据",string(buf[:n]))

}

客户机:

  1. conn,err := net.Dial(“tcp”,服务器的IP+port)
  2. 写数据给 服务器 conn.Write()
  3. 读取服务器回发的 数据 conn.Read()
  4. conn.Close()

下面为代码实例

func main02(){
	conn,err:=net.Dial("tcp","127.0.0.1:8000")
	if err!= nil{
		fmt.Println("net.Dial err:",err)
		return
	}
	defer conn.Close()

	//主动写数据给服务器
	conn.Write([]byte("Are you Ready?"))
	buf := make([]byte,4096)
	//接受服务器回发的数据
	n,err := conn.Read(buf)
	if err!= nil{
		fmt.Println("conn.Read err:",err)
		return
	}
	conn.Write(buf[:n])   //读多少写多少,原封不动
	//处理数据 -- 打印
	fmt.Println("服务器回发:",string(buf[:n]))
}

TCP-CS并发服务器:

  1. 创建 监听套接字 listener := net.Listen(“tcp”,服务器IP+port)
  2. defer listener.Close()
  3. for 循环 阻塞监听 客户端连接事件 conn:= listener.Accept()
  4. 创建 go程 对应每一个客户端进行数据通信 go HandlerConnet()
  5. 实现 HandlerConnet(conn net.Conn) 1) defer conn.Close 2) 获取成功链接的客户端Addr conn.RemoteAddr() 3)for 循环读取客户端发送的数据 conn.Read(buf) 4)处理数据 小写 --- 大写 5)回写转换后的数据 conn.Write(buf[:n])

服务器判断关闭:
Read读客户端 ,返回0 ------ 对端关闭!
nc 命令发送结束符号,结尾会默认带 ‘\n’ 。

下面给出一个并发服务器的例子源代码

import (
	"fmt"
	"net"
	"strings"
)
//所有对于socket的操作都在函数内
func HandlerConnect(conn net.Conn){
	defer conn.Close()
	//获取连接的客户端 Addr
	addr := conn.RemoteAddr()
	fmt.Println(addr,"客户端成功建立连接!")
	//循环读取客户端发出的数据
	buf := make([]byte,4096)
	for{
		n,err:=conn.Read(buf)
		if "exit\n" == string(buf[:n]) || "exit\r\n" == string(buf[:n]){
			fmt.Println("服务器接受到客户端退出请求,关闭")
			return
		}
		if n==0{
			fmt.Println("服务器检测到客户端已关闭,断开连接!!!")
			return
		}
		if err!= nil{
			fmt.Println("conn.Read err:",err)
			return
		}
		fmt.Println("服务器读取到的数据:",string(buf[:n]))    //使用数据
		//小写转大写,回发给客户端
		strings.ToUpper(string(buf[:n]))
		//小写转大写,回发给客户端
		conn.Write([]byte(strings.ToUpper(string(buf[:n]))))
	}
}
func main(){
	listener ,err := net.Listen("tcp","127.0.0.1:8001")
	if err!= nil{
		fmt.Println("net.Listen err :",err)
		return
	}
	defer listener.Close()

	//监听客户端连接请求
	for{
		conn,err := listener.Accept()
		if err!= nil{
			fmt.Println("listener.Accept err:",err)
			return
		}

		//具体完成服务器和客户端的数据通信
		go HandlerConnect(conn)
	}
}

TCP - CS 并发客户端:

  1. 匿名go程,获取键盘输入,写给服务器
  2. for循环读取服务器回发数据
  3. (发送数据的时候,默认在结尾自带‘\r\n’)

源码如下

import (
	"fmt"
	"net"
	"os"
)

func main()  {
	//主动发起连接请求
	conn,err := net.Dial("tcp","127.0.0.1:8001")
	if err!=nil{
		fmt.Println("net.Dial err:",err)
		return
	}
	defer conn.Close()
	//获取用户键盘输入(stdin),将输入数据发送费
	go func(){
		str := make([]byte,4096)
		for{
			n,err:=os.Stdin.Read(str)
			if err!=nil{
				fmt.Println("os.Stdin.Read err:",err)
				continue
			}
			//写给服务器,读多少,写多少
			conn.Write(str[:n])
		}
	}()
	//回显服务器回发的大写数据
	buf := make([]byte,4096)
	for{
		n,err:=conn.Read(buf)
		if n==0{
			fmt.Println("检查到服务器关闭,客户端也关闭")
			return
		}
		if err!= nil{
			fmt.Println("conn.Read err:",err)
			return
		}
		fmt.Println("客户端读到服务器回发:",string(buf[:n]))
	}
}

TCP通信过程:

三次握手:

  1. 主动发起请求端,发送SYN
  2. 被动建立连接请求端,应答ACK 同时发送SYN
  3. 主动发起请求端,发送应答ACK
    标志TCP三次握手建立完成。 – server:Accept() 返回 – client:Dial() 返回。

四次挥手:

  1. 主动关闭请求端,发送FIN
  2. 被动关闭连接请求端,应答ACK 。。 标志 :半关闭完成 ----- close
  3. 被动关闭连接请求端,发送FIN
  4. 主动关闭连接请求端,应答ACK 。。 标志:四次挥手完成 ----- close()

下面我们给出一个TCP的几种状态转换图:

在这里插入图片描述

查看状态命令:
Windows:netstat -an | findstr 8001(端口号)
Linux : netstat -apn | grep 8001

下面我们讲一下UDP的通信使用

udp服务器和tcp服务器创建方式的不同在于首先要创建一个server端地址结构,再对封装的地址结构进行操作
UDP服务器:

  1. 创建server端地址结构 (IP+port) net.ResolveUDPAddr()
  2. 创建用于通信的socket ,绑定地址结构 udpConn = net.ListenUDP()
  3. defer udpConn.close()
  4. 读取客户端发送数据 ReadFromUDP() 返回三个值,分别是读取到的字节数,客户端的地址(cltAddr = IP+port),error
  5. 写数据给客户端 WriteToUDP(“待写数据”,cltAddr)

下面给出一个简单的UDP服务器,当接受到信息的时候,会写回系统的当前时间

package main

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

func main(){
	//组织一个UDP结构,指定服务器的IP+port
	strAddr,err:=net.ResolveUDPAddr("udp","127.0.0.1:8003")
	if err !=nil{
		fmt.Println("ResolveUDPAddrerr :",err)
		return
	}
	fmt.Println("udp 服务器地址结构,创建完成!")
	//创建用通信的SOCKET
	udpConn,err :=net.ListenUDP("udp",strAddr)
	if err!= nil{
		fmt.Println("LIstenUDP err:",err)
		return
	}
	defer udpConn.Close()
	fmt.Println("udp 服务器通信socket创建完成!!!")
	//读取客户端发送的数据
	buf := make([]byte,4096)
	//返回三个值,分别是读取到的字节数,客户端的地址,error
	n,cltAddr,err:=udpConn.ReadFromUDP(buf)      //clientAddr
	if err!=nil{
		fmt.Println("ReadFromUDP err:",err)
		return
	}
	fmt.Printf("服务器读到 %v 的数据:%s \n",cltAddr,string(buf[:n]))

	//提取系统当前时间
	daytime := time.Now().String()
	//回写数据给客户端
	_ ,err =udpConn.WriteToUDP([]byte(daytime),cltAddr)
	if err!=nil{
		fmt.Println("WriteToUDP err:",err)
		return
	}


}

我们如果需要将udp服务器改为并发的,则需要如下一样

func main(){
	//组织一个UDP结构,指定服务器的IP+port
	strAddr,err:=net.ResolveUDPAddr("udp","127.0.0.1:8003")
	if err !=nil{
		fmt.Println("ResolveUDPAddrerr :",err)
		return
	}
	fmt.Println("udp 服务器地址结构,创建完成!")
	//创建用通信的SOCKET
	udpConn,err :=net.ListenUDP("udp",strAddr)
	if err!= nil{
		fmt.Println("LIstenUDP err:",err)
		return
	}
	defer udpConn.Close()
	fmt.Println("udp 服务器通信socket创建完成!!!")
	//读取客户端发送的数据
	buf := make([]byte,4096)

	//并发需要将读和写这件事循环
	for{
		//返回三个值,分别是读取到的字节数,客户端的地址,error
		n,cltAddr,err:=udpConn.ReadFromUDP(buf)      //clientAddr
		if err!=nil{
			fmt.Println("ReadFromUDP err:",err)
			return
		}
		fmt.Printf("服务器读到 %v 的数据:%s \n",cltAddr,string(buf[:n]))

		go func() {
			//提取系统当前时间
			daytime := time.Now().String()
			//回写数据给客户端
			_ ,err =udpConn.WriteToUDP([]byte(daytime),cltAddr)
			if err!=nil{
				fmt.Println("WriteToUDP err:",err)
				return
			}
		}()
	}
}

UDP客户端:
参考TCP,需要改动的是net.Dial
net.Dial(“udp”,server 的IP+port)

  • 2
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

赤狐先生

如果有一点点帮助,可以给点支持

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值