2022-04-09 22:34:05 +00:00
|
|
|
using System;
|
|
|
|
using System.Threading;
|
|
|
|
|
2022-04-10 16:06:56 +00:00
|
|
|
// todo: sending errors as fake close messages
|
|
|
|
|
2022-04-09 22:34:05 +00:00
|
|
|
namespace Hamakaze.WebSocket {
|
|
|
|
public class WsClient : IDisposable {
|
|
|
|
public WsConnection Connection { get; }
|
|
|
|
public bool IsRunning { get; private set; } = true;
|
|
|
|
|
|
|
|
private Thread ReadThread { get; }
|
|
|
|
private Action<WsMessage> MessageHandler { get; }
|
|
|
|
private Action<Exception> ExceptionHandler { get; }
|
|
|
|
|
|
|
|
private Mutex SendLock { get; }
|
2022-04-10 16:06:56 +00:00
|
|
|
private const int TIMEOUT = 60000;
|
2022-04-09 22:34:05 +00:00
|
|
|
|
|
|
|
public WsClient(
|
|
|
|
WsConnection connection,
|
|
|
|
Action<WsMessage> messageHandler,
|
|
|
|
Action<Exception> exceptionHandler
|
|
|
|
) {
|
|
|
|
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
|
|
|
MessageHandler = messageHandler ?? throw new ArgumentNullException(nameof(messageHandler));
|
|
|
|
ExceptionHandler = exceptionHandler ?? throw new ArgumentNullException(nameof(exceptionHandler));
|
|
|
|
|
|
|
|
SendLock = new();
|
|
|
|
|
|
|
|
ReadThread = new(ReadThreadBody) { IsBackground = true };
|
|
|
|
ReadThread.Start();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ReadThreadBody() {
|
|
|
|
try {
|
|
|
|
while(IsRunning)
|
|
|
|
MessageHandler(Connection.Receive());
|
|
|
|
} catch(Exception ex) {
|
|
|
|
IsRunning = false;
|
|
|
|
ExceptionHandler(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Send(string text) {
|
2022-04-10 16:06:56 +00:00
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Send(text);
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Send(object obj) {
|
|
|
|
if(obj == null)
|
|
|
|
throw new ArgumentNullException(nameof(obj));
|
|
|
|
|
2022-04-10 16:06:56 +00:00
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Send(obj.ToString());
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Send(ReadOnlySpan<byte> data) {
|
2022-04-10 16:06:56 +00:00
|
|
|
if(data == null)
|
|
|
|
throw new ArgumentNullException(nameof(data));
|
|
|
|
|
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Send(data);
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Send(byte[] buffer, int offset, int count) {
|
|
|
|
if(buffer == null)
|
|
|
|
throw new ArgumentNullException(nameof(buffer));
|
|
|
|
|
2022-04-10 16:06:56 +00:00
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Send(buffer.AsSpan(offset, count));
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Send(Action<WsBufferedSend> handler) {
|
|
|
|
if(handler == null)
|
|
|
|
throw new ArgumentNullException(nameof(handler));
|
|
|
|
|
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
using(WsBufferedSend bs = Connection.BeginBufferedSend())
|
|
|
|
handler(bs);
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Ping() {
|
2022-04-10 16:06:56 +00:00
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Ping();
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Ping(ReadOnlySpan<byte> data) {
|
2022-04-10 16:06:56 +00:00
|
|
|
if(data == null)
|
|
|
|
throw new ArgumentNullException(nameof(data));
|
|
|
|
|
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Ping(data);
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Ping(byte[] buffer, int offset, int length) {
|
|
|
|
if(buffer == null)
|
|
|
|
throw new ArgumentNullException(nameof(buffer));
|
|
|
|
|
2022-04-10 16:06:56 +00:00
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Ping(buffer.AsSpan(offset, length));
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Pong() {
|
2022-04-10 16:06:56 +00:00
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Pong();
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Pong(ReadOnlySpan<byte> data) {
|
2022-04-10 16:06:56 +00:00
|
|
|
if(data == null)
|
|
|
|
throw new ArgumentNullException(nameof(data));
|
|
|
|
|
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Pong(data);
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Pong(byte[] buffer, int offset, int length) {
|
|
|
|
if(buffer == null)
|
|
|
|
throw new ArgumentNullException(nameof(buffer));
|
|
|
|
|
2022-04-10 16:06:56 +00:00
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Pong(buffer.AsSpan(offset, length));
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Close() {
|
2022-04-10 16:06:56 +00:00
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Close(WsCloseReason.NormalClosure);
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void CloseEmpty() {
|
2022-04-10 16:06:56 +00:00
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.CloseEmpty();
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Close(WsCloseReason opcode) {
|
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Close(opcode);
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Close(string reason) {
|
2022-04-10 16:06:56 +00:00
|
|
|
if(reason == null)
|
|
|
|
throw new ArgumentNullException(nameof(reason));
|
|
|
|
|
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Close(WsCloseReason.NormalClosure, reason);
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Close(WsCloseReason opcode, string reason) {
|
|
|
|
if(reason == null)
|
|
|
|
throw new ArgumentNullException(nameof(reason));
|
|
|
|
|
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Close(opcode, reason);
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Close(ReadOnlySpan<byte> data) {
|
|
|
|
if(data == null)
|
|
|
|
throw new ArgumentNullException(nameof(data));
|
|
|
|
|
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Close(data);
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Close(byte[] buffer, int offset, int length) {
|
|
|
|
if(buffer == null)
|
|
|
|
throw new ArgumentNullException(nameof(buffer));
|
|
|
|
|
2022-04-10 16:06:56 +00:00
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Close(buffer.AsSpan(offset, length));
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Close(WsCloseReason opcode, ReadOnlySpan<byte> data) {
|
|
|
|
if(data == null)
|
|
|
|
throw new ArgumentNullException(nameof(data));
|
|
|
|
|
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Close(opcode, data);
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Close(WsCloseReason code, byte[] buffer, int offset, int length) {
|
|
|
|
if(buffer == null)
|
|
|
|
throw new ArgumentNullException(nameof(buffer));
|
|
|
|
|
2022-04-10 16:06:56 +00:00
|
|
|
try {
|
|
|
|
if(!SendLock.WaitOne(TIMEOUT))
|
|
|
|
throw new WsClientMutexFailedException();
|
|
|
|
Connection.Close(code, buffer.AsSpan(offset, length));
|
|
|
|
} finally {
|
|
|
|
SendLock.ReleaseMutex();
|
|
|
|
}
|
2022-04-09 22:34:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private bool IsDisposed;
|
|
|
|
|
|
|
|
~WsClient() {
|
|
|
|
DoDispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Dispose() {
|
|
|
|
DoDispose();
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void DoDispose() {
|
|
|
|
if(IsDisposed)
|
|
|
|
return;
|
|
|
|
IsDisposed = true;
|
|
|
|
|
|
|
|
SendLock.Dispose();
|
|
|
|
Connection.Dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|