Go 网络编程

一、net库常用类型和函数

1.常用函数
Listen
Listen(network, laddr string) (Listener, error)
创建一个用于接受连接的监听器。
Dial
Dial(network, addr string) (Conn, error)
创建一个连接到指定地址的连接。
ListenTCP
ListenTCP(network, laddr *TCPAddr) (TCPListener, error)
创建一个 TCP 监听器。
DialTCP
DialTCP(network, laddr, raddr *TCPAddr) (TCPConn, error)
创建一个到指定 TCP 地址的连接。
LookupHost
LookupHost(host string) ([]string, error)
解析主机名到 IP 地址列表。
ResolveTCPAddr
ResolveTCPAddr(network, addr string) (*TCPAddr, error)
解析 TCP 地址字符串。
IP
IP
表示一个 IP 地址。
TCPAddr
TCPAddr
表示一个 TCP 地址。
UDPAddr
UDPAddr
表示一个 UDP 地址。


2.常用接口类型
Listener
Listener
接口类型,定义了 Accept 和 Close 方法。
Conn
Conn
接口类型,定义了 Read 和 Write 方法。
TCPConn
TCPConn
实现了 Conn 接口,并提供了 SetReadDeadline, SetWriteDeadline 等方法。
UDPConn
UDPConn
实现了 Conn 接口,并提供了 ReadFrom, WriteTo 等方法。

二、TCP编程

TCP(Transmission Control Protocol) 即传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层(Transport layer)通信协议。

服务端

简单实例,主要使用了net.Listen() 建立监听和监听返回的Listener接口进行接受和处理通信

package main

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

func Read(con net.Conn) {
	buf := make([]byte,1024)
	//1.从连接里面读取数据,使用read,将数据读取到buf中保存为byte类型,返回长度,然后使用sring将byte类型转化为字符串
	//2.也可以是使用bufio.newreader(con) ,新建一个从con中读取数据的reader,使用reader.read(buf)将数据读入buf中。也是返回 n长度。
	for {
		n,err := con.Read(buf)
		if err!=nil {
			fmt.Println("Read err : ",err)
		}
		
		msg := string(buf[:n-1])
		fmt.Println("客户端发来的消息:",msg)

	}
}



func Send(con net.Conn) {
	reader := bufio.NewReader(os.Stdin)
	//无限次的发送,如果不加for 就只能发一次
	for {
		input ,_ := reader.ReadString('\n')

		msg := strings.Trim(input,"\r\n")
	
		_,err := con.Write([]byte(msg+"\n"))//向连接中发送数据,使用write,只接受byte类型,要注意byte类型写入时需要换行。
		if err!= nil {
			fmt.Println("write err :", err)
		}

	}
}


func handel(con net.Conn) {
	defer con.Close()
	fmt.Println("Connect success!")

	
	//使用协程同时实现读写并行
	go Send(con)
	go Read(con)

	//使阻塞
	select {
		
	}
	


}

func main() {

	serverip := "127.0.0.1"
	serverport := 8888

	listener, err := net.Listen("tcp",fmt.Sprintf("%s:%d",serverip,serverport))

	if err!= nil {
		fmt.Println("listen err: ",err)
	}

	for {
		con , err := listener.Accept()
		if err!= nil {
			fmt.Println("accept err :", err)
		}

		go handel(con)
	}

}

客户端

就只有连接的区别

package main

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

func Read(con net.Conn) {
	buf := make([]byte,1024)
	//1.从连接里面读取数据,使用read,将数据读取到buf中保存为byte类型,返回长度,然后使用sring将byte类型转化为字符串
	//2.也可以是使用bufio.newreader(con) ,新建一个从con中读取数据的reader,使用reader.read(buf)将数据读入buf中。也是返回 n长度。
	for {
		n,err := con.Read(buf)
		if err!=nil {
			fmt.Println("Read err : ",err)
		}
		
		msg := string(buf[:n-1])
		fmt.Println("客户端发来的消息:",msg)

	}
}



func Send(con net.Conn) {
	//输入,建立一个从标准输入中读取数据的reader,然后使用reader.Readstring('\n')就可以读取输入的字符串,改字符串可包含空格
	reader := bufio.NewReader(os.Stdin)
	//无限次的发送,如果不加for 就只能发一次
	for {
		input ,_ := reader.ReadString('\n')

		msg := strings.Trim(input,"\r\n")
	
		_,err := con.Write([]byte(msg+"\n"))//向连接中发送数据,使用write,只接受byte类型,要注意byte类型写入时需要换行。
		if err!= nil {
			fmt.Println("write err :", err)
		}
	}
}
func main() {
	
	
	con ,err := net.Dial("tcp", "127.0.0.1:8888")
	defer con.Close()

	if err!=nil {
		fmt.Println("Connect err :",err)
	}
	go Read(con)
	go Send(con)
	

	select {
	}

	
}



三、UDP编程

UDP协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。

服务端

package main

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

func Send(udpcon *net.UDPConn,addr *net.UDPAddr){
	reader := bufio.NewReader(os.Stdin)
	for {
		input , err := reader.ReadString('\n')
		if err !=nil{
			fmt.Println("read err :",err)
		}
	
		msg := strings.Trim(input,"\r\n")
		_,er :=udpcon.WriteToUDP([]byte(msg+"\n"),addr)
		if er !=nil {
			fmt.Println("send write err :",er)
		}

	}


}

func Rec(udpcon net.UDPConn, c chan net.UDPAddr){
	buf :=make([]byte,1024)
	for {

		n,addr , err :=udpcon.ReadFromUDP(buf)
		if err!=nil {
			fmt.Println("recieve data err :",err)
			continue
		}
		c<-*addr
		
		ip:=addr.IP.To4().String()
		port := addr.Port
		msg := string(buf[:n-1])

		fmt.Printf("%s:%d 的信息: %v\n",ip,port,msg )

	}
	

}


func main() {
	// var addr net.UDPAddr
	//建立groutine通道,共享rec函数和send函数的参数adrr,不同go协程之间的参数传递。
	c := make(chan net.UDPAddr,1024)

	//建立udp连接监听
	Udpcon, err :=net.ListenUDP("udp",&net.UDPAddr{
		IP: net.IPv4(127,0,0,1),
		Port: 8888,
	})
	if err!= nil {
		fmt.Println("udp connect err :",err)
	}
	fmt.Println("connect success!")
	go Rec(*Udpcon,c)
	// 接收数据
	addr:= <-c
	go Send(Udpcon,&addr)


	select{

	}

}

客户端

package main

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

func Send(udpcon net.UDPConn){
	reader := bufio.NewReader(os.Stdin)
	for {
		input , err := reader.ReadString('\n')
		if err !=nil{
			fmt.Println("read err :",err)
		}
	
		msg := strings.Trim(input,"\r\n")
		_,er :=udpcon.Write([]byte(msg+"\n"))
		if er !=nil {
			fmt.Println("send write err :",er)
		}

	}


}

func Rec(udpcon net.UDPConn){
	buf :=make([]byte,1024)
	for {

		n,addr , err :=udpcon.ReadFromUDP(buf)
		if err!=nil {
			fmt.Println("recieve data err :",err)
			continue
		}
		
		ip:=addr.IP.To4().String()
		port := addr.Port
		msg := string(buf[:n-1])

		fmt.Printf("%s:%d 的信息: %v\n",ip,port,msg )
	}
	

}

func main() {
	raddr :=net.UDPAddr{
		IP:   net.IPv4(127, 0, 0, 1),
		Port: 8888,
	} 

	
	udpcon, err := net.DialUDP("udp",nil,&raddr)

	if err!=nil{
		fmt.Println("connect err :",err)
	}

	go Send(*udpcon)
	go Rec(*udpcon)

	select {
	
		
	}

}

四、websocket编程实例,聊天室

WebSocket是一种在单个TCP连接上进行全双工通信的协议
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
:ws.send()
在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输

user.go

package main

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/gorilla/websocket"
)

type User struct {
	ws *websocket.Conn
	msgChan chan []byte
	data *Data
}

var WS_UP = &websocket.Upgrader{
	ReadBufferSize:   512,
	WriteBufferSize:  512,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func Handel(w http.ResponseWriter, r *http.Request){
	ws , err := WS_UP.Upgrade(w,r,nil)
	if err!= nil {
		fmt.Println("websockt connect err: ",err)
		return
	}
	fmt.Println("用户连接成功!")
	//新建用户
	user := &User{
		ws: ws,
		msgChan: make(chan []byte,1024),
		data: &Data{},
	}
	fmt.Println("新用户的地址:",user.ws.RemoteAddr().String())

	//像服务器新用户注册通道发送新用户
	server.Newuser<-user
	fmt.Println("新用户注册成功")
	defer func ()  {
		user.data.Type = "logout"
		user_list = del(user_list,user.data.User)
		user.data.UserList = user_list
		user.data.Content = user.data.User
		data_b , _ := json.Marshal(user.data)
		server.MsgBoard <-data_b
		server.Offuser <- user
		fmt.Println("here??")
	}()
	go user.Write_to_wsCOn()
	user.Read_From_wsCon()

}

func (this *User) Write_to_wsCOn(){
	for {
		select {
		case msg_b :=<-this.msgChan:
			this.ws.WriteMessage(websocket.TextMessage,msg_b)
		}
	}

}

//用户发送消息到群聊
func (this *User) Read_From_wsCon(){
	for {
		//从连接中读取发送的消息
		_,msg_b, err:=this.ws.ReadMessage()
		if err!=nil {
			fmt.Println("从连接中读取发送的数据失败:",err)
			server.Offuser<-this
			break
		}
		
		//将信息的json格式反序列成data格式
		json.Unmarshal(msg_b,&this.data)
		fmt.Println("发送消息中的data和数据类型:",this.data,this.data.Type)
		switch this.data.Type {
			//刚登录的时候,用户没发送数据,初始化
		case "login":
			this.data.User = this.data.Content
			this.data.From = this.data.User
			user_list = append(user_list, this.data.User)
			this.data.UserList = user_list

			data_b ,_:= json.Marshal(this.data)
			fmt.Println("login之后:",this.data)
			server.MsgBoard<-data_b
		case "user":
			this.data.Type = "user"
			data_b ,_ := json.Marshal(this.data)
			server.MsgBoard<-data_b
		case "logout":
			this.data.Type = "loginout"
			user_list = del(user_list,this.data.User)
			data_b , _ :=json.Marshal(this.data)
			server.MsgBoard<-data_b
			server.Offuser<-this
		default:
			fmt.Println("发送数据中的用户类型:",this.data.Type)
		}

	}

}

func del(slice []string, user string) []string {
    count := len(slice)
    if count == 0 {
        return slice
    }
    if count == 1 && slice[0] == user {
        return []string{}
    }
    var n_slice = []string{}
    for i := range slice {
        if slice[i] == user && i == count {
            return slice[:count]
        } else if slice[i] == user {
            n_slice = append(slice[:i], slice[i+1:]...)
            break
        }
    }
    fmt.Println(n_slice)
    return n_slice
}

server.go

package main

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/gorilla/mux"
)

type Data struct {
	IP       string   `json:"ip"`
	User     string   `json:"user"`
	Type     string   `json:"type"`
	From     string   `json:"from"`
	Content  string   `json:"content"`
	UserList []string `json:"user_list"`
}
type Server struct {
	UserList map[*User]bool //存储用户列表
	MsgBoard chan []byte    // 广播消息通道
	Newuser  chan *User     // 新建用户通道
	Offuser  chan *User     //注销用户通道

}

func (this *Server) Start() {
	for {
		select {
			//如果有新用户
		case newuser:=<-this.Newuser:
			//新用户握手
			this.UserList[newuser] = true
			newuser.data.IP = newuser.ws.RemoteAddr().String()
			newuser.data.Type = "handshake"
			newuser.data.UserList = user_list
			fmt.Println("刚开始登录时:",user_list,newuser.data)
			//广播新用户上线
			data_b , _ := json.Marshal(newuser.data)
			//向用户进行发送消息,这是第一次与客户端交互。
			newuser.msgChan<-data_b
		// 如果有用户注销下线
		case offuser:=<-this.Offuser:
			//检查是否存在该用户
			_,ok := this.UserList[offuser]
			//从在线用户列表中删除,并断开连接
			if ok {
				delete(this.UserList,offuser)
				offuser.ws.Close()
			}
			fmt.Println("用户下线!")
		// 广播消息
		case msg_b:= <-this.MsgBoard:
			for user ,_ :=range this.UserList {
				//向用户发送消息
				user.msgChan<-msg_b
			}
			
		}
	}


}
var server = Server{
	UserList: make(map[*User]bool),
	MsgBoard: make(chan []byte),
	Newuser: make(chan *User),
	Offuser: make(chan *User),
}
var user_list []string
func main() {

	go server.Start()

	router := mux.NewRouter()
	
	router.HandleFunc("/chat",Handel)

	err :=http.ListenAndServe("127.0.0.1:8888",router)
	if err !=nil {
		fmt.Println("服务器启动失败:",err)
	}
	fmt.Println("服务器启动成功!")

}

login.html

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <style>
        p {
            text-align: left;
            padding-left: 20px;
        }
    </style>
</head>
<body>
<div style="width: 800px;height: 600px;margin: 30px auto;text-align: center">
    <h1>演示聊天室</h1>
    <div style="width: 800px;border: 1px solid gray;height: 300px;">
        <div style="width: 200px;height: 300px;float: left;text-align: left;">
            <p><span>当前在线:</span><span id="user_num">0</span></p>
            <div id="user_list" style="overflow: auto;">
            </div>
        </div>
        <div id="msg_list" style="width: 598px;border:  1px solid gray; height: 300px;overflow: scroll;float: left;">
        </div>
    </div>
    <br>
    <textarea id="msg_box" rows="6" cols="50" onkeydown="confirm(event)"></textarea><br>
    <input type="button" value="发送" onclick="send()">
</div>
</body>
</html>
<script type="text/javascript">
    var uname = prompt('请输入用户名', 'user' + uuid(8, 16));
    var ws = new WebSocket("ws://127.0.0.1:8888/chat");
    ws.onopen = function () {
        var data = "系统消息:建立连接成功";
        listMsg(data);
    };
    console.log('comhere')
    ws.onmessage = function (e) {
        console.log('Received:', e.data); 
        var msg = JSON.parse(e.data);
        var sender, user_name, name_list, change_type;
        switch (msg.type) {
            case 'system':
                sender = '系统消息: ';
                break;
            case 'user':
                sender = msg.from + ': ';
                break;
            case 'handshake':
                var user_info = {'type': 'login', 'content': uname};
                sendMsg(user_info);
                return;
            case 'login':
            case 'logout':
                user_name = msg.content;
                name_list = msg.user_list;
                change_type = msg.type;
                dealUser(user_name, change_type, name_list);
                return;
        }
        var data = sender + msg.content;
        listMsg(data);
    };
    ws.onerror = function () {
        var data = "系统消息 : 出错了,请退出重试.";
        listMsg(data);
    };
    function confirm(event) {
        var key_num = event.keyCode;
        if (13 == key_num) {
            send();
        } else {
            return false;
        }
    }
    function send() {
        var msg_box = document.getElementById("msg_box");
        var content = msg_box.value;
        var reg = new RegExp("\r\n", "g");
        content = content.replace(reg, "");
        var msg = {'content': content.trim(), 'type': 'user'};
        sendMsg(msg);
        msg_box.value = '';
    }
    function listMsg(data) {
        var msg_list = document.getElementById("msg_list");
        var msg = document.createElement("p");
        msg.innerHTML = data;
        msg_list.appendChild(msg);
        msg_list.scrollTop = msg_list.scrollHeight;
    }
    function dealUser(user_name, type, name_list) {
        var user_list = document.getElementById("user_list");
        var user_num = document.getElementById("user_num");
        while(user_list.hasChildNodes()) {
            user_list.removeChild(user_list.firstChild);
        }
        for (var index in name_list) {
            var user = document.createElement("p");
            user.innerHTML = name_list[index];
            user_list.appendChild(user);
        }
        user_num.innerHTML = name_list.length;
        user_list.scrollTop = user_list.scrollHeight;
        var change = type == 'login' ? '上线' : '下线';
        var data = '系统消息: ' + user_name + ' 已' + change;
        listMsg(data);
    }
    function sendMsg(msg) {
        var data = JSON.stringify(msg);
        ws.send(data);
    }
    function uuid(len, radix) {
        var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
        var uuid = [], i;
        radix = radix || chars.length;
        if (len) {
            for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
        } else {
            var r;
            uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
            uuid[14] = '4';
            for (i = 0; i < 36; i++) {
                if (!uuid[i]) {
                    r = 0 | Math.random() * 16;
                    uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
                }
            }
        }
        return uuid.join('');
    }
</script>
posted @   云岛夜川川  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
点击右上角即可分享
微信分享提示