Go语言直接使用Windows的IOCP API写一个echo服务器

2024-05-25 03:12

本文主要是介绍Go语言直接使用Windows的IOCP API写一个echo服务器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Go的标准库中Windows下的网络是使用了IOCP的,参见go源码go/src/runtime/netpoll_windows.go,标准库为了与Epoll、kqueue等不同平台的IO模式使用统一的API,进行了封装。

如果想直接使用Windows的IOCP API编程,比如想按照:Windows下的高效网络模型IOCP完整示例中的流程写,就需要自行封装IOCP相关的API,虽然标准库中封装了很多系统调用,但是不是很全,而且API的函数签名也有一些问题,比如:

// Deprecated: CreateIoCompletionPort has the wrong function signature. Use x/sys/windows.CreateIoCompletionPort.
func CreateIoCompletionPort(filehandle Handle, cphandle Handle, key uint32, threadcnt uint32) (Handle, error) {return createIoCompletionPort(filehandle, cphandle, uintptr(key), threadcnt)
}// Deprecated: GetQueuedCompletionStatus has the wrong function signature. Use x/sys/windows.GetQueuedCompletionStatus.
func GetQueuedCompletionStatus(cphandle Handle, qty *uint32, key *uint32, overlapped **Overlapped, timeout uint32) error {var ukey uintptrvar pukey *uintptrif key != nil {ukey = uintptr(*key)pukey = &ukey}err := getQueuedCompletionStatus(cphandle, qty, pukey, overlapped, timeout)if key != nil {*key = uint32(ukey)if uintptr(*key) != ukey && err == nil {err = errorspkg.New("GetQueuedCompletionStatus returned key overflow")}}return err
}// Deprecated: PostQueuedCompletionStatus has the wrong function signature. Use x/sys/windows.PostQueuedCompletionStatus.
func PostQueuedCompletionStatus(cphandle Handle, qty uint32, key uint32, overlapped *Overlapped) error {return postQueuedCompletionStatus(cphandle, qty, uintptr(key), overlapped)
}

看了一下,其实内部调用的函数签名是没问题的,可以使用Go的魔法指令go:linkname来解决:

//go:linkname CreateIoCompletionPort syscall.createIoCompletionPort
func CreateIoCompletionPort(fileHandle syscall.Handle, cpHandle syscall.Handle, key uintptr, threadCnt uint32) (handle syscall.Handle, err error)//go:linkname GetQueuedCompletionStatus syscall.getQueuedCompletionStatus
func GetQueuedCompletionStatus(cpHandle syscall.Handle, qty *uint32, key *uintptr, overlapped **syscall.Overlapped, timeout uint32) (err error)//go:linkname PostQueuedCompletionStatus syscall.postQueuedCompletionStatus
func PostQueuedCompletionStatus(cphandle syscall.Handle, qty uint32, key uintptr, overlapped *syscall.Overlapped) (err error)

另外还需要使用到一些API,比如WSACreateEventWSAWaitForMultipleEventsWSAResetEventWSAGetOverlappedResult,就需要自行从Ws2_32.dll中装载了:

var (modws2_32 = syscall.NewLazyDLL("Ws2_32.dll")procWSACreateEvent           = modws2_32.NewProc("WSACreateEvent")procWSAWaitForMultipleEvents = modws2_32.NewProc("WSAWaitForMultipleEvents")procWSAResetEvent            = modws2_32.NewProc("WSAResetEvent")procWSAGetOverlappedResult   = modws2_32.NewProc("WSAGetOverlappedResult")
)func WSACreateEvent() (Handle syscall.Handle, err error) {r1, _, e1 := syscall.SyscallN(procWSACreateEvent.Addr())if r1 == 0 {err = errnoErr(e1)}return syscall.Handle(r1), nil
}func WSAWaitForMultipleEvents(cEvents uint32, lpEvent *syscall.Handle, fWaitAll bool, dwTimeout uint32, fAlertable bool) (uint32, error) {var WaitAll, Alertable uint32if fWaitAll {WaitAll = 1}if fAlertable {Alertable = 1}r1, _, e1 := syscall.SyscallN(procWSAWaitForMultipleEvents.Addr(), uintptr(cEvents), uintptr(unsafe.Pointer(lpEvent)), uintptr(WaitAll), uintptr(dwTimeout), uintptr(Alertable))if r1 == syscall.WAIT_FAILED {return 0, errnoErr(e1)}return uint32(r1), nil
}func WSAResetEvent(handle syscall.Handle) (err error) {r1, _, e1 := syscall.SyscallN(procWSAResetEvent.Addr(), uintptr(handle))if r1 == 0 {err = errnoErr(e1)}return
}func WSAGetOverlappedResult(socket syscall.Handle, overlapped *syscall.Overlapped, transferBytes *uint32, bWait bool, flag *uint32) (err error) {var wait uint32if bWait {wait = 1}r1, _, e1 := syscall.SyscallN(procWSAGetOverlappedResult.Addr(), uintptr(socket), uintptr(unsafe.Pointer(overlapped)),uintptr(unsafe.Pointer(transferBytes)), uintptr(wait), uintptr(unsafe.Pointer(flag)))if r1 == 0 {err = errnoErr(e1)}return
}

笔者尝试了下,完全可以,

在这里插入图片描述

直接附上源码:

package mainimport ("errors""fmt""os""runtime""syscall""unsafe"_ "unsafe"
)//go:linkname CreateIoCompletionPort syscall.createIoCompletionPort
func CreateIoCompletionPort(fileHandle syscall.Handle, cpHandle syscall.Handle, key uintptr, threadCnt uint32) (handle syscall.Handle, err error)//go:linkname GetQueuedCompletionStatus syscall.getQueuedCompletionStatus
func GetQueuedCompletionStatus(cpHandle syscall.Handle, qty *uint32, key *uintptr, overlapped **syscall.Overlapped, timeout uint32) (err error)//go:linkname PostQueuedCompletionStatus syscall.postQueuedCompletionStatus
func PostQueuedCompletionStatus(cphandle syscall.Handle, qty uint32, key uintptr, overlapped *syscall.Overlapped) (err error)//go:linkname errnoErr syscall.errnoErr
func errnoErr(e syscall.Errno) errorvar (modws2_32 = syscall.NewLazyDLL("Ws2_32.dll")procWSACreateEvent           = modws2_32.NewProc("WSACreateEvent")procWSAWaitForMultipleEvents = modws2_32.NewProc("WSAWaitForMultipleEvents")procWSAResetEvent            = modws2_32.NewProc("WSAResetEvent")procWSAGetOverlappedResult   = modws2_32.NewProc("WSAGetOverlappedResult")
)func WSACreateEvent() (Handle syscall.Handle, err error) {r1, _, e1 := syscall.SyscallN(procWSACreateEvent.Addr())if r1 == 0 {err = errnoErr(e1)}return syscall.Handle(r1), nil
}func WSAWaitForMultipleEvents(cEvents uint32, lpEvent *syscall.Handle, fWaitAll bool, dwTimeout uint32, fAlertable bool) (uint32, error) {var WaitAll, Alertable uint32if fWaitAll {WaitAll = 1}if fAlertable {Alertable = 1}r1, _, e1 := syscall.SyscallN(procWSAWaitForMultipleEvents.Addr(), uintptr(cEvents), uintptr(unsafe.Pointer(lpEvent)), uintptr(WaitAll), uintptr(dwTimeout), uintptr(Alertable))if r1 == syscall.WAIT_FAILED {return 0, errnoErr(e1)}return uint32(r1), nil
}func WSAResetEvent(handle syscall.Handle) (err error) {r1, _, e1 := syscall.SyscallN(procWSAResetEvent.Addr(), uintptr(handle))if r1 == 0 {err = errnoErr(e1)}return
}func WSAGetOverlappedResult(socket syscall.Handle, overlapped *syscall.Overlapped, transferBytes *uint32, bWait bool, flag *uint32) (err error) {var wait uint32if bWait {wait = 1}r1, _, e1 := syscall.SyscallN(procWSAGetOverlappedResult.Addr(), uintptr(socket), uintptr(unsafe.Pointer(overlapped)),uintptr(unsafe.Pointer(transferBytes)), uintptr(wait), uintptr(unsafe.Pointer(flag)))if r1 == 0 {err = errnoErr(e1)}return
}type IOData struct {Overlapped syscall.OverlappedWsaBuf     syscall.WSABufNBytes     uint32isRead     boolcliSock    syscall.Handle
}func main() {listenFd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)if err != nil {return}defer func() {syscall.Closesocket(listenFd)syscall.WSACleanup()}()v4 := &syscall.SockaddrInet4{Port: 6000,Addr: [4]byte{},}err = syscall.Bind(listenFd, v4)if err != nil {return}err = syscall.Listen(listenFd, 0)if err != nil {return}hIOCP, err := CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)if err != nil {return}count := runtime.NumCPU()for i := 0; i < count; i++ {go workThread(hIOCP)}defer PostQueuedCompletionStatus(hIOCP, 0, 0, nil)for {acceptFd, er := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)if er != nil {return}b := make([]byte, 1024)recvD := uint32(0)data := &IOData{Overlapped: syscall.Overlapped{},WsaBuf: syscall.WSABuf{Len: 1024,Buf: &b[0],},NBytes:  1024,isRead:  true,cliSock: acceptFd,}data.Overlapped.HEvent, er = WSACreateEvent()if er != nil {return}size := uint32(unsafe.Sizeof(&syscall.SockaddrInet4{}) + 16)er = syscall.AcceptEx(listenFd, acceptFd, data.WsaBuf.Buf, data.WsaBuf.Len-size*2, size, size, &recvD, &data.Overlapped)if er != nil && !errors.Is(er, syscall.ERROR_IO_PENDING) {er = os.NewSyscallError("AcceptEx", er)continue}_, er = WSAWaitForMultipleEvents(1, &data.Overlapped.HEvent, true, syscall.INFINITE, true)if er != nil {return}WSAResetEvent(data.Overlapped.HEvent)dwBytes := uint32(0)flag := uint32(0)WSAGetOverlappedResult(acceptFd, (*syscall.Overlapped)(unsafe.Pointer(&data)), &dwBytes, true, &flag)if dwBytes == 0 {continue}fmt.Printf("client %d connected\n", acceptFd)_, err = CreateIoCompletionPort(acceptFd, hIOCP, 0, 0)if err != nil {continue}postWrite(data)}
}func postWrite(data *IOData) (err error) {data.isRead = false// 这里输出一下data指针,让运行时不把data给GC掉,否则就会出问题fmt.Printf("%p cli:%d send %s\n", data, data.cliSock, unsafe.String(data.WsaBuf.Buf, data.WsaBuf.Len))err = syscall.WSASend(data.cliSock, &data.WsaBuf, 1, &data.NBytes, 0, &data.Overlapped, nil)if err != nil {fmt.Printf("cli:%d send failed: %s\n", data.cliSock, err)return err}return
}func postRead(data *IOData) (err error) {data.NBytes = data.WsaBuf.Lendata.isRead = trueflag := uint32(0)err = syscall.WSARecv(data.cliSock, &data.WsaBuf, 1, &data.NBytes, &flag, &data.Overlapped, nil)if err != nil && !errors.Is(err, syscall.ERROR_IO_PENDING) {fmt.Printf("cli:%d receive failed: %s\n", data.cliSock, err)return err}return
}func workThread(hIOCP syscall.Handle) {var pOverlapped *syscall.Overlappedvar ioSize uint32var key uintptrfor {err := GetQueuedCompletionStatus(hIOCP, &ioSize, &key, &pOverlapped, syscall.INFINITE)if err != nil {fmt.Printf("GetQueuedCompletionStatus failed: %s\n", err)return}if ioSize == 0 {break}ioData := (*IOData)(unsafe.Pointer(pOverlapped))if ioData.isRead {postWrite(ioData)} else {postRead(ioData)}}
}

源码只是一个示例,有资源泄漏的问题待处理。

这篇关于Go语言直接使用Windows的IOCP API写一个echo服务器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1000321

相关文章

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

Python中注释使用方法举例详解

《Python中注释使用方法举例详解》在Python编程语言中注释是必不可少的一部分,它有助于提高代码的可读性和维护性,:本文主要介绍Python中注释使用方法的相关资料,需要的朋友可以参考下... 目录一、前言二、什么是注释?示例:三、单行注释语法:以 China编程# 开头,后面的内容为注释内容示例:示例:四

Go语言中nil判断的注意事项(最新推荐)

《Go语言中nil判断的注意事项(最新推荐)》本文给大家介绍Go语言中nil判断的注意事项,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.接口变量的特殊行为2.nil的合法类型3.nil值的实用行为4.自定义类型与nil5.反射判断nil6.函数返回的

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

mysql中的服务器架构详解

《mysql中的服务器架构详解》:本文主要介绍mysql中的服务器架构,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、mysql服务器架构解释3、总结1、背景简单理解一下mysqphpl的服务器架构。2、mysjsql服务器架构解释mysql的架

ModelMapper基本使用和常见场景示例详解

《ModelMapper基本使用和常见场景示例详解》ModelMapper是Java对象映射库,支持自动映射、自定义规则、集合转换及高级配置(如匹配策略、转换器),可集成SpringBoot,减少样板... 目录1. 添加依赖2. 基本用法示例:简单对象映射3. 自定义映射规则4. 集合映射5. 高级配置匹

Spring 框架之Springfox使用详解

《Spring框架之Springfox使用详解》Springfox是Spring框架的API文档工具,集成Swagger规范,自动生成文档并支持多语言/版本,模块化设计便于扩展,但存在版本兼容性、性... 目录核心功能工作原理模块化设计使用示例注意事项优缺点优点缺点总结适用场景建议总结Springfox 是

嵌入式数据库SQLite 3配置使用讲解

《嵌入式数据库SQLite3配置使用讲解》本文强调嵌入式项目中SQLite3数据库的重要性,因其零配置、轻量级、跨平台及事务处理特性,可保障数据溯源与责任明确,详细讲解安装配置、基础语法及SQLit... 目录0、惨痛教训1、SQLite3环境配置(1)、下载安装SQLite库(2)、解压下载的文件(3)、

使用Python绘制3D堆叠条形图全解析

《使用Python绘制3D堆叠条形图全解析》在数据可视化的工具箱里,3D图表总能带来眼前一亮的效果,本文就来和大家聊聊如何使用Python实现绘制3D堆叠条形图,感兴趣的小伙伴可以了解下... 目录为什么选择 3D 堆叠条形图代码实现:从数据到 3D 世界的搭建核心代码逐行解析细节优化应用场景:3D 堆叠图

Springboot如何正确使用AOP问题

《Springboot如何正确使用AOP问题》:本文主要介绍Springboot如何正确使用AOP问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录​一、AOP概念二、切点表达式​execution表达式案例三、AOP通知四、springboot中使用AOP导出