首先我们先复习一下 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服务器:
服务器:
- 创建监听socket listener := net.Listen(“TCP”,“IP+port”) ---- 服务器自己的IP和port
- 启动监听 conn := listener.Accept() conn 用于通信的socket
- conn.Read(buf)
- 处理使用 数据
- conn.Write()
- 关闭 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]))
}
客户机:
- conn,err := net.Dial(“tcp”,服务器的IP+port)
- 写数据给 服务器 conn.Write()
- 读取服务器回发的 数据 conn.Read()
- 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) 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 并发客户端:
- 匿名go程,获取键盘输入,写给服务器
- for循环读取服务器回发数据
- (发送数据的时候,默认在结尾自带‘\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通信过程:
三次握手:
- 主动发起请求端,发送SYN
- 被动建立连接请求端,应答ACK 同时发送SYN
- 主动发起请求端,发送应答ACK
标志TCP三次握手建立完成。 – server:Accept() 返回 – client:Dial() 返回。
四次挥手:
- 主动关闭请求端,发送FIN
- 被动关闭连接请求端,应答ACK 。。 标志 :半关闭完成 ----- close
- 被动关闭连接请求端,发送FIN
- 主动关闭连接请求端,应答ACK 。。 标志:四次挥手完成 ----- close()
下面我们给出一个TCP的几种状态转换图:
查看状态命令:
Windows:netstat -an | findstr 8001(端口号)
Linux : netstat -apn | grep 8001
下面我们讲一下UDP的通信使用
udp服务器和tcp服务器创建方式的不同在于首先要创建一个server端地址结构,再对封装的地址结构进行操作
UDP服务器:
net.ResolveUDPAddr()udpConn = net.ListenUDP()ReadFromUDP()
下面给出一个简单的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)