Finding WebSocket Server memory leak - websocket

Hello guys i am currently testing a websocket server written in Asp .Net Core 2.0 and i believe i have a memory leak.I can not find it even though i have tried to dispose everything that might be a concern.The tests were done consecutively and the value of the ram is taken when it reaches stability.(this varies from 5 sec to 20 sec).
The occupied Ram is measured with plain Task Manager Monitor.The testing tool: Thor : https://github.com/observing/thor
The command : thor --amount [amount] ws://[HostIP (localhost)]:[portnumber]
Results:
Connections | RAM Consumed at the end of test (GB):
0 4.54
50 4.55
100 4.55
150 4.61
200 4.68
300 4.76
400 4.59
400 4.59
500 4.62
500 4.65
550 4.65
The WebSocket Server:
SocketMiddleware -used by the appbuilder:
public class SocketMiddleware
{
public byte[] ToSegment(string message) => System.Text.Encoding.UTF8.GetBytes(message);
ClientTracker clientTracker; //the socket clients tracker this is the object we're speaking of
RequestDelegate next;
public SocketMiddleware(ClientTracker tracker,RequestDelegate del)
{
this.clientTracker=tracker;
this.next=del;
}
public async Task Invoke(HttpContext context)
{
if(!context.WebSockets.IsWebSocketRequest)
{
await this.next.Invoke(context);
return;
}
await this.clientTracker.AddClient(context.WebSockets);
}
}
SocketTracker- this is the suspect which is dealing with all the opened sockets
public class ClientTracker
{
ConcurrentDictionary<string, Client> clientMap = new ConcurrentDictionary<string, Client>();
public string CreateConnectionID() => Guid.NewGuid().ToString();
public string GetIDOfSocket(WebSocket socket) => this.clientMap.First(x => x.Value.webSocket.Equals(socket)).Key;
public Client GetClientByID(string id)
{
this.clientMap.TryGetValue(id, out Client client);
return client;
}
public async Task AddClient(WebSocketManager manager)
{
using (WebSocket socket = await manager.AcceptWebSocketAsync())
{
Client newClient = Client.CreateClient(socket, CreateConnectionID());
if(clientMap.TryAdd(newClient.clientID, newClient))
{
await ReceiveMessage(newClient);
}
}
}
public async Task ReceiveMessage(Client client)
{
while (client.webSocket.State == WebSocketState.Open)
{
WebSocketReceiveResult result = await client.ReceiveResult();
//dosomething with result...
if (result.MessageType == WebSocketMessageType.Close)
{
await client.webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closed", CancellationToken.None);
break;
}
//send custom message
await client.SendMessage("lala");
}
}
}
Client- socketWrapper which performs all required operations on the socket and stores temporary data to be used by the tracker
public class Client
{
//Fields
public readonly WebSocket webSocket;
public readonly string clientID;
public StringBuilder tempData;
//Auxiliary
private const int BufferSize = 1024 * 4;
public static Client CreateClient(WebSocket socket, string id)
{
Client client = new Client(socket, id);
return client;
}
public Client(WebSocket socket, string id)
{
this.webSocket = socket;
this.clientID = id;
tempData = new StringBuilder();
}
public async Task<WebSocketReceiveResult> ReceiveResult()
{
tempData.Clear();
ArraySegment<byte> segment = new ArraySegment<byte>(new byte[BufferSize]);
WebSocketReceiveResult result = await this.webSocket.ReceiveAsync(segment, CancellationToken.None);
tempData.Append(BitConverter.ToString(segment.Array));
return result;
}
public async Task SendMessage(string message)
{
byte[] bytes = Encoding.UTF8.GetBytes(message);
await this.webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<ClientTracker>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseWebSockets();
app.UseMiddleware<SocketMiddleware>();
}
P.S:
The server was not closed between requests.Could it be the concurrent dictionary ?Besides it, the reference of the Client is cleaned ,the Client is disposed,and the socket is closed.The string builder can not be disposed,and the Tracker/Middleware live as long as the app lives.

Related

Troubleshooting MassTransit request client when using RoutingSlipRequestProxy/RoutingSlipResponseProxy for handling courier activities

Can someone give a working example in ASP.NET Core (with DI) of using RequestClient with RoutingSlipRequestProxy/RoutingSlipResponseProxy? I am trying the following code but somehow my message goes into skipped queue and I never get a reply in the controller action. The code in CreateESimOrderCommandResponseConsumer never gets executed:
public class CreateESimOrderCommandConsumer : RoutingSlipRequestProxy<CreateESimOrderCommand>
{
protected override Task BuildRoutingSlip(RoutingSlipBuilder builder, ConsumeContext<CreateESimOrderCommand> request)
{
builder.AddActivity("OrderSaveToDb", QueueNames.GetActivityUri(nameof(OrderSaveToDbActivity)));
builder.AddActivity("CreatePreactiveSubscriber", QueueNames.GetActivityUri(nameof(CreatePreactiveSubscriberActivity)));
builder.AddActivity("OrderUpdateStatus", QueueNames.GetActivityUri(nameof(OrderUpdateStatusActivity)));
builder.SetVariables(new
{
ProfileId = request.Message.ESimCatalogItemProfileId,
});
return Task.CompletedTask;
}
}
public class CreateESimOrderCommandResponseConsumer : RoutingSlipResponseProxy<CreateESimOrderCommand, SubscriberCreationRequested>
{
protected override Task<SubscriberCreationRequested> CreateResponseMessage(ConsumeContext<RoutingSlipCompleted> context, CreateESimOrderCommand request)
{
SubscriberCreationRequested subscriberCreationRequestedImpl = new SubscriberCreationRequestedImpl(context.GetVariable<string>("PhoneNumber"));
return Task.FromResult(subscriberCreationRequestedImpl);
}
}
public interface SubscriberCreationRequested
{
public string PhoneNumber { get; }
}
public record SubscriberCreationRequestedImpl(string PhoneNumber): SubscriberCreationRequested;
public interface CreateESimOrderCommand
{
public int ESimCatalogItemProfileId { get; }
}
In program.cs
builder.Services.AddMassTransit(x =>
{
x.SetKebabCaseEndpointNameFormatter();
x.UsingRabbitMq((context, cfg) =>
{
cfg.AutoStart = true;
cfg.ConfigureEndpoints(context, KebabCaseEndpointNameFormatter.Instance);
cfg.Host("localhost", "/", h =>
{
h.Username("guest");
h.Password("guest");
});
});
x.AddRequestClient<CreateESimOrderCommandConsumer>();
x.AddRequestClient<CreateESimOrderCommandResponseConsumer>();
x.AddConsumersFromNamespaceContaining<CreateESimOrderCommandConsumer>();
x.AddActivitiesFromNamespaceContaining<ESimOrderSaveToDbActivity>();
});
In my asp.net core controller:
private readonly IRequestClient<CreateESimOrderCommand> requestCreateOrderRequestConsumerClient;
private readonly ILogger<ESimController> logger;
public ESimController(
IRequestClient<CreateESimOrderCommand> requestCreateOrderRequestConsumerClient,
ILogger<ESimController> logger)
{
this.logger = logger;
this.requestCreateOrderRequestConsumerClient = requestCreateOrderRequestConsumerClient;
}
[HttpPost]
public async Task<IActionResult> Generate(ESimGenerateModel eSimGenerateModel, CancellationToken cancellationToken)
{
var resp = await requestCreateOrderRequestConsumerClient.GetResponse<SubscriberCreationRequested>(new
{
ESimCatalogItemProfileId = eSimGenerateModel.ESimProfileId,
}, cancellationToken);
logger.LogInformation("Resp = {0}", resp.Message.PhoneNumber);
return RedirectToAction("Index");
}
The console logging shows that the message goes to SKIP queue:
[10:31:21.994 DBG] [] SEND rabbitmq://localhost/order-update-status_execute?bind=true 0c4f0000-2019-c2f6-cdaa-08db0a78061b MassTransit.Courier.Contracts.RoutingSlip [s:MassTransit.Messages]
[10:31:21.994 DBG] [] RECEIVE rabbitmq://localhost/create-crmpreactive-subscriber_execute 0c4f0000-2019-c2f6-cdaa-08db0a78061b MassTransit.Courier.Contracts.RoutingSlip [....].ESim.CourierActivities.CreatePreactiveSubscriberActivity(00:00:00.0552592) [s:MassTransit.Messages]
[10:31:22.137 DBG] [] SKIP rabbitmq://localhost/create-esim-order-command 0c4f0000-2019-c2f6-0750-08db0a780650 [s:MassTransit.Messages]
[10:31:22.140 DBG] [] SEND rabbitmq://localhost/create-esim-order-command 0c4f0000-2019-c2f6-0750-08db0a780650 MassTransit.Courier.Contracts.RoutingSlipCompleted [s:MassTransit.Messages]
[10:31:22.140 DBG] [] RECEIVE rabbitmq://localhost/order-update-status_execute 0c4f0000-2019-c2f6-cdaa-08db0a78061b MassTransit.Courier.Contracts.RoutingSlip [...].ESim.CourierActivities.OrderUpdateStatusActivity(00:00:00.1486087) [s:MassTransit.Messages]
For calling the request/response proxy consumer from a saga I came up with the following code:
public class ESimOrderStateMachine : MassTransitStateMachine<ESimOrderState>
{
static ESimOrderStateMachine()
{
MessageContracts.Initialize();
}
public State ESimOrderSubscriberPendingActivation { get; set; }
public Event<ESimCreateOrder> ESimOrderSubmittedEvent { get; set; }
public ESimOrderStateMachine(ILogger<ESimOrderStateMachine> logger)
{
Request(() => CreateCRMSubscriber);
InstanceState(m => m.CurrentState);
Event(() => ESimOrderSubmittedEvent);
Initially(
When(ESimOrderSubmittedEvent)
.Then(context =>
{
context.Saga.CorrelationId = context.Message.CorrelationId;
})
.Then(x => logger.LogInformation("ESim order submitted"))
.Request(CreateCRMSubscriber, context => context.Init<CreateESimOrderCommand>(new
{
ESimCatalogItemProfileId = context.Message.ESimCatalogItemProfileId,
}))
.TransitionTo(ESimOrderSubscriberPendingActivation)
);
During(ESimOrderSubscriberPendingActivation,
When(ESimOrderCancelRequestEvent)
.Finalize()
);
}
public Request<ESimOrderState, CreateESimOrderCommand, SubscriberCreationRequested> CreateCRMSubscriber { get; set; }
}
Not sure if this is the intended way to make a request using request/response proxies from saga, but when I execute the saga, I get a fault:
MassTransit.EventExecutionException: The ESimOrderSubmittedEvent<ESimCreateOrder> (Event) execution faulted
---> MassTransit.EventExecutionException: The ESimOrderSubmittedEvent<ESimCreateOrder> (Event) execution faulted
---> MassTransit.EventExecutionException: The ESimOrderSubmittedEvent<ESimCreateOrder> (Event) execution faulted
---> MassTransit.ConfigurationException: A request timeout was specified but no message scheduler was specified or available
at MassTransit.SagaStateMachine.RequestActivityImpl`3.SendRequest(BehaviorContext`1 context, SendTuple`1 sendTuple, Uri serviceAddress) in /_/src/MassTransit/SagaStateMachine/SagaStateMachine/Activities/RequestActivityImpl.cs:line 45
[...]
Both proxy consumers, the request and the response proxy, should be configured to use the same endpoint. You can use a ConsumerDefinition for each consumer and specify the same endpoint name in the constructor, or manually configure the endpoint. Both of these options are documented.

Vertx http post client runs forever

I have the following Vertx Route setup:
router.post("/api/apple/")
.handler(e -> {
e.response()
.putHeader("content-type", "application/json")
.setStatusCode(200)
.end("hello");
})
.failureHandler(ctx -> {
LOG.error("Error: "+ ctx.response().getStatusMessage());
ctx.response().end();
});
vertx.createHttpServer().requestHandler(router::accept)
.listen(config().getInteger("http.port", 8081), result -> {
if (result.succeeded()) {
LOG.info("result succeeded in my start method");
future.complete();
} else {
LOG.error("result failed");
future.fail(result.cause());
}
});
When I call this from my Java test client:
Async async = context.async();
io.vertx.core.http.HttpClient client = vertx.createHttpClient();
HttpClientRequest request = client.post(8081, "localhost", "/api/apple/", response -> {
async.complete();
LOG.info("Some callback {}",response.statusCode());
});
String body = "{'username':'www','password':'www'}";
request.putHeader("content-length", "1000");
request.putHeader("content-type", "application/x-www-form-urlencoded");
request.write(body);
request.end();
The client keeps running and then the client times out. Seems like it is not able to find the endpoint on localhost:8081/api/apple
You didn't deploy your verticle defining routes in the test scope. Here is a working snippet:
public class HttpServerVerticleTest extends VertxTestRunner {
private WebClient webClient;
private HttpServerVerticle httpServer;
private int port;
#Before
public void setUp(TestContext context) throws IOException {
port = 8081;
httpServer = new HttpServerVerticle(); // the verticle where your routes are registered
// NOTICE HERE
vertx.deployVerticle(httpServer, yourdeploymentOptions, context.asyncAssertSuccess());
webClient = WebClient.wrap(vertx.createHttpClient());
}
#After
public void tearDown(TestContext testContext) {
webClient.close();
vertx.close(testContext.asyncAssertSuccess());
}
#Test
public void test_my_post_method(TestContext testContext) {
Async http = testContext.async();
String body = "{'username':'www','password':'www'}";
webClient.post(port, "localhost", "/api/apple/")
//.putHeader("Authorization", JWT_TOKEN)
.putHeader("content-length", "1000");
.putHeader("content-type", "application/x-www-form-urlencoded");
.sendJson(Buffer.buffer(body.getBytes()), requestResponse -> {
if (requestResponse.succeeded()) {
testContext.assertTrue(requestResponse.result().statusCode() == HttpResponseStatus.OK.code());
testContext.assertTrue(requestResponse.result().body().getString().equals("hello"));
} else {
testContext.fail(requestResponse.cause());
}
http.complete();
});
}
}

Building a Future API on top of Netty

I want to build an API based on Futures (from java.util.concurrent) that is powered by a custom protocol on top of Netty (version 4). Basic idea is to write a simple library that would abstract the underlying Netty implementation and make it easier to make requests.
Using this library, one should be able to write something like this:
Request req = new Request(...);
Future<Response> responseFuture = new ServerIFace(host, port).call(req);
// For example, let's block until this future is resolved
Reponse res = responseFuture.get().getResult();
Underneath this code, a Netty client is connected
public class ServerIFace {
private Bootstrap bootstrap;
private EventLoopGroup workerGroup;
private String host;
private int port;
public ServerIFace(String host, int port) {
this.host = host;
this.port = port;
this.workerGroup = new NioEventLoopGroup();
bootstrap();
}
private void bootstrap() {
bootstrap = new Bootstrap();
bootstrap.group(workerGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ObjectEncoder());
ch.pipeline().addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(Response.class.getClassLoader())));
ch.pipeline().addLast("response", new ResponseReceiverChannelHandler());
}
});
}
public Future<Response> call(final Request request) throws InterruptedException {
CompletableFuture<Response> responseFuture = new CompletableFuture<>();
Channel ch = bootstrap.connect(host, port).sync().channel();
ch.writeAndFlush(request).addListener((f) -> {
if (f.isSuccess()) {
System.out.println("Wrote successfully");
} else {
f.cause().printStackTrace();
}
});
ChannelFuture closeFuture = ch.closeFuture();
// Have to 'convert' ChannelFuture to java.util.concurrent.Future
closeFuture.addListener((f) -> {
if (f.isSuccess()) {
// How to get this response?
Response response = ((ResponseReceiverChannelHandler) ch.pipeline().get("response")).getResponse();
responseFuture.complete(response);
} else {
f.cause().printStackTrace();
responseFuture.cancel(true);
}
ch.close();
}).sync();
return responseFuture;
}
}
Now, as you can see, in order to abstract Netty's inner ChannelFuture, I have to 'convert' it to Java's Future (I'm aware that ChannelFuture is derived from Future, but that information doesn't seem useful at this point).
Right now, I'm capturing this Response object in the last handler of my inbound part of the client pipeline, the ResponseReceiverChannelHandler.
public class ResponseReceiverChannelHandler extends ChannelInboundHandlerAdapter {
private Response response = null;
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
this.response = (Response)msg;
ctx.close();
}
public Response getResponse() {
return response;
}
}
Since I'm new to Netty and these things in general, I'm looking for a cleaner, thread-safe way of delivering this object to the API user.
Correct me if I'm wrong, but none of the Netty examples show how to achieve this, and most of the Client examples just print out whatever they get from Server.
Please note that my main goal here is to learn more about Netty, and that this code has no production purposes.
For the reference (although I don't think it's that relevant) here's the Server code.
public class Server {
public static class RequestProcessorHandler extends ChannelInboundHandlerAdapter {
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ChannelFuture future;
if (msg instanceof Request) {
Request req = (Request)msg;
Response res = some function of req
future = ctx.writeAndFlush(res);
} else {
future = ctx.writeAndFlush("Error, not a request!");
}
future.addListener((f) -> {
if (f.isSuccess()) {
System.out.println("Response sent!");
} else {
System.out.println("Response not sent!");
f.cause().printStackTrace();
}
});
}
}
public int port;
public Server(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(Request.class.getClassLoader())));
ch.pipeline().addLast(new ObjectEncoder());
// Not really shutting down this threadpool but it's ok for now
ch.pipeline().addLast(new DefaultEventExecutorGroup(2), new RequestProcessorHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new Server(port).run();
}
}

Http proxy in Netty websocket client to connect to internet

My application is running behind a corporate firewall and I need to use http proxy(http://theclientproxy.net:8080) to connect to internet
I have used the Netty client as below,
https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/http/websocketx/client
Code:
public final class WebSocketClient {
static final String URL = System.getProperty("url", "wss://127.0.0.1:8080/websocket");
public static void main(String[] args) throws Exception {
URI uri = new URI(URL);
String scheme = uri.getScheme() == null? "ws" : uri.getScheme();
final String host = uri.getHost() == null? "127.0.0.1" : uri.getHost();
final int port;
final boolean ssl = "wss".equalsIgnoreCase(scheme);
final SslContext sslCtx;
if (ssl) {
sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
}
EventLoopGroup group = new NioEventLoopGroup();
try {
final WebSocketClientHandler handler =
new WebSocketClientHandler(
WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()));
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addFirst(new HttpProxyHandler(new InetSocketAddress("theclientproxy.net", 8080) ) );
p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
}
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator(8192),
WebSocketClientCompressionHandler.INSTANCE,
handler);
}
});
Channel ch = b.connect(uri.getHost(), port).sync().channel();
handler.handshakeFuture().sync();
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String msg = console.readLine(); //THIS IS NULL IN DATA CENTER LOGS
if (msg == null) {
break;
} else if ("bye".equals(msg.toLowerCase())) {
ch.writeAndFlush(new CloseWebSocketFrame());
ch.closeFuture().sync();
break;
} else if ("ping".equals(msg.toLowerCase())) {
WebSocketFrame frame = new PingWebSocketFrame(Unpooled.wrappedBuffer(new byte[] { 8, 1, 8, 1 }));
ch.writeAndFlush(frame);
} else {
WebSocketFrame frame = new TextWebSocketFrame(msg);
ch.writeAndFlush(frame);
}
}
} finally {
group.shutdownGracefully();
}
Handler:
public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
private final WebSocketClientHandshaker handshaker;
private ChannelPromise handshakeFuture;
public WebSocketClientHandler(WebSocketClientHandshaker handshaker) {
this.handshaker = handshaker;
}
public ChannelFuture handshakeFuture() {
return handshakeFuture;
}
#Override
public void handlerAdded(ChannelHandlerContext ctx) {
handshakeFuture = ctx.newPromise();
}
#Override
public void channelActive(ChannelHandlerContext ctx) {
handshaker.handshake(ctx.channel());
}
#Override
public void channelInactive(ChannelHandlerContext ctx) {
System.out.println("WebSocket Client disconnected!");
}
#Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel ch = ctx.channel();
if (!handshaker.isHandshakeComplete()) {
try {
handshaker.finishHandshake(ch, (FullHttpResponse) msg);
System.out.println("WebSocket Client connected!");
handshakeFuture.setSuccess();
} catch (WebSocketHandshakeException e) {
System.out.println("WebSocket Client failed to connect");
handshakeFuture.setFailure(e);
}
return;
}
The application is able to connect to the websocket server endpoint from my local machine successfully.
But in the company datacenter where my application is deployed, I see the msg value is null and the websocket client is disconnected
Does that mean my connection is blocked at firewall? If that is the case then why did the statement "WebSocket Client connected!" is printed at all?
Thanks
The httpproxyhandler you used is correct
Just remove the BufferredReader code as mentioned below when deploying in linux, docker, etc:
Netty WebSocket Client Channel always gets inactive on Linux Server

Does web sockets only support 1 connection using IIS 8 Client

i have a MVC asp.net web app.
It is hosted on my local network on a Windows 8.1 32bit OS.
I have found that SignalR only supports 1 client connection.
So, I removed SignalR and switched to web sockets.
However, this did not solve the problem.
Am I accurate in stating that Web Sockets only supports 1 client connection on this OS and IIS 8 Client?
Is the solution to use IIS Express because I had problems getting this to work and in the end ended up rebuilding my PC.
Is there no workaround for this issue?
This is my code:
In my Global.cs file:
public static WebSocketCollection clients = new WebSocketCollection();
In my handler:
public class MicrosoftWebSockets : WebSocketHandler
{
private string name;
public override void OnOpen()
{
this.name = this.WebSocketContext.QueryString["chatName"];
MvcApplication.clients.Add(this);
//clients.Broadcast(name + " has connected.");
}
public void SendImage(byte[] jpeg)
{
this.Send(jpeg);
}
public override void OnMessage(string message)
{
MvcApplication.clients.Broadcast(string.Format("{0} said: {1}", name, message));
}
public override void OnClose()
{
MvcApplication.clients.Remove(this);
MvcApplication.clients.Broadcast(string.Format("{0} has gone away.", name));
}
}
In my HTML page:
var conversation = $('conversation');
var url = 'ws://192.168.0.13:80/SocketHandler.ashx?name=John Doe';
ws = new WebSocket(url);
ws.binaryType = 'arraybuffer';
ws.onerror = function (e) {
$('#divSystemMessage').html('Problem with connection: ' + e.message);
};
ws.onopen = function () {
$('#divSystemMessage').html('Client connected');
};
ws.onmessage = function (e) {
liveViewIndex = 0;
desktopImage.src = 'data:image/jpeg;base64,' + base64ArrayBuffer(e.data);
};
ws.onclose = function () {
$('#divSystemMessage').html('Closed connection!');
};
public class SocketHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
context.AcceptWebSocketRequest(new MicrosoftWebSockets());
}
public bool IsReusable
{
get
{
return false;
}
}
}

Resources