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

相关文章

如何使用Lombok进行spring 注入

《如何使用Lombok进行spring注入》本文介绍如何用Lombok简化Spring注入,推荐优先使用setter注入,通过注解自动生成getter/setter及构造器,减少冗余代码,提升开发效... Lombok为了开发环境简化代码,好处不用多说。spring 注入方式为2种,构造器注入和setter

MySQL中比较运算符的具体使用

《MySQL中比较运算符的具体使用》本文介绍了SQL中常用的符号类型和非符号类型运算符,符号类型运算符包括等于(=)、安全等于(=)、不等于(/!=)、大小比较(,=,,=)等,感兴趣的可以了解一下... 目录符号类型运算符1. 等于运算符=2. 安全等于运算符<=>3. 不等于运算符<>或!=4. 小于运

使用zip4j实现Java中的ZIP文件加密压缩的操作方法

《使用zip4j实现Java中的ZIP文件加密压缩的操作方法》本文介绍如何通过Maven集成zip4j1.3.2库创建带密码保护的ZIP文件,涵盖依赖配置、代码示例及加密原理,确保数据安全性,感兴趣的... 目录1. zip4j库介绍和版本1.1 zip4j库概述1.2 zip4j的版本演变1.3 zip4

Python 字典 (Dictionary)使用详解

《Python字典(Dictionary)使用详解》字典是python中最重要,最常用的数据结构之一,它提供了高效的键值对存储和查找能力,:本文主要介绍Python字典(Dictionary)... 目录字典1.基本特性2.创建字典3.访问元素4.修改字典5.删除元素6.字典遍历7.字典的高级特性默认字典

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

一文详解如何使用Java获取PDF页面信息

《一文详解如何使用Java获取PDF页面信息》了解PDF页面属性是我们在处理文档、内容提取、打印设置或页面重组等任务时不可或缺的一环,下面我们就来看看如何使用Java语言获取这些信息吧... 目录引言一、安装和引入PDF处理库引入依赖二、获取 PDF 页数三、获取页面尺寸(宽高)四、获取页面旋转角度五、判断

Windows环境下解决Matplotlib中文字体显示问题的详细教程

《Windows环境下解决Matplotlib中文字体显示问题的详细教程》本文详细介绍了在Windows下解决Matplotlib中文显示问题的方法,包括安装字体、更新缓存、配置文件设置及编码調整,并... 目录引言问题分析解决方案详解1. 检查系统已安装字体2. 手动添加中文字体(以SimHei为例)步骤

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

使用Python删除Excel中的行列和单元格示例详解

《使用Python删除Excel中的行列和单元格示例详解》在处理Excel数据时,删除不需要的行、列或单元格是一项常见且必要的操作,本文将使用Python脚本实现对Excel表格的高效自动化处理,感兴... 目录开发环境准备使用 python 删除 Excphpel 表格中的行删除特定行删除空白行删除含指定