Migrating WebSocketServer to SuperSocket 2.0
My old SuperSocket 1.6 application had a WebSocketServer<TSesssion> which I am moving to SuperSocket 2.0. I am struggling a bit to see how to register my JsonAsyncCommand<TAppSession, TJsonObject> together with a MultipleServerHostBuilder, preferably I would like to call AddCommandAssembly as I have many commands registered within the same namespace.
In my startup, I configure the server like this:
.AddWebSocketServer<Backoffice.BackofficeWebSocketServer>(builder =>
{
builder
.UseCommand((commandOptions) =>
{
var assembly = Assembly.GetAssembly(typeof(Backoffice.BackofficeWebSocketServer));
commandOptions.AddCommandAssembly(assembly);
})
.UseSession<Backoffice.BackofficeSession>()
.ConfigureServerOptions((ctx, config) =>
{
return config.GetSection("BackofficeServer");
})
.UseInProcSessionContainer();
})
But I get the exception The package type TPackageInfo should implement the interface IKeyedPackageInfo`1. when starting the application. I assume this is because WebSocketPackage does not implement IKeyedPackagageInfo. I have looked a bit at the tests and samples but I don't get how I am supposed to configure this correctly to pick up the commands and be able to parse the same message format from SuperSocker 1.6 (the application that talks with this is an application using WebSocket4Net). If you could give me some pointers in where I should look or what I am doing wrong, that would be much appreciated.
Managed to get it working eventually. There were a few things I needed to get in place. The snippet I wrote above now looks like this:
.AddWebSocketServer<Backoffice.BackofficeWebSocketServer>(builder =>
{
builder
.UseCommand<BaseRequestInfo, JsonRequestPackageConverter>(commandOptions =>
{
commandOptions.AddCommand<DOBUSINESS>();
commandOptions.AddGlobalCommandFilter<TokenFilter>();
})
.UseSession<Backoffice.BackofficeSession>()
.ConfigureServerOptions((ctx, config) =>
{
return config.GetSection("BackofficeServer");
})
.UseInProcSessionContainer();
})
What is essential here is that BaseRequestInfo is basically containing the non-deserialized data and is also implementing an IStringPackage that is required by the JsonCommands:
public class BaseRequestInfo : IKeyedPackageInfo<string>, IStringPackage
{
public string Key { get; set; }
public string Token { get; set; }
public string Body { get; set; }
}
The JsonRequestPackageConverter is copied from how 1.6 parses these messages: https://github.com/kerryjiang/SuperSocket/blob/v1.6/Protocols/WebSocket/SubProtocol/BasicSubCommandParser.cs:
public class JsonRequestPackageConverter : IPackageMapper<WebSocketPackage, BaseRequestInfo>
{
public BaseRequestInfo Map(WebSocketPackage package)
{
if (string.IsNullOrWhiteSpace(package.Message))
return null;
var source = package.Message;
var cmd = source.Trim();
int pos = cmd.IndexOf(' ');
string name;
string param;
if (pos > 0)
{
name = cmd.Substring(0, pos);
param = cmd.Substring(pos + 1);
}
else
{
name = cmd;
param = string.Empty;
}
pos = name.IndexOf('-');
string token = string.Empty;
if (pos > 0)
{
token = name.Substring(pos + 1);
name = name.Substring(0, pos);
}
return new BaseRequestInfo
{
Key = name,
Token = token,
Body = param
};
}
}
Another important part is the TokenFilter which is a command filter that will fetch the token from the incoming command. If the token is present, the outgoing packet must contain this:
public class TokenFilter : AsyncCommandFilterAttribute
{
public override ValueTask<bool> OnCommandExecutingAsync(CommandExecutingContext commandContext)
{
var session = (ITokenizedWebSocketSession)commandContext.Session;
var package = (BaseRequestInfo)commandContext.Package;
session.CurrentToken = package.Token;
return new ValueTask<bool>(true);
}
public override ValueTask OnCommandExecutedAsync(CommandExecutingContext commandContext)
{
return new ValueTask(Task.CompletedTask);
}
}
The token is stored in an ITokenizedWebSocketSession that only exposes a getter/setter for the string property CurrentToken. I can then respond to the messages using a SendJsonMessageAsync that resides in a wrapper I have around JsonAsyncCommand<TWebSocketSession, TJsonCommandInfo> where TWebSocketSession : WebSocketSession, IAppSession, ITokenizedWebSocketSession:
public async Task SendJsonMessageAsync(TWebSocketSession session, object content)
{
var commandName = GetType().Name;
var serializedJson = JsonConvert.SerializeObject(content);
var stringResponse = GetJsonMessage(commandName, session.CurrentToken, serializedJson) + Environment.NewLine;
await session.SendAsync(stringResponse);
}
private string GetJsonMessage(string commandName, string token, string json)
{
if (string.IsNullOrEmpty(token))
return string.Format(ResponseMessageFormatWithoutToken, commandName, json);
else
return string.Format(ResponseMessageFormatWithToken, commandName, token, json);
}
If there are any suggestions on how to stuff in a cleaner/more appropriate way (or even if some of the stuff I already wrote is present in SuperSocket 2.0 already), please let me know.
Yes, it is a missing part in SuperSocket .WebSocket 2.0. I will figure it out in the coming week.
I was using the older one
using this code
public partial class MainForm : Form
{
private static WebSocketServer wsServer = new WebSocketServer();
private async void MainForm_Load(object sender, EventArgs e)
{
await Task.Run(() =>
{
wsServer.Setup(8888);
wsServer.NewSessionConnected += WsServer_NewSessionConnected;
wsServer.NewMessageReceived += WsServer_NewMessageReceived;
wsServer.NewDataReceived += WsServer_NewDataReceived;
wsServer.SessionClosed += WsServer_SessionClosed;
wsServer.Start();
});
}
private void WsServer_NewSessionConnected(WebSocketSession session)
{
Task.Run(() =>
{
// processing data using session.SessionID
});
}
private void WsServer_NewDataReceived(WebSocketSession session, byte[] value)
{
Task.Run(() =>
{
// processing data using session.SessionID
});
}
private void WsServer_NewMessageReceived(WebSocketSession session, string value)
{
Task.Run(() =>
{
// processing data using session.SessionID
});
}
private void WsServer_SessionClosed(WebSocketSession session, SuperSocket.SocketBase.CloseReason value)
{
Task.Run(() =>
{
// processing data using session.SessionID
});
}
}
now I have used the new version with same code, by changing the namespace.
works fine but after approx 100 connections, the client is unable to connect gives this error
Network Name Deleted.
"Network Name Deleted"?
Could you show me the full stack trace of the exception?