添加readv功能;给出测试数据;修订代码,注释;

toml 新增 app.noreadv项,命令行参数新增 -readv

默认readv是打开状态,可以选择性关闭。
This commit is contained in:
hahahrfool
2022-03-22 14:12:32 +08:00
parent e75860ef7a
commit 5661c8737f
18 changed files with 715 additions and 51 deletions

View File

@@ -246,7 +246,7 @@ verysimple -c server.toml
## 验证方式
对于功能的golang test请使用 `go test ./...` 命令。如果要详细的打印出test的过程可以添加 -v 参数
对于功能的golang test请使用 `go test ./... -count=1` 命令。如果要详细的打印出test的过程可以添加 -v 参数
## 关于证书

View File

@@ -7,6 +7,7 @@ import (
"path/filepath"
"github.com/hahahrfool/v2ray_simple/httpLayer"
"github.com/hahahrfool/v2ray_simple/netLayer"
"github.com/hahahrfool/v2ray_simple/proxy"
"github.com/hahahrfool/v2ray_simple/utils"
)
@@ -43,6 +44,9 @@ func loadConfig() {
if appConf := standardConf.App; appConf != nil {
utils.LogLevel = appConf.LogLevel
default_uuid = appConf.DefaultUUID
if appConf.NoReadV {
netLayer.UseReadv = false
}
}
return
} else {

17
main.go
View File

@@ -694,12 +694,13 @@ afterLocalServerHandshake:
}
if theFallbackFirstBuffer != nil {
//这里注意,因为是tls解密了之后的数据发送到目标地址所以这种方式只支持转发到本机纯http服务器
//这里注意,因为是tls解密了之后的数据发送到目标地址所以这种方式只支持转发到本机纯http服务器
wrc.Write(theFallbackFirstBuffer.Bytes())
utils.PutBytes(theFallbackFirstBuffer.Bytes()) //这个Buf不是从utils.GetBuf创建的而是从一个 GetBytes的[]byte 包装 的所以我们要PutBytes而不是PutBuf
}
if utils.CanLogDebug() {
/*
go func() {
n, e := io.Copy(wrc, wlc)
log.Println("本地->远程 转发结束", realTargetAddr.String(), n, e)
@@ -707,11 +708,21 @@ afterLocalServerHandshake:
n, e := io.Copy(wlc, wrc)
log.Println("远程->本地 转发结束", realTargetAddr.String(), n, e)
*/
go func() {
n, e := netLayer.TryCopy(wrc, wlc)
log.Println("本地->远程 转发结束", realTargetAddr.String(), n, e)
}()
n, e := netLayer.TryCopy(wlc, wrc)
log.Println("远程->本地 转发结束", realTargetAddr.String(), n, e)
} else {
//如果两个都是 *net.TCPConn或uds, 则Copy会自动进行splice/sendfile无需额外处理
go io.Copy(wrc, wlc)
io.Copy(wlc, wrc)
//go io.Copy(wrc, wlc)
//io.Copy(wlc, wrc)
netLayer.Relay(wlc, wrc)
}
}

View File

@@ -1,6 +1,7 @@
package netLayer
import (
"math/rand"
"net"
"net/url"
"strconv"
@@ -27,6 +28,10 @@ type Addr struct {
Network string
}
func GetRandLocalAddr() string {
return "0.0.0.0:" + strconv.Itoa(rand.Intn(60000)+4096)
}
func NewAddrFromUDPAddr(addr *net.UDPAddr) *Addr {
return &Addr{
IP: addr.IP,

108
netLayer/readv.go Normal file
View File

@@ -0,0 +1,108 @@
package netLayer
import (
"io"
"log"
"net"
"syscall"
"github.com/hahahrfool/v2ray_simple/utils"
)
func GetRawConn(reader io.Reader) syscall.RawConn {
if sc, ok := reader.(syscall.Conn); ok {
rawConn, err := sc.SyscallConn()
if err != nil {
if utils.CanLogDebug() {
log.Println("can't convert syscall.Conn to syscall.RawConn", reader, err)
}
return nil
} else {
return rawConn
}
}
return nil
}
// 用于读端实现了 readv但是写端的情况比如 从socks5读取 数据, 等裸协议的情况
// 小贴士:将该 net.Buffers 写入io.Writer的话只需使用 其WriteTo方法。
/*
使用方式
var readConn, writeConn net.Conn
//想办法初始化这两个Conn
rawConn:= netLayer.GetRawConn(readConn)
mr:=utils.GetReadVReader()
buffers,err:= netLayer.ReadFromMultiReader(rawConn,mr)
if err!=nil{
log.Fatal("sfdfdaf")
}
buffers.WriteTo(writeConn)
包装的代码见 TryCopy 函数; Relay函数也用到了
*/
func ReadFromMultiReader(rawReadConn syscall.RawConn, mr utils.MultiReader) (net.Buffers, error) {
//v2ray里还使用了动态分配的方式我们为了简便先啥也不做
bs := make([][]byte, 16)
// 实测16个buf已经完全够用平时也就偶尔遇到5个buf的情况, 极速测速时会占用更多;
// 16个1500那就是 24000, 23.4375 KB, 不算小了;
for i := range bs {
bs[i] = utils.GetMTU()
}
mr.Init(bs)
var nBytes int32
err := rawReadConn.Read(func(fd uintptr) bool {
n := mr.Read(fd)
if n < 0 {
return false
}
nBytes = n
return true
})
mr.Clear()
if err != nil {
ReleaseNetBuffers(bs)
return nil, err
}
if nBytes == 0 {
ReleaseNetBuffers(bs)
return nil, io.EOF
}
//删减buffer 到合适的长度并释放没用到的buf
nBuf := 0
for nBuf < len(bs) {
if nBytes <= 0 {
break
}
end := nBytes
if end > int32(utils.StandardBytesLength) {
end = int32(utils.StandardBytesLength)
}
bs[nBuf] = bs[nBuf][:end]
nBytes -= end
nBuf++
}
/*
if utils.CanLogDebug() {
// 可用于查看到底用了几个buf, 便于我们调整buf最大长度
log.Println("release buf", len(bs)-nBuf)
}
*/
ReleaseNetBuffers(bs[nBuf:])
return bs[:nBuf], nil
}
func ReleaseNetBuffers(mb [][]byte) {
for i := range mb {
utils.PutBytes(mb[i])
}
}

377
netLayer/readv_test.go Normal file
View File

@@ -0,0 +1,377 @@
package netLayer
import (
"bytes"
"crypto/rand"
"io"
"log"
"net"
"strings"
"testing"
"github.com/hahahrfool/v2ray_simple/utils"
)
/*
我们本地benchmark实际benchmark readv 是比 经典拷贝慢的
BenchmarkReadVCopy-8 426525 2934 ns/op
BenchmarkClassicCopy-8 531406 2185 ns/op
BenchmarkClassicCopy_SimulateRealWorld-8 60873 19631 ns/op
BenchmarkClassicCopy_SimulateRealWorld_ReadV-8 66138 17907 ns/op
我们添加一种情况 SimulateRealWorld, 分10次写入, 每次写入长度均小于MTU此时即可发现readv更快
总之这种本地benchmark 对于 readv来说意义不大因为本地回环太快了, readv只能徒增各种附加操作.
理论上来说在非本地测试环境下只要每次传输的包超过了MTU那么readv就应该是有优势的包长度越大越有优势因为越大越容易被割包那么readv就越好用
*/
//我们不断向一个net.Conn 发送大数据
func TestReadVCopy(t *testing.T) {
listenAddr := GetRandLocalAddr()
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
t.Log(err)
t.FailNow()
}
bigBytes := make([]byte, 10240) //10k
n, err := rand.Reader.Read(bigBytes)
if err != nil || n != 10240 {
t.Log(err)
t.FailNow()
}
transmitCount := 10
go func() {
conn, err := listener.Accept()
if err != nil {
//t.Log(err)
t.Fail()
return
}
for i := 0; i < transmitCount; i++ {
buf := &bytes.Buffer{}
_, err := TryCopyOnce(buf, conn)
if err != nil {
if strings.Contains(err.Error(), "close") {
t.Fail()
}
//t.Log(err)
return
}
}
}()
tcpConn, err := net.Dial("tcp", listenAddr)
if err != nil {
t.Log(err)
t.FailNow()
}
for i := 0; i < transmitCount; i++ {
_, e := tcpConn.Write(bigBytes)
if e != nil {
t.Log(err)
t.FailNow()
}
}
tcpConn.Close()
}
func BenchmarkReadVCopy(b *testing.B) {
transmitCount := b.N
listenAddr := GetRandLocalAddr()
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Fatalln(err)
}
bigBytes := make([]byte, 10240) //10k
n, err := rand.Reader.Read(bigBytes)
if err != nil || n != 10240 {
log.Fatalln(n, err)
}
go func() {
conn, err := listener.Accept()
if err != nil {
//b.Log(err)
b.Fail()
}
//buf := &bytes.Buffer{}
for i := 0; i < transmitCount; i++ {
buf := utils.GetBuf()
_, err := TryCopyOnce(buf, conn)
if err != nil {
if strings.Contains(err.Error(), "close") {
b.Fail()
}
}
utils.PutBuf(buf)
//buf.Reset()
}
}()
tcpConn, err := net.Dial("tcp", listenAddr)
if err != nil {
log.Fatalln(err)
}
for i := 0; i < transmitCount; i++ {
_, e := tcpConn.Write(bigBytes)
if e != nil {
log.Fatalln(err)
}
}
tcpConn.Close()
}
func BenchmarkClassicCopy(b *testing.B) {
b.StopTimer()
b.ResetTimer()
transmitCount := b.N
listenAddr := GetRandLocalAddr()
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Fatalln(err)
}
const bigBytesLen = 10240
bigBytes := make([]byte, bigBytesLen) //10k
n, err := rand.Reader.Read(bigBytes)
if err != nil || n != bigBytesLen {
log.Fatalln(n, err)
}
go func() {
conn, err := listener.Accept()
if err != nil {
//b.Log(err)
b.Fail()
}
//bs := make([]byte, bigBytesLen)
//buf := &bytes.Buffer{}
// 不能直接使用单一buf否则对readv来说不公平必须同样从pool中存取
for {
buf := utils.GetBuf()
//bs := utils.GetMTU()
bs := utils.GetPacket()
n, err := conn.Read(bs)
if err != nil {
if err != io.EOF && !strings.Contains(err.Error(), "close") {
b.Fail()
}
return
}
buf.Write(bs[:n])
utils.PutBuf(buf)
utils.PutPacket(bs)
//utils.PutBytes(bs)
//buf.Reset()
}
}()
tcpConn, err := net.Dial("tcp", listenAddr)
if err != nil {
log.Fatalln(err)
}
b.StartTimer()
for i := 0; i < transmitCount; i++ {
_, e := tcpConn.Write(bigBytes)
if e != nil {
log.Fatalln(err)
}
}
tcpConn.Close()
}
func BenchmarkClassicCopy_SimulateRealWorld(b *testing.B) {
b.StopTimer()
b.ResetTimer()
transmitCount := b.N
listenAddr := GetRandLocalAddr()
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
b.Log(err)
b.FailNow()
}
const bigBytesLen = 10240
bigBytes := make([]byte, bigBytesLen) //10k
n, err := rand.Reader.Read(bigBytes)
if err != nil || n != bigBytesLen {
b.Log(err)
b.FailNow()
}
go func() {
conn, err := listener.Accept()
if err != nil {
b.Log(err)
b.Fail()
return
}
//bs := make([]byte, bigBytesLen)
for {
//bs := utils.GetMTU()
bs := utils.GetPacket()
n, err := conn.Read(bs)
if err != nil {
if err != io.EOF && !strings.Contains(err.Error(), "close") {
b.Log(err)
b.Fail()
}
return
}
buf := utils.GetBuf()
buf.Write(bs[:n])
utils.PutBuf(buf)
utils.PutPacket(bs)
//utils.PutBytes(bs)
}
}()
tcpConn, err := net.Dial("tcp", listenAddr)
if err != nil {
b.Log(err)
b.FailNow()
}
b.StartTimer()
for i := 0; i < transmitCount; i++ {
unit := bigBytesLen / 10
for cursor := 0; cursor < bigBytesLen; cursor += unit {
_, e := tcpConn.Write(bigBytes[cursor : cursor+unit])
if e != nil {
//log.Fatalln(err)
b.Log(err)
b.FailNow()
}
}
}
tcpConn.Close()
}
func BenchmarkClassicCopy_SimulateRealWorld_ReadV(b *testing.B) {
b.StopTimer()
b.ResetTimer()
transmitCount := b.N
listenAddr := GetRandLocalAddr()
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
b.Log(err)
b.FailNow()
}
const bigBytesLen = 10240
bigBytes := make([]byte, bigBytesLen) //10k
n, err := rand.Reader.Read(bigBytes)
if err != nil || n != bigBytesLen {
b.Log(err)
b.FailNow()
}
go func() {
conn, err := listener.Accept()
if err != nil {
b.Log(err)
b.Fail()
return
}
for {
//buf := &bytes.Buffer{}
buf := utils.GetBuf()
_, err := TryCopyOnce(buf, conn)
if err != nil {
if err != io.EOF && strings.Contains(err.Error(), "close") {
b.Fail()
}
return
}
utils.PutBuf(buf)
}
}()
tcpConn, err := net.Dial("tcp", listenAddr)
if err != nil {
b.Log(err)
b.FailNow()
}
b.StartTimer()
for i := 0; i < transmitCount; i++ {
unit := bigBytesLen / 10
for cursor := 0; cursor < bigBytesLen; cursor += unit {
_, e := tcpConn.Write(bigBytes[cursor : cursor+unit])
if e != nil {
//log.Fatalln(err)
b.Log(err)
b.FailNow()
}
}
}
tcpConn.Close()
}

View File

@@ -1,14 +1,161 @@
package netLayer
import (
"flag"
"io"
"log"
"net"
"reflect"
"runtime"
"syscall"
"github.com/hahahrfool/v2ray_simple/utils"
)
const SystemCanSplice = runtime.GOARCH != "wasm" && runtime.GOOS != "windows"
var UseReadv bool
func init() {
flag.BoolVar(&UseReadv, "readv", true, "toggle the use of 'readv' syscall")
}
//这里认为能 splice 或 sendfile的 都算
func CanSplice(r interface{}) bool {
if _, ok := r.(*net.TCPConn); ok {
return true
} else if _, ok := r.(*net.UnixConn); ok {
return true
}
return false
}
// TryCopy 尝试 循环 从 readConn 读取数据并写入 writeConn, 直到错误发生。
//会接连尝试 splice、循环readv 以及 原始Copy方法
func TryCopy(writeConn io.Writer, readConn io.Reader) (allnum int64, err error) {
var mr utils.MultiReader
var buffers net.Buffers
var rawConn syscall.RawConn
if utils.CanLogDebug() {
log.Println("TryCopy", reflect.TypeOf(readConn), "->", reflect.TypeOf(writeConn))
}
if SystemCanSplice && CanSplice(readConn) && CanSplice(writeConn) {
if utils.CanLogDebug() {
log.Println("copying with splice")
}
goto copy
}
// 不全 支持splice的话我们就考虑 read端 可 readv 的情况
// 连readv都不让 那就直接 经典拷贝
if !UseReadv {
goto classic
}
rawConn = GetRawConn(readConn)
if rawConn == nil {
goto classic
}
mr = utils.GetReadVReader()
if utils.CanLogDebug() {
log.Println("copying with readv")
}
for {
buffers, err = ReadFromMultiReader(rawConn, mr)
if err != nil {
return 0, err
}
num, err2 := buffers.WriteTo(writeConn)
allnum += num
if err2 != nil {
err = err2
return
}
ReleaseNetBuffers(buffers)
}
classic:
if utils.CanLogDebug() {
log.Println("copying with classic method")
}
copy:
//Copy内部实现 会自动进行splice, 若无splice实现则直接使用原始方法 “循环读取 并 写入”
return io.Copy(writeConn, readConn)
}
// 类似TryCopy但是只会读写一次
func TryCopyOnce(writeConn io.Writer, readConn io.Reader) (allnum int64, err error) {
var mr utils.MultiReader
var buffers net.Buffers
var rawConn syscall.RawConn
if utils.CanLogDebug() {
log.Println("TryCopy", reflect.TypeOf(readConn), "->", reflect.TypeOf(writeConn))
}
if SystemCanSplice && CanSplice(readConn) && CanSplice(writeConn) {
if utils.CanLogDebug() {
log.Println("copying with splice")
}
goto copy
}
// 不全 支持splice的话我们就考虑 read端 可 readv 的情况
// 连readv都不让 那就直接 经典拷贝
if !UseReadv {
goto classic
}
rawConn = GetRawConn(readConn)
if rawConn == nil {
goto classic
}
mr = utils.GetReadVReader()
if utils.CanLogDebug() {
log.Println("copying with readv")
}
buffers, err = ReadFromMultiReader(rawConn, mr)
if err != nil {
return 0, err
}
allnum, err = buffers.WriteTo(writeConn)
ReleaseNetBuffers(buffers)
return
classic:
if utils.CanLogDebug() {
log.Println("copying with classic method")
}
copy:
//Copy内部实现 会自动进行splice, 若无splice实现则直接使用原始方法 “循环读取 并 写入”
return io.Copy(writeConn, readConn)
}
// 从conn1读取 写入到 conn2并同时从 conn2读取写入conn1
// 阻塞
func RelayTCP(conn1, conn2 net.Conn) {
go io.Copy(conn2, conn1)
io.Copy(conn1, conn2)
// 返回从 conn1读取 写入到 conn2的数据
// UseReadv==true 时 内部使用 TryCopy 进行拷贝
func Relay(conn1, conn2 io.ReadWriter) (int64, error) {
if UseReadv {
go TryCopy(conn1, conn2)
return TryCopy(conn2, conn1)
} else {
go io.Copy(conn1, conn2)
return io.Copy(conn2, conn1)
}
}
// 阻塞.

View File

@@ -76,6 +76,7 @@ type AppConf struct {
DefaultUUID string `toml:"default_uuid"`
MyCountryISO_3166 string `toml:"mycountry" json:"mycountry"` //加了mycountry后就会自动按照geoip分流,也会对顶级域名进行国别分流
NoReadV bool `toml:"noreadv"`
}
type RuleConf struct {

View File

@@ -89,5 +89,3 @@ func (s *Server) Handshake(underlay net.Conn) (io.ReadWriter, *netLayer.Addr, er
func (s *Server) CanFallback() bool {
return false
}
func (s *Server) Stop() {
}

View File

@@ -53,9 +53,7 @@ func (_ Server) CanFallback() bool {
func (_ Server) Name() string {
return name
}
func (_ Server) Stop() {
return
}
func (s *Server) Handshake(underlay net.Conn) (newconn io.ReadWriter, targetAddr *netLayer.Addr, err error) {
var b = utils.GetBytes(300) //一般要获取请求信息,不需要那么长; 就算是http加了pathpath也不会上千字节吧

View File

@@ -161,6 +161,9 @@ func prepareTLS_forProxyCommon_withURL(u *url.URL, isclient bool, com ProxyCommo
}
// ProxyCommonStruct 实现 ProxyCommon中除了Name 之外的其他方法
// 规定所有的proxy都要内嵌本struct
// 这是verysimple的架构所要求的。verysimple规定在加载完配置文件后一个listen和一个dial所使用的全部层级都是确定了的
// 因为所有使用的层级都是确定的,就可以进行针对性优化
type ProxyCommonStruct struct {
Addr string
TLS bool
@@ -230,6 +233,10 @@ func (pcs *ProxyCommonStruct) AdvancedLayer() string {
return pcs.AdvancedL
}
//do nothing. As a placeholder.
func (s *ProxyCommonStruct) Stop() {
}
// 从 url 初始化一些通用的配置,目前只有 u.Host
func (pcs *ProxyCommonStruct) InitFromUrl(u *url.URL) {
pcs.Addr = u.Host

View File

@@ -59,9 +59,6 @@ func (s *Server) Name() string { return Name }
func (s *Server) CanFallback() bool {
return false
}
func (s *Server) Stop() {
// Nothing to stop or close
}
//English: https://www.ietf.org/rfc/rfc1928.txt

View File

@@ -386,15 +386,13 @@ realPart:
return &UserConn{
Conn: underlay,
optionalReader: io.MultiReader(readbuf, underlay),
remainFirstBufLen: readbuf.Len(),
uuid: thisUUIDBytes,
version: int(version),
isUDP: addr.Network == "udp",
isServerEnd: true,
}, addr, nil
}
func (s *Server) Stop() {
}
func (s *Server) Get_CRUMFURS(id string) *CRUMFURS {

View File

@@ -32,7 +32,10 @@ const (
type UserConn struct {
net.Conn
optionalReader io.Reader //在使用了缓存读取包头后就产生了buffer中有剩余数据的可能性此时就要使用MultiReader
optionalReader io.Reader //在使用了缓存读取握手包头后就产生了buffer中有剩余数据的可能性此时就要使用MultiReader
remainFirstBufLen int //记录读取握手包头时读到的buf的长度. 如果我们读超过了这个部分的话,实际上我们就可以不再使用 optionalReader 读取, 而是直接从Conn读取
uuid [16]byte
convertedStr string
version int
@@ -170,6 +173,7 @@ func (uc *UserConn) Read(p []byte) (int, error) {
if !uc.isUDP {
if !uc.isServerEnd && !uc.isntFirstPacket {
//先读取响应头
uc.isntFirstPacket = true

View File

@@ -6,20 +6,23 @@ import (
)
var (
standardBytesPool sync.Pool //1500, 最大MTU的长度
standardBytesPool sync.Pool //专门储存 长度为 StandardBytesLength 的 []byte
// 作为参考对比tcp默认是 16384, 16k实际上范围是1k128k之间
// 而 udp则最大还不到 64k。(65535208)
// io.Copy 内部默认buffer大小为 32k
// 总之 我们64k已经够了
standardPacketPool sync.Pool //64*1024
standardPacketPool sync.Pool // 专门储存 长度为 MaxBufLen 的 []byte
bufPool sync.Pool
bufPool sync.Pool //储存 *bytes.Buffer
)
//即MTU
//即MTU, Maximum transmission unit, 参照的是 Ethernet v2 的MTU;
const StandardBytesLength int = 1500
//注意wifi信号MTU是 2304我们并未考虑wifi,主要是因为就算用wifi传, 早晚还是要经过以太网,除非两个wifi设备互传
// https://en.wikipedia.org/wiki/Maximum_transmission_unit
//本作设定的最大buf大小64k
const MaxBufLen = 64 * 1024
@@ -52,16 +55,16 @@ func PutBuf(buf *bytes.Buffer) {
bufPool.Put(buf)
}
//建议在 Read net.Conn 时, 使用 GetPacket函数 获取到足够大的 []byteMaxBufLen, 64*1024字节
//建议在 Read net.Conn 时, 使用 GetPacket函数 获取到足够大的 []byteMaxBufLen
func GetPacket() []byte {
return standardPacketPool.Get().([]byte)
}
// 放回用 GetPacket 获取的 []byte, 或者长度够长的可回收的 []byte
// 放回用 GetPacket 获取的 []byte
func PutPacket(bs []byte) {
c := cap(bs)
if c < MaxBufLen { //如果不够大,考虑放到更小的 pool里
if c > StandardBytesLength {
if c < MaxBufLen {
if c >= StandardBytesLength {
standardBytesPool.Put(bs[:c])
}
return
@@ -70,21 +73,19 @@ func PutPacket(bs []byte) {
standardPacketPool.Put(bs[:c])
}
// 从pool中获取 []byte, 在 size <= 1500时有最佳性能
func GetMTU() []byte {
return standardBytesPool.Get().([]byte)[:StandardBytesLength]
}
// 从pool中获取 []byte, 在 size <= StandardBytesLength 时有最佳性能
// 否则会直接调用 GetPacket
func GetBytes(size int) []byte {
if size < StandardBytesLength {
if size <= StandardBytesLength {
bs := standardBytesPool.Get().([]byte)
return bs[:size]
}
randomBytes1 := standardBytesPool.Get().([]byte)
if len(randomBytes1) >= size {
return randomBytes1[:size]
} else {
standardBytesPool.Put(randomBytes1)
return make([]byte, size)
}
return GetPacket()[:size]
}

View File

@@ -1,2 +1,9 @@
// Package utils provides utils that needed by all packages of verysimle
package utils
//具体实现见 readv_*.go
type MultiReader interface {
Init([][]byte)
Read(fd uintptr) int32
Clear()
}

View File

@@ -83,7 +83,7 @@ func (c *Conn) Read(p []byte) (int, error) {
*/
//log.Println("OpCode not Binary", h.OpCode)
return 0, utils.NewDataErr("ws first OpCode not Binary", nil, h.OpCode)
return 0, utils.NewDataErr("ws OpCode not Binary", nil, h.OpCode)
}
//log.Println("Read next frame header ok,", h.Length, c.r.State.Fragmented(), "givenbuf len", len(p))

View File

@@ -7,6 +7,7 @@ import (
"testing"
)
// ws基本读写功能测试.
// 分别测试写入短数据和长数据
func TestWs(t *testing.T) {
listenAddr := "127.0.0.1:7777"