golang socket io复用(无需第三方库)

golang socket IO复用纯粹实现(epoll/poll)(无需引进第三方库)

首先回顾golang经典并发socket

import (
    "net"
    "fmt"
)

func main(){
    listener,err:=net.ListenTCP("tcp",&net.TCPAddr{Port: 9000})
    if err==nil{
        var(
                lang int
                con *net.TCPConn
        )
        for{
                con,err=listener.AccpetTCP()
                if err==nil{
                    go func(conn net.Conn){
                        buffer []byte=make([]byte,5<<10)
                        defer conn.Close()
                        for{
                            lang,err=conn.Read(buffer)
                            if err==nil{
                                fmt.Println(string(buffer[:lang]))
                            }else{
                                return                            
                            }
                        }
                        
                    }(con)
                }
        }
    }
}

这里可以看出来有两个很浪费的点:大量连接建立时,内存浪费大,这里示例虽然用的5kb,但实践时,你可能会用于二进制文件传输,你一个buffer不得至少小几十MB;还有的一些连接有”小问题“,连上了一直都没用的那种,这时不仅要占用协程,还霍霍着内存

此时此景,好大哥poll/epoll就跳出来了(为什么不直接是epoll,因为mac和bsd上面没有对epoll进行实现,poll和select是posix标准里的,所以如果目的平台不止linux,为了更好的可移植性,可以使用poll进行封装)

接下来实现ListenTCP

package net
/*
这里致敬golang 标准库中的net.ListenTCP
*/
import (
	"errors"
	"syscall"
)

type IP []byte
type TCPAddr struct {
	Port int
	IP   IP
}

func ListenTCP(network string, addr *TCPAddr) (int, error) {
	sid, _, errs := syscall.Syscall(syscall.SYS_SOCKET, syscall.AF_INET, syscall.SOCK_STREAM, 0)
	if len(errs.Error()) != 0 || errs.Error() == "0" {
		var add [4]byte
		if addr.IP == nil {
			add = [4]byte{0, 0, 0, 0}
		} else if len(addr.IP) != 4 {
			add = [4]byte(addr.IP)
		} else {
			syscall.Close(int(sid))
			return 0, errors.New("ip address is not correct")
		}
		err := syscall.Bind(int(sid), &syscall.SockaddrInet4{Port: addr.Port, Addr: add})
		if err == nil {
			err = syscall.Listen(int(sid), 0)
		}
		if err != nil {
			syscall.Close(int(sid))
		}
		return int(sid), err
	} else {
		return -1, errs
	}
}

接下来为主函数

package main

import (
	"fmt"
	"log"
	"os"
	"syscall"
	"tutorial/net" //导入我们上面自行实现的net
)

const (
	EPOLLSIZE  = 10 //epoll 队列的容量
	BUFFERSIZE = 1 << 20 
)

var errorlogger *log.Logger = log.New(os.Stderr, "[error]", log.Llongfile|log.Ltime)

func main() {
	tcpid, err := net.ListenTCP("tcp", &net.TCPAddr{Port: 9000})
	if err == nil {
		defer syscall.Close(tcpid)
		var eid int
		eid, err = syscall.EpollCreate(EPOLLSIZE)
		if err == nil {
			//将文件描述符加入epoll 队列,后面一有动静就会发送信号给前面,然后你wait就有了
			syscall.EpollCtl(eid, syscall.EPOLL_CTL_ADD, tcpid, &syscall.EpollEvent{Fd: int32(tcpid), Events: syscall.EPOLLIN})
			var (
				epoll_quene           []syscall.EpollEvent = make([]syscall.EpollEvent, EPOLLSIZE)
				lang, count, i, conid int
				buffer                []byte = make([]byte, BUFFERSIZE)
			)
			for {
				count, err = syscall.EpollWait(eid, epoll_quene, -1)
				if err == nil {
					for i = 0; i < count; i++ {
						switch epoll_quene[i].Fd {
						case int32(tcpid):
							//listener 有动静就是有连接accpet 上了
							conid, _, err := syscall.Accept(tcpid)
							if err == nil {
								syscall.EpollCtl(eid, syscall.EPOLL_CTL_ADD, conid, &syscall.EpollEvent{Fd: int32(conid), Events: syscall.EPOLLIN})
							} else {
								errorlogger.Println(err.Error())
							}
						default:
							//这种就是前面的加入的conid
							conid = int(epoll_quene[i].Fd)
							lang, err = syscall.Read(conid, buffer)
							if err == nil {
								fmt.Println(string(buffer[:lang]))
							} else { //通常错误为eof,意思是连接断开了,但是服务端这边一定得也关闭连接,否则此次连接状态会是close_wait!!!并且将其从epol队列中移除
								syscall.Close(conid)
								syscall.EpollCtl(eid, syscall.EPOLL_CTL_DEL, conid, nil)
							}
						}
					}
				} else {
					errorlogger.Println(err.Error())
				}
			}
		} else {
			errorlogger.Println(err.Error())
		}
	}
}

实现效果展示

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值