Skip to content

Commit 71c0f2b

Browse files
committed
changes
1 parent 9bd0141 commit 71c0f2b

File tree

9 files changed

+734
-0
lines changed

9 files changed

+734
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="../../src/SuperSocket.Server/SuperSocket.Server.csproj" />
12+
<ProjectReference Include="../../src/SuperSocket.ProtoBase/SuperSocket.ProtoBase.csproj" />
13+
<ProjectReference Include="../../src/SuperSocket.Connection/SuperSocket.Connection.csproj" />
14+
<ProjectReference Include="../../src/SuperSocket.Server.Abstractions/SuperSocket.Server.Abstractions.csproj" />
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using Microsoft.Extensions.Hosting;
2+
using Microsoft.Extensions.Logging;
3+
using SuperSocket;
4+
using SuperSocket.ProtoBase;
5+
using SuperSocket.Server;
6+
using SuperSocket.Server.Host;
7+
using SuperSocket.Server.Abstractions.Session;
8+
using SuperSocket.Connection;
9+
using System.Buffers;
10+
using System.Text;
11+
12+
namespace ConsoleEchoServer
13+
{
14+
/// <summary>
15+
/// A simple line-based protocol filter for text commands.
16+
/// </summary>
17+
public class LinePipelineFilter : TerminatorPipelineFilter<StringPackageInfo>
18+
{
19+
public LinePipelineFilter() : base(Encoding.UTF8.GetBytes("\r\n"))
20+
{
21+
}
22+
23+
protected override StringPackageInfo DecodePackage(ref ReadOnlySequence<byte> buffer)
24+
{
25+
var text = buffer.GetString(Encoding.UTF8);
26+
return new StringPackageInfo { Key = "LINE", Body = text };
27+
}
28+
}
29+
30+
/// <summary>
31+
/// Application session for handling console-based commands.
32+
/// </summary>
33+
public class ConsoleSession : AppSession
34+
{
35+
protected override async ValueTask OnSessionConnectedAsync()
36+
{
37+
await ((IAppSession)this).SendAsync(Encoding.UTF8.GetBytes("Welcome to Console Echo Server!\r\nType 'quit' to exit, 'help' for commands.\r\n"));
38+
}
39+
40+
protected override async ValueTask OnSessionClosedAsync(CloseEventArgs e)
41+
{
42+
Console.WriteLine($"Session closed: {e.Reason}");
43+
}
44+
}
45+
46+
/// <summary>
47+
/// Main program for the console echo server.
48+
/// </summary>
49+
class Program
50+
{
51+
static async Task Main(string[] args)
52+
{
53+
Console.WriteLine("Console Echo Server starting...");
54+
Console.WriteLine("This server reads from stdin and writes to stdout.");
55+
Console.WriteLine("Type commands and press Enter. Type 'quit' to exit.");
56+
Console.WriteLine("----------------------------------------");
57+
58+
var host = SuperSocketHostBuilder.Create<StringPackageInfo, LinePipelineFilter>()
59+
.UseConsole()
60+
.UseSession<ConsoleSession>()
61+
.UsePackageHandler(async (session, package) =>
62+
{
63+
var command = package.Body?.Trim();
64+
65+
switch (command?.ToLowerInvariant())
66+
{
67+
case "quit":
68+
case "exit":
69+
Console.WriteLine("Server shutting down...");
70+
await session.CloseAsync(CloseReason.ApplicationError);
71+
Environment.Exit(0);
72+
break;
73+
74+
case "help":
75+
await ((IAppSession)session).SendAsync(Encoding.UTF8.GetBytes(
76+
"Available commands:\r\n" +
77+
" help - Show this help message\r\n" +
78+
" time - Show current time\r\n" +
79+
" echo <message> - Echo back the message\r\n" +
80+
" quit/exit - Close the server\r\n"));
81+
break;
82+
83+
case "time":
84+
await ((IAppSession)session).SendAsync(Encoding.UTF8.GetBytes($"Current time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\r\n"));
85+
break;
86+
87+
case var cmd when cmd?.StartsWith("echo ") == true:
88+
var message = command.Substring(5);
89+
await ((IAppSession)session).SendAsync(Encoding.UTF8.GetBytes($"Echo: {message}\r\n"));
90+
break;
91+
92+
case "":
93+
// Empty line, do nothing
94+
break;
95+
96+
default:
97+
await ((IAppSession)session).SendAsync(Encoding.UTF8.GetBytes($"Unknown command: '{command}'. Type 'help' for available commands.\r\n"));
98+
break;
99+
}
100+
})
101+
.ConfigureLogging((hostCtx, loggingBuilder) =>
102+
{
103+
loggingBuilder.AddConsole();
104+
loggingBuilder.SetMinimumLevel(LogLevel.Information);
105+
})
106+
.Build();
107+
108+
try
109+
{
110+
await host.RunAsync();
111+
}
112+
catch (Exception ex)
113+
{
114+
Console.WriteLine($"Server error: {ex.Message}");
115+
}
116+
}
117+
}
118+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Console Echo Server Sample
2+
3+
This sample demonstrates how to use SuperSocket with stdin/stdout for console-based communication.
4+
5+
## Overview
6+
7+
The Console Echo Server shows how to:
8+
- Use SuperSocket with console input/output streams
9+
- Create a simple line-based protocol
10+
- Handle text commands interactively
11+
- Provide a command-line interface using SuperSocket's architecture
12+
13+
## Features
14+
15+
- **Console Integration**: Reads from stdin and writes to stdout
16+
- **Interactive Commands**: Supports various text commands
17+
- **Line-based Protocol**: Uses `\r\n` as message delimiter
18+
- **Session Management**: Proper session lifecycle handling
19+
20+
## Available Commands
21+
22+
- `help` - Show available commands
23+
- `time` - Display current server time
24+
- `echo <message>` - Echo back the provided message
25+
- `quit` or `exit` - Shutdown the server
26+
27+
## Running the Sample
28+
29+
```bash
30+
cd samples/ConsoleEchoServer
31+
dotnet run
32+
```
33+
34+
## Usage Example
35+
36+
```
37+
Console Echo Server starting...
38+
This server reads from stdin and writes to stdout.
39+
Type commands and press Enter. Type 'quit' to exit.
40+
----------------------------------------
41+
Welcome to Console Echo Server!
42+
Type 'quit' to exit, 'help' for commands.
43+
44+
help
45+
Available commands:
46+
help - Show this help message
47+
time - Show current time
48+
echo <message> - Echo back the message
49+
quit/exit - Close the server
50+
51+
echo Hello World
52+
Echo: Hello World
53+
54+
time
55+
Current time: 2025-10-03 14:30:15
56+
57+
quit
58+
Server shutting down...
59+
```
60+
61+
## Implementation Details
62+
63+
### Console Connection
64+
- Uses `ConsoleConnection` which wraps `Console.In` and `Console.Out` streams
65+
- Provides bidirectional communication through standard streams
66+
- Handles stream lifecycle properly without disposing system streams
67+
68+
### Protocol
69+
- Implements `LinePipelineFilter` for line-based text processing
70+
- Uses `\r\n` as message terminator
71+
- Returns `StringPackageInfo` for easy text handling
72+
73+
### Session Handling
74+
- Custom `ConsoleSession` class extends `AppSession`
75+
- Provides welcome message on connection
76+
- Handles session cleanup on disconnection
77+
78+
## Use Cases
79+
80+
This pattern is useful for:
81+
- Command-line tools that need protocol processing
82+
- Development and debugging tools
83+
- Console-based administration interfaces
84+
- Interactive testing utilities
85+
- Pipe-based communication scenarios
86+
87+
## Integration with Other Streams
88+
89+
The console connection can be adapted to work with other streams:
90+
- File streams for batch processing
91+
- Named pipes for inter-process communication
92+
- Network streams for debugging
93+
- Custom streams for specialized scenarios
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
using System;
2+
using System.IO;
3+
using System.Net;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using SuperSocket.ProtoBase;
7+
8+
namespace SuperSocket.Connection
9+
{
10+
/// <summary>
11+
/// Represents a connection that uses the console's standard input/output streams for communication.
12+
/// </summary>
13+
public class ConsoleConnection : StreamPipeConnection
14+
{
15+
private static readonly EndPoint _consoleEndPoint = new ConsoleEndPoint();
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="ConsoleConnection"/> class.
19+
/// </summary>
20+
/// <param name="options">The connection options.</param>
21+
public ConsoleConnection(ConnectionOptions options)
22+
: base(CreateConsoleStream(), _consoleEndPoint, _consoleEndPoint, options)
23+
{
24+
}
25+
26+
/// <summary>
27+
/// Creates a bidirectional stream that combines console input and output.
28+
/// </summary>
29+
/// <returns>A stream that reads from stdin and writes to stdout.</returns>
30+
private static Stream CreateConsoleStream()
31+
{
32+
return new ConsoleStream();
33+
}
34+
35+
/// <summary>
36+
/// Represents an endpoint for console communication.
37+
/// </summary>
38+
private class ConsoleEndPoint : EndPoint
39+
{
40+
public override string ToString() => "Console";
41+
}
42+
43+
/// <summary>
44+
/// A stream implementation that combines console input and output operations.
45+
/// </summary>
46+
private class ConsoleStream : Stream
47+
{
48+
private readonly Stream _inputStream;
49+
private readonly Stream _outputStream;
50+
51+
public ConsoleStream()
52+
{
53+
_inputStream = Console.OpenStandardInput();
54+
_outputStream = Console.OpenStandardOutput();
55+
}
56+
57+
public override bool CanRead => true;
58+
public override bool CanSeek => false;
59+
public override bool CanWrite => true;
60+
public override long Length => throw new NotSupportedException();
61+
public override long Position
62+
{
63+
get => throw new NotSupportedException();
64+
set => throw new NotSupportedException();
65+
}
66+
67+
public override void Flush()
68+
{
69+
_outputStream.Flush();
70+
}
71+
72+
public override async Task FlushAsync(CancellationToken cancellationToken)
73+
{
74+
await _outputStream.FlushAsync(cancellationToken).ConfigureAwait(false);
75+
}
76+
77+
public override int Read(byte[] buffer, int offset, int count)
78+
{
79+
return _inputStream.Read(buffer, offset, count);
80+
}
81+
82+
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
83+
{
84+
return await _inputStream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
85+
}
86+
87+
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
88+
{
89+
return await _inputStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
90+
}
91+
92+
public override long Seek(long offset, SeekOrigin origin)
93+
{
94+
throw new NotSupportedException();
95+
}
96+
97+
public override void SetLength(long value)
98+
{
99+
throw new NotSupportedException();
100+
}
101+
102+
public override void Write(byte[] buffer, int offset, int count)
103+
{
104+
_outputStream.Write(buffer, offset, count);
105+
}
106+
107+
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
108+
{
109+
await _outputStream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
110+
}
111+
112+
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
113+
{
114+
await _outputStream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
115+
}
116+
117+
protected override void Dispose(bool disposing)
118+
{
119+
if (disposing)
120+
{
121+
// Don't dispose the console streams as they are managed by the runtime
122+
// _inputStream?.Dispose();
123+
// _outputStream?.Dispose();
124+
}
125+
base.Dispose(disposing);
126+
}
127+
128+
public override async ValueTask DisposeAsync()
129+
{
130+
// Don't dispose the console streams as they are managed by the runtime
131+
// if (_inputStream != null)
132+
// await _inputStream.DisposeAsync().ConfigureAwait(false);
133+
// if (_outputStream != null)
134+
// await _outputStream.DisposeAsync().ConfigureAwait(false);
135+
await base.DisposeAsync().ConfigureAwait(false);
136+
}
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)