Showing posts with label Redis. Show all posts
Showing posts with label Redis. Show all posts

Thursday, 17 January 2019

C# - RTD - Redis - Real Time Data on the worksheet

In this article I share C# code for a Real Time Data worksheet function callable using RTD(). It will call into a local instance of Redis and will tick in real time. This article assumes you have installed Redis.

Click here for separate Youtube window

So I am very much pleased with Redis as outlined in the two articles (here and here) I wrote earlier this month. I hope you have read those previous two articles , and/or watched the videos. Redis is exactly what I have been looking for, I want a machine wide data cache, not too heavyweight but accessible to multiple instances of Excel because it would be inefficient for multiple Excel sessions to draw down duplicate data from network servers. In my humble opinion, much better to draw the data down to one executable, Redis, and then have the Excel sessions call into Redis locally.

RTD Server

There are already examples online for how to write an RTD server in C# (including the superb Learning Tree blog post here) and they share a similar pattern in that they have a timer and the single data item that they return is typically a timestamp so they can show the timestamp moving. I started from those examples and then expanded the functionality. I want multiple data items. I also want to make use of the multiple arguments, for example supplying a JSON path to drill into a JSON document.

Introducing ExcelCoin

I do have a stockbroker but I'm not going to advertise their data. Instead, I invent a fake financial security called ExcelCoin. I write the ExcelCoin price to Redis as a scalar number. I also write a JSON meta document to Redis, which gives the timestamp; for financial securities it could also give trading volume etc. The VBA code for this is given below but it does rely on the C# component given even further below. So just hang on.

I've invented ExcelCoin just to fake a real-time data feed. If you already have a real-time data feed then you can use that to write prices, headlines or whatever to Redis by re-writing the code below.

[If running the code in Word then you need the Python class from this blog post to generate a random Normal observation. If running in Excel then the code uses Excel's WorksheetFunction].

Option Explicit

Private vExcelCoinPrice As Variant

Private moPythonNormsInv As Object
Private moRedisSocket As Object

Public Property Get RedisSocket() As Object
    If moRedisSocket Is Nothing Then
        
        '* see next blog post for codeo this server
        Set moRedisSocket = VBA.CreateObject("RedisRTDServerLib.RedisSocket")
        moRedisSocket.Initialize
    End If
    Set RedisSocket = moRedisSocket
End Property

Public Sub DisposeRedisSocket()
    If Not moRedisSocket Is Nothing Then
        moRedisSocket.Dispose
        Set moRedisSocket = Nothing
    
    End If
End Sub


Public Property Get PythonNormsInv2() As Object
    If moPythonNormsInv Is Nothing Then
        Set moPythonNormsInv = VBA.CreateObject("SciPyInVBA.PythonNormsInv")
    End If
    Set PythonNormsInv2 = moPythonNormsInv
End Property

Sub Test_VBAPyNormStdInv()
    Debug.Print PythonNormsInv2.PyNormStdInv(0.95)
End Sub

Sub ResetCoin()
    vExcelCoinPrice = 1.2
End Sub

Sub TestOnTime()
    
    '*
    '* initialize price of ExcelCoin
    '*
    If IsEmpty(vExcelCoinPrice) Then
        vExcelCoinPrice = 1.2
    End If

    Const dDrift As Double = 1.0001
    
    Dim dRectangular As Double
    dRectangular = Rnd(1)
    
    Dim dNormal2 As Double
    If WinWordVBA() Then
        dNormal2 = PythonNormsInv2.PyNormInv(dRectangular, 0, 0.001)
    ElseIf ExcelVBA() Then
        '=NORM.INV(0.95,0,0.001)
        Dim objApp As Object
        Set objApp = Application
        dNormal2 = objApp.WorksheetFunction.NormInv(dRectangular, 0, 0.001)
    
    End If
    
    Dim dLogNormal As Double
    dLogNormal = Exp(dNormal2)
    
    vExcelCoinPrice = vExcelCoinPrice * dLogNormal * dDrift
    Debug.Print vExcelCoinPrice

    '*
    '* call Redis to update price of Excel Coin, this is just a number (double)
    '*
    Dim oRedisSocket As Object
    Set oRedisSocket = RedisSocket
    Dim sResponse As String
    sResponse = oRedisSocket.SendAndReadReponse("SET USD/ExcelCoin " & CStr(vExcelCoinPrice) & vbCrLf)
    Dim vParsed As Variant
    vParsed = oRedisSocket.Parse(sResponse)
    
    '*
    '* also write the fuller meta document that carries timestamps, source URI etc.
    '*
    Dim sJsonDoc As String
    sJsonDoc = "{ ""timestamp2"": " & CDbl(Now()) & ", ""redisId"": ""USD/ExcelCoin/meta"" ,  ""point"": " & vExcelCoinPrice & " }"
    sJsonDoc = VBA.Replace(sJsonDoc, """", "\""")
    sResponse = oRedisSocket.SendAndReadReponse("SET USD/ExcelCoin/meta """ & sJsonDoc & """" & vbCrLf)
    vParsed = oRedisSocket.Parse(sResponse)
    
    '*
    '* set up next call
    '*
    DoEvents
    Call Application.OnTime(Format(Now() + CDate("00:00:02"), "dd/mmm/yyyy hh:mm:ss"), "TestOnTime")
End Sub

Function ExcelVBA() As Boolean
    On Error GoTo QuickExit
    Dim objApp As Object
    Set objApp = Application
    
    Dim objWbs As Object
    Set objWbs = objApp.Workbooks
    
    ExcelVBA = True
QuickExit:
End Function

Function WinWordVBA() As Boolean
    On Error GoTo QuickExit
    Dim objApp As Object
    Set objApp = Application
    
    Dim objActiveDocument As Object
    Set objActiveDocument = objApp.ActiveDocument
    
    WinWordVBA = True
QuickExit:
End Function

About the C# RTD Server Code

The listing is quite large and worth talking through. The project type is a C# assembly with the 'Register for COM interop' checkbox checked (found on the Build tab of the project properties). Registering COM components requires administrator rights so run Visual Studio with admin rights. In AssemblyInfo.cs I have set [assembly: ComVisible(true)]

The RTD interfaces are defined in the Excel type library. So one must add a reference to Microsoft.Office.Interop.Excel primary interop assembly (PIA).

Also, I do some JSON processing so I have added the Newtonsoft.Json package by using the Package Manager.

Code Reuse

I do try to write object orientated code. In the listing below, there are three major classes plus some minor helper classes. The code split is intended to promote code re-use. Firstly, there is the RTDServer class which houses the RTD interface implementation. Secondly, there is the RedisSocket class which houses code for interacting with Redis via Sockets. Hopefully those two will be quite reusable.

Thirdly, the RedisRTDServer class bridges the RTDServer and RedisSocket classes. This is less likely to be reusable as it is very much idiosyncratic to this application.

DebugView and Error Handling

One potential impediment to re-use is error handling. The RTD function will smother any errors, if I want to communicate an error I need to write it to a log or call the Win32 Api OutputDebugString function and view the message using the DebugView application from SysInternals. Also, I can pass back the error message so it is written to calling cell. In the code I do both.

To facilitate this I subclass the C# Exception class to give a DebugMonitorableException class.

Error handling can be quite a personal choice so feel free to take a different view on error handling to me.

COM Interfaces

Two of the three major classes, RedisSocket and RedisRTDServer also have COM interfaces which allows them to be callable from VBA. This promotes re-use. I like to develop classes in C# whilst writing unit tests in Excel VBA. This is frequently how my classes mature and evolve.

Singleton substitute for global variable

We all know that global variables are naughty and in fact impossible in C#. However, I needed a singleton instance of the RedisSocket class to be initialised and be available thereafter. I found that creating and disposing the class every time I wanted to read a price caused the RTD server code to freeze. Moving to a singleton instance solved this.

RTDServer implements IRTDServer

There is a standard interface IRTDServer that any RTD server class must implement. One can read the Microsoft official documentation here. Actually better to read this superb Learning Tree blog post here.

In fact that Learning Tree blog post is probably a better place to begin and once you have that running correctly you can return to this blog post to see how to implement multiple topics as served by Redis.

RedisSocket encapsulates Redis Sockets interface

The RedisSocket class encapsulates a minimalistic interface to Redis. There are more comprehensive C# Redis interface libraries if you want them but for this blog this code will serve. The code uses the .NET TcpClient class to send and receive bytes via Sockets to/from Redis. The code parses the response using a simple split function. It is based on some equally simple VBA code I wrote in previous blog posts. Nevertheless it serves. I won't comment further on it here. See the previous two Redis blog posts for more details.

RedisRTDServer houses application specific code

As mentioned above, in order to make the RedisSockets and RTDServer classes as re-usable as possible I felt it necessary to offload as much as possible into a third class, RedisRTDServer, which houses all the code idiosyncratic to this application. It is called by the RTDServer class and is handed an array of strings as parameters.

The RedisRTDServer class interprets this array of strings. This is the role of the Router() method. The first string is the key; for the ExcelCoin the key is 'USD/ExcelCoin'. The second string specifies a handler which allows different processing logic, so if I want a single price then I supply "scalar" as the handler or indeed just an empty string ("").

I have added one other handler option, "json" and in this use case the key identifies a JSON document the third string is interpreted as a JSON path which allows a drill down into the JSON document.

Conceivably, in the future I could add another option "xml" and allow an XPath expression to be supplied to drill down into an Xml document.

I think you'll agree, there is plenty of scope to define a detailed interface.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Timers;
using System.Text;
using System.Reflection;

// Added Reference to Microsoft Excel 15
using Microsoft.Office.Interop.Excel;
using Newtonsoft.Json.Linq;  // I used Package Manager to add Newtonsoft.Json

namespace RedisRTDServerLib
{
    [ComVisible(false)]
    static class Win32Api
    {   // this is so I can use DebugView from SysInternals
        [DllImport("kernel32.dll")]
        public static extern void OutputDebugString(string lpOutputString);
    }

    [ComVisible(false)]
    static class Singletons
    {   // this Singleton class is in lieu of a global variable
        static RedisSocket _redisSocket = null;

        static public RedisSocket RedisSocketSingleton
        {
            get
            {
                if (_redisSocket == null)
                {
                    _redisSocket = new RedisSocket();
                    _redisSocket.Initialize();
                }
                return _redisSocket;
            }
            set
            {
                // only interested in this for tidy up purposes
                if (value == null)
                {
                    _redisSocket = value;
                }
            }
        }
    }

    [ComVisible(false)]
    public class DebugMonitorableException : Exception
    {   // this class does the same as an Exception but simply prints the error message to the debug monitor
        public DebugMonitorableException()
        {
        }

        public DebugMonitorableException(string message) : base(message)
        {
            Win32Api.OutputDebugString(message);
        }

        public DebugMonitorableException(string message, Exception inner) : base(message, inner)
        {
            Win32Api.OutputDebugString(message);
        }
    }

    [ComVisible(true)]
    public interface IRedisSocket
    {
        void Initialize();
        void Dispose();
        string SendAndReadReponse(string sCommand);
        object Parse(string sRaw);
    }
    [ClassInterface(ClassInterfaceType.None), ComVisible(true)]
    [ComDefaultInterface(typeof(IRedisSocket))]
    public class RedisSocket : IRedisSocket, IDisposable
    {
        TcpClient _client;

        public void Initialize()
        {
            try
            {
                _client = new TcpClient();
                _client.Connect("127.0.0.1", 6379);
            }
            catch (Exception ex)
            {
                throw new DebugMonitorableException("Trapped error: " + ex.Message);
            }
        }

        public void Dispose()
        {
            _client.Close();

            _client.Dispose();
            _client = null;
        }

        public string SendAndReadReponse(string sCommand)
        {
            try
            {
                //TcpClient client = new TcpClient();
                NetworkStream stream;

                stream = _client.GetStream();

                Byte[] sendBytes = Encoding.UTF8.GetBytes(sCommand);
                stream.Write(sendBytes, 0, sendBytes.Length);
                stream.Flush();

                Byte[] recvBytes = new byte[_client.ReceiveBufferSize];
                stream.Read(recvBytes, 0, recvBytes.Length);

                string result = Encoding.UTF8.GetString(recvBytes);


                string result2 = result.Substring(0, result.LastIndexOf("\r\n"));
                return result2;
            }
            catch (Exception ex)
            {
                throw new DebugMonitorableException("SendAndReadReponse error: " + ex.Message);
            }
        }

        public object Parse(string sResponse)
        {
            try
            {
                Int32 lTotalLength = sResponse.Length;
                Debug.Assert(lTotalLength > 0);

                string[] vSplitResponse;
                {
                    string[] splitStrings = { "\r\n" };
                    vSplitResponse = sResponse.Split(splitStrings, StringSplitOptions.None);
                }

                Int32 lReponseLineCount = vSplitResponse.Length;

                switch (sResponse.Substring(0, 1))
                {
                    case "$":
                        if (sResponse == "$-1")
                        {
                            throw new Exception("Not found!");
                        }
                        else
                        {
                            return vSplitResponse[1];
                        }
                    case "+":
                        return vSplitResponse[0].Substring(1);
                    case ":":

                        //'* response is a numeric
                        return Double.Parse(vSplitResponse[0].Substring(1));
                    case "-":
                        //'* response is an error
                        throw new Exception(vSplitResponse[0].Substring(1));
                    case "*":
                        //'* multiple responses, build an array to return
                        Int32 lResponseCount = Int32.Parse(vSplitResponse[0].Substring(1));
                        if (lResponseCount > 0)
                        {
                            Debug.Assert(lResponseCount == (lReponseLineCount - 1) / 2);
                            List<Object> returnList = new List<Object>();
                            for (Int32 lLoop = 0; lLoop < lResponseCount; lLoop++)
                            {
                                returnList.Add(vSplitResponse[(lLoop + 1) * 2]);
                            }
                            return returnList.ToArray();
                        }
                        else
                        {
                            return false;
                        }
                    default:
                        // '* this should not happen
                        throw new Exception("Unrecognised return type '" + sResponse.Substring(0, 1) + "'");
                }
            }
            catch (Exception ex)
            {
                throw new DebugMonitorableException("Parse error: " + ex.Message);
            }
        }
    }

    [ComVisible(true)]
    public interface IRedisRTDServer
    {
        object Router(Array Strings);
        string GetJsonDocOrError(Array Strings);
        Boolean GetJSON(string sKey, string path, out string json, out string errorMsg);
        string GetKey(Array Strings);
        string GetPath(Array Strings);
        string GetHandler(Array Strings);
        object GetScalarOrError(Array Strings);
        Boolean GetScalar(string sKey, out double scalar, out string errorMsg);
    }

    [ClassInterface(ClassInterfaceType.None), ComVisible(true)]
    [ComDefaultInterface(typeof(IRedisRTDServer))]
    public class RedisRTDServer : IRedisRTDServer
    {
        public object Router(Array Strings)
        {
            try
            {
                string handler = GetHandler(Strings);
                string path = GetPath(Strings);

                switch (handler)
                {
                    case "":
                    case "scalar":
                        return GetScalarOrError(Strings);
                        
                    case "json":
                        return GetJsonDocOrError(Strings);
                        
                    default:
                        return "Failed to request with handler '" + handler + "'";
                }
            }
            catch (Exception ex)
            {
                string errMsg = this.GetType().Name + '.' + MethodBase.GetCurrentMethod() + " error:" + ex.Message;
                Win32Api.OutputDebugString(errMsg);
                return errMsg;
            }
        }

        public string GetJsonDocOrError(Array Strings)
        {
            try
            {
                string sKey = GetKey(Strings);
                string path = GetPath(Strings);
                if (GetJSON(sKey, path, out string json, out string errMsg))
                {
                    return json;
                }
                else
                {
                    return errMsg;
                }
            }
            catch (Exception ex)
            {
                string errMsg = this.GetType().Name + '.' + MethodBase.GetCurrentMethod() + " error:" + ex.Message;
                Win32Api.OutputDebugString(errMsg);
                return errMsg;
            }
        }

        public Boolean GetJSON(string sKey, string path, out string json, out string errorMsg)
        {

            json = "";
            errorMsg = "";
            try
            {

                object parsed = null;

                try
                {
                    RedisSocket redisSocket = null;
                    redisSocket = Singletons.RedisSocketSingleton;
                    string rawResponse = redisSocket.SendAndReadReponse("GET " + sKey + "\r\n");
                    parsed = redisSocket.Parse(rawResponse);
                }
                catch (Exception ex)
                {
                    // for the time being I'm not rethrowing this
                    errorMsg = ex.Message;
                    return false;
                }
                if (parsed is String)
                {
                    json = (string)parsed;

                    if (path.Length == 0)
                    {
                        return true;
                    }
                    else
                    {   // NewtonSoft.JSON code to drill into a JSON document with a JSON path
                        JObject o = JObject.Parse(json);
                        JToken acme = o.SelectToken(path);
                        json = acme.ToString();
                        return true;
                    }
                }
                else
                {
                    errorMsg = "Not a JSON document!";
                    return false;
                }

            }
            catch (Exception ex)
            {
                throw new DebugMonitorableException(this.GetType().Name + '.' + MethodBase.GetCurrentMethod() + ": error\r\n" + ex.Message);
            }
        }

        public string GetKey(Array Strings)
        {
            try
            {
                string sKey;
                sKey = (string)Strings.GetValue(0);
                if (sKey.Contains(" "))
                {
                    sKey = "'" + sKey + "'";
                }
                return sKey;
            }
            catch (Exception ex)
            {
                string errMsg = this.GetType().Name + '.' + MethodBase.GetCurrentMethod() + " error:" + ex.Message;
                Win32Api.OutputDebugString(errMsg);
                throw new Exception(errMsg);
            }
        }

        public string GetPath(Array Strings)
        {
            try
            {
                string path = "";
                {
                    if (Strings.Length >= 3)
                    {
                        path = (string)Strings.GetValue(2);
                    }
                }
                return path;
            }
            catch (Exception ex)
            {
                string errMsg = this.GetType().Name + '.' + MethodBase.GetCurrentMethod() + " error:" + ex.Message;
                Win32Api.OutputDebugString(errMsg);
                throw new Exception(errMsg);
            }
        }

        public string GetHandler(Array Strings)
        {
            try
            {
                string handler = "";
                {
                    if (Strings.Length >= 2)
                    {
                        handler = (string)Strings.GetValue(1);
                    }
                }
                return handler;
            }
            catch (Exception ex)
            {
                string errMsg = this.GetType().Name + '.' + MethodBase.GetCurrentMethod() + " error:" + ex.Message;
                Win32Api.OutputDebugString(errMsg);
                throw new Exception(errMsg);
            }
        }

        public object GetScalarOrError(Array Strings)
        {
            try
            {
                string sKey = GetKey(Strings);

                if (GetScalar(sKey, out double price, out string errMsg))
                {
                    return price;
                }
                else
                {
                    return errMsg;
                }
            }
            catch (Exception ex)
            {
                string errMsg = this.GetType().Name + '.' + MethodBase.GetCurrentMethod() + " error:" + ex.Message;
                Win32Api.OutputDebugString(errMsg);
                return errMsg;
            }
        }

        public Boolean GetScalar(string sKey, out double scalar, out string errorMsg)
        {

            scalar = 0;
            errorMsg = "";
            try
            {

                object parsed = null;

                try
                {
                    RedisSocket redisSocket = null;
                    
                    redisSocket = Singletons.RedisSocketSingleton;
                    string rawResponse = redisSocket.SendAndReadReponse("GET " + sKey + "\r\n");
                    parsed = redisSocket.Parse(rawResponse);
                }
                catch (Exception ex)
                {
                    // for the time being I'm not rethrowing this
                    errorMsg = ex.Message;
                    return false;
                }
                if (parsed is String)
                {
                    string parsedString = (string)parsed;
                    if (!Double.TryParse(parsedString, out scalar))
                    {   
                        throw new DebugMonitorableException(this.GetType().Name + '.' + MethodBase.GetCurrentMethod() + ": could not parse '" + parsedString + "' to double");
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                throw new DebugMonitorableException(this.GetType().Name + '.' + MethodBase.GetCurrentMethod() + ": error\r\n" + ex.Message);
            }
        }
    }


    public class RTDServer : IRtdServer
    {
        private Dictionary<int, Array> _topicsAndStrings = new Dictionary<int, Array>();
        private IRTDUpdateEvent m_callback;
        private Timer m_timer;
        private int m_topicId;

        int IRtdServer.ServerStart(IRTDUpdateEvent CallbackObject)
        {
            try
            {
                m_callback = CallbackObject;
                m_timer = new Timer();
                m_timer.Elapsed += new ElapsedEventHandler(TimerEventHandler);
                m_timer.Interval = 3000; // in milliseconds
                return 1;
            }
            catch (Exception ex)
            {
                Win32Api.OutputDebugString("IRtdServer.ServerStart:" + ex.Message);
                return 0;
            }
        }

        dynamic IRtdServer.ConnectData(int TopicID, ref Array Strings, ref bool GetNewValues)
        {
            try
            {
                _topicsAndStrings.Add(TopicID, Strings);

                m_topicId = TopicID;
                m_timer.Start();
                return GetTime();
            }
            catch (Exception ex)
            {
                string errMsg = "IRtdServer.ConnectData error:" + ex.Message;
                Win32Api.OutputDebugString(errMsg);
                return errMsg;
            }
        }

        private string GetTime()
        {
            return "RTD Server GetTime method: " + DateTime.Now.ToString("hh: mm:ss");
        }

        private void TimerEventHandler(object sender, EventArgs args)
        {
            try
            {
                // UpdateNotify is called to inform Excel that new data are available
                // the timer is turned off so that if Excel is busy, the TimerEventHandler is not called repeatedly

                m_timer.Stop();
                m_callback.UpdateNotify();
            }
            catch (Exception ex)
            {
                Win32Api.OutputDebugString("TimerEventHandler:" + ex.Message);
            }
        }

        Array IRtdServer.RefreshData(ref int TopicCount)
        {
            object[,] data = null;
            try
            {
                TopicCount = _topicsAndStrings.Count;
                data = new object[2, TopicCount];

                RedisRTDServer redisRtdServer = new RedisRTDServer();
                int idx = 0;
                foreach (var kvp in _topicsAndStrings)
                {
                    Array Strings = kvp.Value;
                    data[0, idx] = kvp.Key;
                    data[1, idx] = redisRtdServer.Router(kvp.Value);
                    idx++;
                }

                m_timer.Start();
                return data;
            }
            catch (Exception ex)
            {
                Win32Api.OutputDebugString("IRtdServer.RefreshData:" + ex.Message);
                return data;
            }
        }

        void IRtdServer.DisconnectData(int TopicID)
        {
            try
            {
                if (_topicsAndStrings.ContainsKey(TopicID))
                {
                    _topicsAndStrings.Remove(TopicID);
                }

                if (_topicsAndStrings.Count == 0)
                {
                    m_timer.Stop();
                    Singletons.RedisSocketSingleton = null;
                }
            }
            catch (Exception ex)
            {
                Win32Api.OutputDebugString("IRtdServer.DisconnectData:" + ex.Message);
            }
        }

        int IRtdServer.Heartbeat()
        {
            return 1;
        }

        void IRtdServer.ServerTerminate()
        {
            try
            {
                if (null != m_timer)
                {
                    m_timer.Dispose();
                    m_timer = null;
                }
                if (null != Singletons.RedisSocketSingleton)
                {
                    Singletons.RedisSocketSingleton = null;
                }
            }
            catch (Exception ex)
            {
                Win32Api.OutputDebugString("IRtdServer.ServerTerminate:" + ex.Message);
            }
        }
    }
}

Examples of calling from the worksheet

So finally, to call from the worksheet you need some cell formulas

=RTD("redisrtdserverlib.rtdserver",,"USD/ExcelCoin","","")
=RTD("redisrtdserverlib.rtdserver",,"USD/ExcelCoin/meta","json","$.timestamp2")+0

Monday, 7 January 2019

VBA - Redis - Sockets - Low level TCP/IP code to interop with Redis

I'm going to show you some low level TCP/IP code to interoperate with Redis which is an open-source in-memory key-value store. Redis is a often used as a database cache to make cloud based applications more scalable by relieving the load on a relational database. I will give both C++ and VBA code that call a group of Win-32 Api functions known as Windows Sockets2 or Winsock.

Click here for separate Youtube window

This post follows on from the previous post where I detailed (both in text and video) ...

  1. How to install Redis
  2. How to get some data into and out of Redis using the command line tool, redis-cli
  3. How to call Redis from a VB.NET console application
  4. How to call Redis from VBA via a VB.Net COM Dll Interop Assembly

This post shows a solution that skips the .NET component and instead scripts directly against the Windows Sockets 2 Api.

Background to Sockets - TCP/IP

Redis does not have an HTTP interface and so we have to drop down a few layers and program Sockets. If you are new to Sockets then here is a little explanation. When I say drop down a few layers, the diagram below is very useful. It illustrates the OSI model. When dealing with HTTP we deal with the top layer of the OSI model, the Application layer or layer 7. Programming sockets means programming the Transport layer or layer 4 of the OSI model. If you program a lower layer then you can expect the code to be more complicated (unless of course you are given a high level abstraction like .NET framework does).

Wwww2

Forewarned of the increased complexity let's look at some sample code.

Starting with C++

So Windows API functions (except .NET platform) are 99% designed to be used by C++ developers. Typically when reading a Windows API documentation page the sample code will be given in C++. It is a useful skill therefore to be able to get the C++ sample up running so that you can step through and understand the code. You are best trying to run a sample from the most recently updated documentation page. Any compile errors can be googled and, of course, stackoverflow.com is your friend.

The C++ sample I began with was the recv defined in winsock.h and housed in ws2_32.dll.

Sometimes you need to disable warnings so in the sample code for the recv function I needed to added the following line at the top

#pragma warning(disable:4996)   //https://stackoverflow.com/questions/36683785/inet-addr-use-inet-pton-or-inetpton-instead-or-define-winsock-deprecated/47397620

Here is the full C++ code for pasting into a Win32 C++ Console program

  1. // Win32CppWinSockets2Play.cpp : This file contains the 'main' function. Program execution begins and ends there.
  2. //
  3.  
  4. #include "pch.h"
  5. #include <iostream>
  6.  
  7. #include <winsock2.h>
  8. #include <Ws2tcpip.h>
  9. #include <stdio.h>
  10.  
  11. // Link with ws2_32.lib
  12. #pragma comment(lib"Ws2_32.lib")
  13.  
  14. #define DEFAULT_BUFLEN 512
  15. #define DEFAULT_PORT 6379
  16.  
  17. //#define _WINSOCK_DEPRECATED_NO_WARNINGS
  18. #pragma warning(disable:4996)   //https://stackoverflow.com/questions/36683785/inet-addr-use-inet-pton-or-inetpton-instead-or-define-winsock-deprecated/47397620
  19.  
  20. int main()
  21. {
  22.         //----------------------
  23.         // Declare and initialize variables.
  24.         int iResult;
  25.         WSADATA wsaData;
  26.  
  27.         SOCKET ConnectSocket = INVALID_SOCKET;
  28.         struct sockaddr_in clientService;
  29.  
  30.         int recvbuflen = DEFAULT_BUFLEN;
  31.         char *sendbuf = (char *)"keys *\r\n";
  32.         char recvbuf[DEFAULT_BUFLEN] = "";
  33.  
  34.         //----------------------
  35.         // Initialize Winsock
  36.         iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
  37.         if (iResult != NO_ERROR) {
  38.                 wprintf(L"WSAStartup failed with error: %d\n", iResult);
  39.                 return 1;
  40.         }
  41.  
  42.         //----------------------
  43.         // Create a SOCKET for connecting to server
  44.         ConnectSocket = socket(AF_INETSOCK_STREAMIPPROTO_TCP);
  45.         if (ConnectSocket == INVALID_SOCKET) {
  46.                 wprintf(L"socket failed with error: %ld\n", WSAGetLastError());
  47.                 WSACleanup();
  48.                 return 1;
  49.         }
  50.  
  51.         //----------------------
  52.         // The sockaddr_in structure specifies the address family,
  53.         // IP address, and port of the server to be connected to.
  54.         clientService.sin_family = AF_INET;
  55.  
  56.         clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
  57.  
  58.         clientService.sin_port = htons(DEFAULT_PORT);
  59.  
  60.         //----------------------
  61.         // Connect to server.
  62.         iResult = connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService));
  63.         if (iResult == SOCKET_ERROR) {
  64.                 wprintf(L"connect failed with error: %d\n", WSAGetLastError());
  65.                 closesocket(ConnectSocket);
  66.                 WSACleanup();
  67.                 return 1;
  68.         }
  69.  
  70.         //----------------------
  71.         // Send an initial buffer
  72.         iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
  73.         if (iResult == SOCKET_ERROR) {
  74.                 wprintf(L"send failed with error: %d\n", WSAGetLastError());
  75.                 closesocket(ConnectSocket);
  76.                 WSACleanup();
  77.                 return 1;
  78.         }
  79.  
  80.         printf("Bytes Sent: %d\n", iResult);
  81.  
  82.         // shutdown the connection since no more data will be sent
  83.         iResult = shutdown(ConnectSocket, SD_SEND);
  84.         if (iResult == SOCKET_ERROR) {
  85.                 wprintf(L"shutdown failed with error: %d\n", WSAGetLastError());
  86.                 closesocket(ConnectSocket);
  87.                 WSACleanup();
  88.                 return 1;
  89.         }
  90.  
  91.         // Receive until the peer closes the connection
  92.         do {
  93.  
  94.                 iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
  95.                 if (iResult > 0)
  96.                         wprintf(L"Bytes received: %d\n", iResult);
  97.                 else if (iResult == 0)
  98.                         wprintf(L"Connection closed\n");
  99.                 else
  100.                         wprintf(L"recv failed with error: %d\n", WSAGetLastError());
  101.  
  102.         } while (iResult > 0);
  103.  
  104.  
  105.         // close the socket
  106.         iResult = closesocket(ConnectSocket);
  107.         if (iResult == SOCKET_ERROR) {
  108.                 wprintf(L"close failed with error: %d\n", WSAGetLastError());
  109.                 WSACleanup();
  110.                 return 1;
  111.         }
  112.  
  113.         WSACleanup();
  114.         return 0;
  115.     std::cout << "Hello World!\n"
  116. }
  117.  
  118. // Run program: Ctrl + F5 or Debug > Start Without Debugging menu
  119. // Debug program: F5 or Debug > Start Debugging menu
  120.  
  121. // Tips for Getting Started: 
  122. //   1. Use the Solution Explorer window to add/manage files
  123. //   2. Use the Team Explorer window to connect to source control
  124. //   3. Use the Output window to see build output and other messages
  125. //   4. Use the Error List window to view errors
  126. //   5. Go to Project > Add New Item to create new code files, or Project > Add Existing Item to add existing code files to the project
  127. //   6. In the future, to open this project again, go to File > Open > Project and select the .sln file
  128.  

Transcribing from C++ to VBA

So Windows API functions are 99.9% designed to be used by C++ developers. VBA Developers must find function declarations with compatible signatures that match. for this Google is your friend but after a while one can get familiar with passing pointers by using VarPtr, StrPtr and ObjPtr.

  1. Option Explicit
  2. Option Private Module
  3.  
  4.  
  5. 'reference Windows Sockets 2 - Windows applications _ Microsoft Docs
  6. 'http://msdn.microsoft.com/en-us/library/windows/desktop/ms740673(v=vs.85).aspx
  7. Private Const INVALID_SOCKET = -1
  8. Private Const WSADESCRIPTION_LEN = 256
  9. Private Const SOCKET_ERROR As Long = -1 'const #define SOCKET_ERROR            (-1)
  10.  
  11. Private Enum AF
  12.     AF_UNSPEC = 0
  13.     AF_INET = 2
  14.     AF_IPX = 6
  15.     AF_APPLETALK = 16
  16.     AF_NETBIOS = 17
  17.     AF_INET6 = 23
  18.     AF_IRDA = 26
  19.     AF_BTH = 32
  20. End Enum
  21.  
  22. Private Enum sock_type
  23.     SOCK_STREAM = 1
  24.     SOCK_DGRAM = 2
  25.     SOCK_RAW = 3
  26.     SOCK_RDM = 4
  27.     SOCK_SEQPACKET = 5
  28. End Enum
  29.  
  30. Private Enum Protocol
  31.     IPPROTO_ICMP = 1
  32.     IPPROTO_IGMP = 2
  33.     BTHPROTO_RFCOMM = 3
  34.     IPPROTO_TCP = 6
  35.     IPPROTO_UDP = 17
  36.     IPPROTO_ICMPV6 = 58
  37.     IPPROTO_RM = 113
  38. End Enum
  39.  
  40. 'Private Type sockaddr
  41. '    sa_family As Integer
  42. '    sa_data(0 To 13) As Byte
  43. 'End Type
  44.  
  45. Private Type sockaddr_in
  46.     sin_family As Integer
  47.     sin_port(0 To 1) As Byte
  48.     sin_addr(0 To 3) As Byte
  49.     sin_zero(0 To 7) As Byte
  50. End Type
  51.  
  52.  
  53. 'typedef UINT_PTR        SOCKET;
  54. Private Type udtSOCKET
  55.     pointer As Long
  56. End Type
  57.  
  58.  
  59.  
  60. ' typedef struct WSAData {
  61. '  WORD           wVersion;
  62. '  WORD           wHighVersion;
  63. '  char           szDescription[WSADESCRIPTION_LEN+1];
  64. '  char           szSystemStatus[WSASYS_STATUS_LEN+1];
  65. '  unsigned short iMaxSockets;
  66. '  unsigned short iMaxUdpDg;
  67. '  char FAR       *lpVendorInfo;
  68. '} WSADATA, *LPWSADATA;
  69.  
  70. Private Type udtWSADATA
  71.     wVersion As Integer
  72.     wHighVersion As Integer
  73.     szDescription(0 To WSADESCRIPTION_LEN) As Byte
  74.     szSystemStatus(0 To WSADESCRIPTION_LEN) As Byte
  75.     iMaxSockets As Integer
  76.     iMaxUdpDg As Integer
  77.     lpVendorInfo As Long
  78. End Type
  79.  
  80. 'int errorno = WSAGetLastError()
  81. Private Declare Function WSAGetLastError Lib "Ws2_32" () As Integer
  82.  
  83. '   int WSAStartup(
  84. '  __in   WORD wVersionRequested,
  85. '  __out  LPWSADATA lpWSAData
  86. ');
  87. Private Declare Function WSAStartup Lib "Ws2_32" _
  88.     (ByVal wVersionRequested As IntegerByRef lpWSAData As udtWSADATA) As winsockErrorCodes2
  89.  
  90.  
  91. '    SOCKET WSAAPI socket(
  92. '  __in  int af,
  93. '  __in  int type,
  94. '  __in  int protocol
  95. ');
  96.  
  97. Private Declare Function ws2_socket Lib "Ws2_32" Alias "socket" _
  98.     (ByVal AF As LongByVal stype As LongByVal Protocol As LongAs LongPtr
  99.  
  100. Private Declare Function ws2_closesocket Lib "Ws2_32" Alias "closesocket" _
  101.     (ByVal socket As LongAs Long
  102.  
  103. 'int recv(
  104. '  SOCKET s,
  105. '  char   *buf,
  106. '  int    len,
  107. '  int    flags
  108. ');
  109. Private Declare Function ws2_recv Lib "Ws2_32" Alias "recv" _
  110.     (ByVal socket As LongByVal buf As LongPtr, _
  111.      ByVal length As LongByVal flags As LongAs Long
  112.  
  113. 'int WSAAPI connect(
  114. '  SOCKET         s,
  115. '  const sockaddr *name,
  116. '  int            namelen
  117. ');
  118.  
  119. Private Declare Function ws2_connect Lib "Ws2_32" Alias "connect" _
  120.     (ByVal As LongPtr, ByRef name As sockaddr_in, ByVal namelen As LongAs Long
  121.  
  122. 'int WSAAPI send(
  123. '  SOCKET     s,
  124. '  const char *buf,
  125. '  int        len,
  126. '  int        flags
  127. ');
  128. Private Declare Function ws2_send Lib "Ws2_32" Alias "send" _
  129.     (ByVal As LongPtr, ByVal buf As LongPtr, ByVal buflen As LongByVal flags As LongAs Long
  130.  
  131.  
  132. Private Declare Function ws2_shutdown Lib "Ws2_32" Alias "shutdown" _
  133.         (ByVal As LongByVal how As LongAs Long
  134.  
  135. Private Declare Sub WSACleanup Lib "Ws2_32" ()
  136.  
  137. Private Enum eShutdownConstants
  138.     SD_RECEIVE = 0  '#define SD_RECEIVE      0x00
  139.     SD_SEND = 1     '#define SD_SEND         0x01
  140.     SD_BOTH = 2     '#define SD_BOTH         0x02
  141. End Enum
  142.  
  143. Private Sub TestWS2SendAndReceive()
  144.  
  145.     Dim sResponse As String
  146.     If WS2SendAndReceive("KEYS *" & vbCrLf, sResponse) Then
  147.         Debug.Print VBA.Join(RedisResponseToTypedVariable(sResponse), ";")
  148.     End If
  149.  
  150.     If WS2SendAndReceive("GET foo" & vbCrLf, sResponse) Then
  151.         Debug.Print RedisResponseToTypedVariable(sResponse)
  152.     End If
  153.  
  154.     If WS2SendAndReceive("SET baz Barry" & vbCrLf, sResponse) Then
  155.         Debug.Assert RedisResponseToTypedVariable(sResponse) = "OK"
  156.     End If
  157.  
  158.     If WS2SendAndReceive("SET foo BAR" & vbCrLf, sResponse) Then
  159.         Debug.Assert RedisResponseToTypedVariable(sResponse) = "OK"
  160.     End If
  161.  
  162.     If WS2SendAndReceive("DEL baz" & vbCrLf, sResponse) Then
  163.         Debug.Print RedisResponseToTypedVariable(sResponse)
  164.     End If
  165.  
  166.  
  167.     If WS2SendAndReceive("GET baz" & vbCrLf, sResponse) Then
  168.         Debug.Print RedisResponseToTypedVariable(sResponse)
  169.     End If
  170.  
  171.     If WS2SendAndReceive("GET foo" & vbCrLf, sResponse) Then
  172.         Debug.Print RedisResponseToTypedVariable(sResponse)
  173.     End If
  174.  
  175.  
  176.     If WS2SendAndReceive("KEYS *" & vbCrLf, sResponse) Then
  177.         Debug.Print VBA.Join(RedisResponseToTypedVariable(sResponse), ";")
  178.     End If
  179.  
  180.  
  181.     If WS2SendAndReceive("SET count 0" & vbCrLf, sResponse) Then
  182.         Debug.Assert RedisResponseToTypedVariable(sResponse) = "OK"
  183.     End If
  184.  
  185.     If WS2SendAndReceive("INCR count" & vbCrLf, sResponse) Then
  186.         Debug.Assert RedisResponseToTypedVariable(sResponse) = "1"
  187.     End If
  188.  
  189.  
  190.  
  191.  
  192. End Sub
  193.  
  194. Public Function WS2SendAndReceive(ByVal sCommand As StringByRef psResponse As StringAs Boolean
  195.     'https://docs.microsoft.com/en-gb/windows/desktop/api/winsock/nf-winsock-recv
  196.  
  197.     psResponse = ""
  198.     '//----------------------
  199.     '// Declare and initialize variables.
  200.     Dim iResult As Integer : iResult = 0
  201.     Dim wsaData As udtWSADATA
  202.  
  203.     Dim ConnectSocket As LongPtr
  204.  
  205.     Dim clientService As sockaddr_in
  206.  
  207.     Dim sendBuf() As Byte
  208.     sendBuf = StrConv(sCommand, vbFromUnicode)
  209.  
  210.     Const recvbuflen As Long = 512
  211.     Dim recvbuf(0 To recvbuflen - 1) As Byte
  212.  
  213.     '//----------------------
  214.     '// Initialize Winsock
  215.     Dim eResult As winsockErrorCodes2
  216.     eResult = WSAStartup(&H202, wsaData)
  217.     If eResult <> 0 Then
  218.         Debug.Print "WSAStartup failed with error: " & eResult
  219.         WS2SendAndReceive = False
  220.         GoTo SingleExit
  221.     End If
  222.  
  223.  
  224.     '//----------------------
  225.     '// Create a SOCKET for connecting to server
  226.     ConnectSocket = ws2_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
  227.     If ConnectSocket = INVALID_SOCKET Then
  228.         Dim eLastError As winsockErrorCodes2
  229.         eLastError = WSAGetLastError()
  230.         Debug.Print "socket failed with error: " & eLastError
  231.         Call WSACleanup
  232.         WS2SendAndReceive = False
  233.         GoTo SingleExit
  234.     End If
  235.  
  236.  
  237.     '//----------------------
  238.     '// The sockaddr_in structure specifies the address family,
  239.     '// IP address, and port of the server to be connected to.
  240.     clientService.sin_family = AF_INET
  241.  
  242.     clientService.sin_addr(0) = 127
  243.     clientService.sin_addr(1) = 0
  244.     clientService.sin_addr(2) = 0
  245.     clientService.sin_addr(3) = 1
  246.  
  247.     clientService.sin_port(1) = 235 '* 6379
  248.     clientService.sin_port(0) = 24
  249.  
  250.     '//----------------------
  251.     '// Connect to server.
  252.  
  253.     iResult = ws2_connect(ConnectSocket, clientService, LenB(clientService))
  254.     If (iResult = SOCKET_ERROR) Then
  255.  
  256.         eLastError = WSAGetLastError()
  257.  
  258.         Debug.Print "connect failed with error: " & eLastError
  259.         Call ws2_closesocket(ConnectSocket)
  260.         Call WSACleanup
  261.         WS2SendAndReceive = False
  262.         GoTo SingleExit
  263.     End If
  264.  
  265.     '//----------------------
  266.     '// Send an initial buffer
  267.     Dim sendbuflen As Long
  268.     sendbuflen = UBound(sendBuf) - LBound(sendBuf) + 1
  269.     iResult = ws2_send(ConnectSocket, VarPtr(sendBuf(0)), sendbuflen, 0)
  270.     If (iResult = SOCKET_ERROR) Then
  271.         eLastError = WSAGetLastError()
  272.         Debug.Print "send failed with error: " & eLastError
  273.  
  274.         Call ws2_closesocket(ConnectSocket)
  275.         Call WSACleanup
  276.         WS2SendAndReceive = False
  277.         GoTo SingleExit
  278.     End If
  279.  
  280.     'Debug.Print "Bytes Sent: ", iResult
  281.  
  282.     '// shutdown the connection since no more data will be sent
  283.     iResult = ws2_shutdown(ConnectSocket, SD_SEND)
  284.     If (iResult = SOCKET_ERROR) Then
  285.  
  286.         eLastError = WSAGetLastError()
  287.         Debug.Print "shutdown failed with error: " & eLastError
  288.  
  289.         Call ws2_closesocket(ConnectSocket)
  290.         Call WSACleanup
  291.         WS2SendAndReceive = False
  292.         GoTo SingleExit
  293.     End If
  294.  
  295.     ' receive only one message (TODO handle when buffer is not large enough)
  296.  
  297.     iResult = ws2_recv(ConnectSocket, VarPtr(recvbuf(0)), recvbuflen, 0)
  298.     If (iResult > 0) Then
  299.         'Debug.Print "Bytes received: ", iResult
  300.     ElseIf (iResult = 0) Then
  301.         Debug.Print "Connection closed"
  302.         WS2SendAndReceive = False
  303.         Call ws2_closesocket(ConnectSocket)
  304.         Call WSACleanup
  305.         GoTo SingleExit
  306.     Else
  307.         eLastError = WSAGetLastError()
  308.         Debug.Print "recv failed with error: " & eLastError
  309.     End If
  310.  
  311.     psResponse = Left$(StrConv(recvbuf, vbUnicode), iResult)
  312.  
  313.     'Debug.Print psResponse
  314.  
  315.     '// close the socket
  316.     iResult = ws2_closesocket(ConnectSocket)
  317.     If (iResult = SOCKET_ERROR) Then
  318.  
  319.         eLastError = WSAGetLastError()
  320.         Debug.Print "close failed with error: " & eLastError
  321.  
  322.         Call WSACleanup
  323.         WS2SendAndReceive = False
  324.         GoTo SingleExit
  325.     End If
  326.  
  327.     Call WSACleanup
  328.     WS2SendAndReceive = True
  329.  
  330. SingleExit:
  331.     Exit Function
  332. ErrHand:
  333.  
  334. End Function
  335.  
  336. Public Function RedisResponseToTypedVariable(ByVal sResponse As String)
  337.  
  338.     Dim lTotalLength As Long
  339.     lTotalLength = Len(sResponse)
  340.     Debug.Assert lTotalLength > 0
  341.  
  342.     Dim vSplitResponse As Variant
  343.     vSplitResponse = VBA.Split(sResponse, vbCrLf)
  344.  
  345.     Dim lReponseLineCount As Long
  346.     lReponseLineCount = UBound(vSplitResponse) - LBound(vSplitResponse)
  347.  
  348.     Select Case Left(sResponse, 1)
  349.         Case "$"
  350.  
  351.             RedisResponseToTypedVariable = vSplitResponse(1)
  352.  
  353.         Case "+"
  354.             RedisResponseToTypedVariable = Mid$(vSplitResponse(0), 2)
  355.  
  356.         Case ":"
  357.             '* response is an integer
  358.             RedisResponseToTypedVariable = CLng(Mid$(vSplitResponse(0), 2))
  359.  
  360.         Case "-"
  361.             '* response is an error
  362.             Err.Raise vbObjectError, , Mid$(vSplitResponse(0), 2)
  363.         'Stop
  364.         Case "*"
  365.             '* multiple responses, build an array to return
  366.             Dim lResponseCount As Long
  367.             lResponseCount = CLng(Mid$(vSplitResponse(0), 2))
  368.             If lResponseCount > 0 Then
  369.                 Debug.Assert lResponseCount = (lReponseLineCount - 1) / 2
  370.         ReDim vReturn(0 To lResponseCount - 1)
  371.                 Dim lLoop As Long
  372.                 For lLoop = 0 To lResponseCount - 1
  373.                     vReturn(lLoop) = vSplitResponse((lLoop + 1) * 2)
  374.                 Next lLoop
  375.             End If
  376.             RedisResponseToTypedVariable = vReturn
  377.  
  378.         Case Else
  379.             Stop  '* this should not happen
  380.     End Select
  381.  
  382. End Function
  383.  
  384.  

Appendix A - Useful Enums

So it turned out I did not need these enumerations as such but I may in the future so parking here for safekeeping

modGlobalEnums Standard Module

  1. Option Explicit
  2.  
  3. Public Enum sckState
  4.     sckClosed = 0            ' Default. Closed
  5.     sckOpen = 1              ' Open
  6.     sckListening = 2         ' Listening
  7.     sckConnectionPending = 3 ' Connection pending
  8.     sckResolvingHost = 4     ' Resolving host
  9.     sckHostResolved = 5      ' Host resolved
  10.     sckConnecting = 6        ' Connecting
  11.     sckConnected = 7         ' Connected
  12.     sckClosing = 8           ' Peer is closing the connection
  13.     sckError = 9             ' Error
  14.  
  15. End Enum
  16.  
  17. ''https://docs.microsoft.com/en-gb/windows/desktop/WinSock/windows-sockets-error-codes-2
  18.  
  19. Public Enum winsockErrorCodes2
  20.     WSA_SUCCESS = 0
  21.     WSA_INVALID_HANDLE = 6
  22.     WSA_NOT_ENOUGH_MEMORY = 8
  23.     WSA_INVALID_PARAMETER = 87
  24.     WSA_OPERATION_ABORTED = 995
  25.     WSA_IO_INCOMPLETE = 996
  26.     WSA_IO_PENDING = 997
  27.     WSA_E_INTR = 10004
  28.     WSA_E_BADF = 10009
  29.     WSA_E_ACCES = 10013
  30.     WSA_E_FAULT = 10014
  31.     WSA_E_INVAL = 10022
  32.     WSA_E_MFILE = 10024
  33.     WSA_E_WOULDBLOCK = 10035
  34.     WSA_E_INPROGRESS = 10036
  35.     WSA_E_ALREADY = 10037
  36.     WSA_E_NOTSOCK = 10038
  37.     WSA_E_DESTADDRREQ = 10039
  38.     WSA_E_MSGSIZE = 10040         '* Message too long.
  39.     WSA_E_PROTOTYPE = 10041
  40.     WSA_E_NOPROTOOPT = 10042
  41.     WSA_E_PROTONOSUPPORT = 10043
  42.     WSA_E_SOCKTNOSUPPORT = 10044
  43.     WSA_E_OPNOTSUPP = 10045
  44.     WSA_E_PFNOSUPPORT = 10046
  45.     WSA_E_AFNOSUPPORT = 10047
  46.     WSA_E_ADDRINUSE = 10048
  47.     WSA_E_ADDRNOTAVAIL = 10049
  48.     WSA_E_NETDOWN = 10050
  49.     WSA_E_NETUNREACH = 10051
  50.     WSA_E_NETRESET = 10052
  51.     WSA_E_CONNABORTED = 10053
  52.     WSA_E_CONNRESET = 10054       '* Connection reset by peer.
  53.     WSA_E_NOBUFS = 10055
  54.     WSA_E_ISCONN = 10056
  55.     WSA_E_NOTCONN = 10057
  56.     WSA_E_SHUTDOWN = 10058
  57.     WSA_E_TOOMANYREFS = 10059
  58.     WSA_E_TIMEDOUT = 10060
  59.     WSA_E_CONNREFUSED = 10061
  60.     WSA_E_LOOP = 10062
  61.     WSA_E_NAMETOOLONG = 10063
  62.     WSA_E_HOSTDOWN = 10064
  63.     WSA_E_HOSTUNREACH = 10065
  64.     WSA_E_NOTEMPTY = 10066
  65.     WSA_E_PROCLIM = 10067
  66.     WSA_E_USERS = 10068
  67.     WSA_E_DQUOT = 10069
  68.     WSA_E_STALE = 10070
  69.     WSA_E_REMOTE = 10071
  70.     WSASYSNOTREADY = 10091
  71.     WSAVERNOTSUPPORTED = 10092
  72.     WSANOTINITIALISED = 10093
  73.     WSA_E_DISCON = 10101
  74.     WSA_E_NOMORE = 10102
  75.     WSA_E_CANCELLED = 10103
  76.     WSA_E_INVALIDPROCTABLE = 10104
  77.     WSA_E_INVALIDPROVIDER = 10105
  78.     WSA_E_PROVIDERFAILEDINIT = 10106
  79.     WSASYSCALLFAILURE = 10107
  80.     WSASERVICE_NOT_FOUND = 10108
  81.     WSATYPE_NOT_FOUND = 10109
  82.     WSA_E_NO_MORE = 10110
  83.     WSA_E_CANCELLED_2 = 10111
  84.     WSA_E_REFUSED = 10112
  85.     WSAHOST_NOT_FOUND = 11001
  86.     WSATRY_AGAIN = 11002
  87.     WSANO_RECOVERY = 11003
  88.     WSANO_DATA = 11004
  89.     WSA_QOS_RECEIVERS = 11005
  90.     WSA_QOS_SENDERS = 11006
  91.     WSA_QOS_NO_SENDERS = 11007
  92.     WSA_QOS_NO_RECEIVERS = 11008
  93.     WSA_QOS_REQUEST_CONFIRMED = 11009
  94.     WSA_QOS_ADMISSION_FAILURE = 11010
  95.     WSA_QOS_POLICY_FAILURE = 11011
  96.     WSA_QOS_BAD_STYLE = 11012
  97.     WSA_QOS_BAD_OBJECT = 11013
  98.     WSA_QOS_TRAFFIC_CTRL_ERROR = 11014
  99.     WSA_QOS_GENERIC_ERROR = 11015
  100.     WSA_QOS_ESERVICETYPE = 11016
  101.     WSA_QOS_EFLOWSPEC = 11017
  102.     WSA_QOS_EPROVSPECBUF = 11018
  103.     WSA_QOS_EFILTERSTYLE = 11019
  104.     WSA_QOS_EFILTERTYPE = 11020
  105.     WSA_QOS_EFILTERCOUNT = 11021
  106.     WSA_QOS_EOBJLENGTH = 11022
  107.     WSA_QOS_EFLOWCOUNT = 11023
  108.     WSA_QOS_EUNKOWNPSOBJ = 11024
  109.     WSA_QOS_EPOLICYOBJ = 11025
  110.     WSA_QOS_EFLOWDESC = 11026
  111.     WSA_QOS_EPSFLOWSPEC = 11027
  112.     WSA_QOS_EPSFILTERSPEC = 11028
  113.     WSA_QOS_ESDMODEOBJ = 11029
  114.     WSA_QOS_ESHAPERATEOBJ = 11030
  115.     WSA_QOS_RESERVED_PETYPE = 11031
  116. End Enum