golang之tcp/udp连接+写聊天软件(笔记)

274 阅读3分钟

客户端和服务器

//socket: BSD UNIX的进程通信机制,通常也称为“套接字”。来描述IP地址和端口。任何协议,HTTP,https,底层封装的都是socketsocket可以理解成tcp和upd网络通信API,定义很多函数,程序员可以用这些函数开发TCP/IP网络应用程序

处理流程
监听端口,mysql服务启动后开始监听3306端口
接收客户端的连接请求
处理连接,
当一个客户发送请求过来,我们要处理连接
所以我们要启动一个协程

客户端处理流程:
服务器不会主动连接客户端
客户端主动与服务器建立连接
向服务器接收或发送数据
之后关闭连接

服务端
启用socket
绑定端口
进入监听
等待客户端连接过来

客户端
启动socket
调用connect方法连接客户端

连接好后服务器写消息客户端来读
你一句我一句就搞起来了

TCP处理连接函数

tcp

服务器

package main

import (
	"fmt"
	"net"
)

//处理连接函数
func process(conn net.Conn) {
	//结束时候关闭conn
	defer conn.Close()
	//接收发来的内容
	for {
		//接收1兆数据
		var buf [1024]byte
		//读取数据
		n, err := conn.Read(buf[:])
		if err != nil {
			fmt.Println("read err:", err)
			return
		}
		//将数据转换成字符串
		str := string(buf[:n])
		fmt.Println("接收到客户端发来的数据内容为:", str)

		//conn.Write([]byte("消息已经收到"))
	}
}

func main() {
	//1,启动监听端口
	listener, err := net.Listen("tcp", "127.0.0.1:7777")
	if err != nil {
		fmt.Println("listen failed,err:", err)
		return
	}
	//方法退出时关闭连接,释放端口
	defer listener.Close()

	for {
		//检测一次连接
		conn, err := listener.Accept()
		//如果检测不到继续检测
		if err != nil {
			fmt.Println("accept failed,err:", err)
			continue
		}
		//启动一个协程处理客户端连接
		go process(conn)
	}
}

客户端

package main

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

//客户端
func main() {
	//连接服务器的服务。conn就是连接
	conn, err := net.Dial("tcp", "127.0.0.1:7777")
	if err != nil {
		fmt.Println("连接服务器失败,err:", err)
		return
	}
	//结束的时候把连接断开
	defer conn.Close()
	for {
		//在控制台输入信息
		reader := bufio.NewReader(os.Stdin)
		input, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("获取控制台消息失败")
			return
		}

		//给连接端发送一个字符串
		_, err = conn.Write([]byte(input))
		if err != nil {
			fmt.Println("发送消息失败")
			return
		}
	}

}

拨号,如果拨号不到就会报错

conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))

UDP服务器处理连接

package main

import (
	"fmt"
	"net"
)

func main() {
	//127.0.0.1:8888端口接收UDP请求
	listener, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.ParseIP("127.0.0.1"),
		Port: 8888,
	})
	if err != nil {
		fmt.Println("UDP server服务启动失败,err", err)
		return
	}
	defer listener.Close()

	for {
		//设置个方法,接收1024位
		var buf [1024]byte
		//n代表长度
		n, addr, err := listener.ReadFromUDP(buf[:])
		if err != nil {
			fmt.Println("接收数据失败,err:", err)
			return
		}
		fmt.Println("接收到的消息", addr, string(buf[:n]))

		//回复消息
		listener.WriteToUDP([]byte("hi"), addr)
		if err != nil {
			fmt.Println("回复失败,err:", err)
			return
		}
	}
}

客户端

package main

import (
	"fmt"
	"net"
)

func main() {
	//发送udp给127.0.0.1:8888
	conn, err := net.Dial("udp", "127.0.0.1:8888")
	if err != nil {
		fmt.Println("连接失败,err:", err)
		return
	}
	defer conn.Close()
	//给目标发一个hello udp
	n, err := conn.Write([]byte("hello udp"))
	if err != nil {
		fmt.Println("发送失败,err:", err)
		return
	}
	//接收消息,只接收1024字节
	var buf [1024]byte
	n, err = conn.Read(buf[:])
	if err != nil {
		fmt.Println("接收消息失败,err:", err)
		return
	}
	fmt.Println("收到消息:", string(buf[:n]))
}

写个聊天软件

package main

func main() {
	server := NewServer("0.0.0.0", 8888)
	server.Start()
}

package main

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

//项目整体
type Server struct {
	Ip   string
	Port int

	//存放在线用户列表
	OnlineMap map[string]*User

	//创建读写锁,防止登录时列表出错
	maplock sync.RWMutex

	//用于消息广播的chan
	Messsage chan string
}

//创建server的连接口

func NewServer(ip string, port int) *Server {
	Server := &Server{
		Ip:        ip,
		Port:      port,
		OnlineMap: make(map[string]*User),
		Messsage:  make(chan string),
	}
	return Server
}

//创建一个用于广播的方法,广播内容组装,并将组装好的内容存放入server的massagechan中
func (this *Server) BroadCast(user *User, msg string) {
	//放到群发消息里,就能触发群发规则了
	sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
	this.Messsage <- sendMsg
}

//群发规则
//监听message channnel中的广播消息,一旦有消息需要广播,要将消息广播给所有在线user
func (this *Server) ListenMessage() {
	for {
		msg := <-this.Messsage
		this.maplock.RLock()
		//对所有人的C里发送消息
		for _, cli := range this.OnlineMap {
			cli.C <- msg
		}
		this.maplock.RUnlock()

	}
}

//每个用户启动一个协程
func (this *Server) Handler(conn net.Conn) {
	//当前连接业务
	fmt.Println("建立连接成功", conn.RemoteAddr().String())
	//每一个客户端连接成功后都会被创建一个user对象
	user := NewUser(conn)
	this.maplock.Lock()
	//将用户存放如服务器的在线列表的数组中
	this.OnlineMap[user.Name] = user
	this.maplock.Unlock()

	//新用户上线广播
	this.BroadCast(user, "已上线")

	//接受客户端发送消息
	go func() {
		//设置上限1024,创建一个管道
		buf := make([]byte, 1024)
		for {
			//循环读取内容,会一直等待,断开时会读取0
			//读取后的信息会放到n里
			//buf表示读1024
			n, err := conn.Read(buf)
			if err != nil && err != io.EOF {
				fmt.Println("Conn Read err:", err)
				return
			}
			//如果用户字符串长度等于0就下线了
			if n == 0 {
				this.BroadCast(user, "下线了")
				return
			}
			//接收客户端发来的消息。buf[:n-1]表示吧多余的字符串删除
			msg := string(buf[:n-1])
			//将用户发送的消息广播给所有用户
			this.BroadCast(user, msg)
		}
	}()

}

//启动server方法
func (this *Server) Start() {
	//开始监听tcp连接
	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))
	if err != nil {
		fmt.Println("net.listen err:", err)
		return
	}
	//在结束的时候关闭连接
	defer listener.Close()

	//开启监听端口
	go this.ListenMessage()

	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener accept is err:", err)
			continue
		}
		//启动一个协程连接客户端
		go this.Handler(conn)
	}
}

package main

import "net"

//每个TCP请求,如果成功后就会创建一个User客户端
type User struct {
	Name string
	Addr string
	conn net.Conn
	C    chan string
}

//创建用户方法API
func NewUser(conn net.Conn) *User {
	//conn.RemoteAddr()目标地址,String()转换成字符串
	userAddr := conn.RemoteAddr().String()
	user := &User{
		Name: userAddr,
		Addr: userAddr,
		//自己的ip地址
		conn: conn,
		//消息队列
		C: make(chan string),
	}

	go user.ListenMessage()

	return user

}

//监听当前User C channel方法,一旦有消息,就发送给客户端
func (this *User) ListenMessage() {
	for {
		//如果C接收到信息则打印conn.Write([]byte(msg + "\n"))
		msg := <-this.C
		//打印到目标的终端上
		this.conn.Write([]byte(msg + "\n"))
	}
}

开始监听

开启服务器端口

listener, err := net.Listen("tcp", "127.0.0.1:8888")
服务器的地址和开放端口

等待别人接入

conn, err := listener.Accept()
等待直到有端口接入,conn记录的是端口

得到客户的IP和端口

userAddr := conn.RemoteAddr().String()
读取端口的IP和端口字符串

关闭监听

listener.Close()
listener是变量

对客户读写

conn.Write([]byte("字符串" + "\n"))
写给客户端

//从this.conn中读取,也就是从服务器读数据,并写到终端上
io.Copy(os.Stdout, this.conn)
//设置上限1024,创建一个管道
buf := make([]byte, 1024)
//读取客户终端信息,会一直等待,断开时会读取0
//读取后的信息会放到n里
//buf表示读1024,其他用空格补全
ok, err := conn.Read(buf)
//将空格都删除,如此得到读取的字符串
msg := string(buf[:ok-1])