C# 异步套接字——代码分析
C# Async Sockets - Code Analysis
我第二次向服务器发出请求后,我的客户端正在关闭,但没有错误,它就消失了:
class Client
{
static void Main(string[] args)
{
try
{
Console.Title = "Client";
AsyncClient client = new AsyncClient(60101);
client.Connect();
Console.Read();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.Read();
}
}
}
public class AsyncClient
{
private IPAddress ipAddress;
private int port;
/// <summary>
/// Connects to the local IPAddress.
/// </summary>
/// <param name="port"></param>
public AsyncClient(int port)
{
this.port = port;
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
this.ipAddress = null;
for (int i = 0; i < ipHostInfo.AddressList.Length; i++)
{
if (ipHostInfo.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
{
this.ipAddress = ipHostInfo.AddressList[i];
break;
}
}
if (this.ipAddress == null)
throw new Exception("No IPv4 address has been found");
}
public AsyncClient(string ip, int port)
{
this.port = port;
IPAddress.TryParse(ip, out ipAddress);
}
public async void Connect()
{
int attempts = 0;
TcpClient client = new TcpClient();
while (!client.Connected)
{
try
{
attempts++;
client.Connect(this.ipAddress, this.port);
Console.Clear();
Console.WriteLine("Connected");
await Process(client);
}
catch (SocketException)
{
Console.Clear();
Console.WriteLine("Connection Attempts: {0}", attempts);
}
}
}
public async Task Process(TcpClient tcpClient)
{
try
{
NetworkStream stream = tcpClient.GetStream();
StreamWriter writer = new StreamWriter(stream);
StreamReader reader = new StreamReader(stream);
writer.AutoFlush = true;
while (true)
{
Console.WriteLine("Enter a Request: ");
await writer.WriteLineAsync(Console.ReadLine());
string response = await reader.ReadLineAsync();
if (response != null)
Console.WriteLine(response);
else
break;
}
}
catch (Exception)
{
//
}
finally
{
if (!tcpClient.Connected)
{
for (int i = 5; i >= 1; i--)
{
Console.WriteLine($"Connection lost, trying to reconnect in {i}");
Thread.Sleep(1000);
}
Connect();
}
}
}
}
下面是服务端代码,仅供学习使用。我正在尝试学习如何使用套接字,在尝试了 "begin" 方法等许多不同的方法之后,我觉得我终于找到了正确的方法,因为与其他人一样我遇到了问题并发访问、关闭连接等,但这次我相信我做对了。
是我错了还是这次我的代码真的很好?
class Server
{
static void Main(string[] args)
{
try
{
Console.Title = "Server";
AsyncServer server = new AsyncServer(60101);
server.Run();
Console.Read();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.Read();
}
}
}
public class AsyncServer
{
private IPAddress ipAddress;
private int port;
public AsyncServer(int port)
{
this.port = port;
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
this.ipAddress = null;
for (int i = 0; i < ipHostInfo.AddressList.Length; i++)
{
if (ipHostInfo.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
{
this.ipAddress = ipHostInfo.AddressList[i];
break;
}
}
if (this.ipAddress == null)
throw new Exception("No IPv4 address for server");
}
public async void Run()
{
TcpListener listener = new TcpListener(this.ipAddress, this.port);
listener.Start();
Console.WriteLine($"Server is now online on Port: {this.port}");
Console.WriteLine("Hit <Enter> to stop the service");
while (true)
{
try
{
TcpClient tcpClient = await listener.AcceptTcpClientAsync();
Process(tcpClient);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
private async void Process(TcpClient tcpClient)
{
string clientEndPoint = tcpClient.Client.RemoteEndPoint.ToString();
Console.WriteLine($"Received connection request from {clientEndPoint}");
try
{
NetworkStream networkStream = tcpClient.GetStream();
StreamReader reader = new StreamReader(networkStream);
StreamWriter writer = new StreamWriter(networkStream);
writer.AutoFlush = true;
while (true)
{
string request = await reader.ReadLineAsync();
if (request != null)
Handle(request, writer);
else
break;
}
}
catch (Exception)
{
//
}
finally
{
if (tcpClient.Connected)
tcpClient.Close();
Console.WriteLine($"{clientEndPoint} has closed the connection, aborting operation");
}
}
private string Response(string request)
{
Thread.Sleep(10000);
if (request.ToLower() == "get time")
return DateTime.Now.ToLongTimeString();
else
return $"\"{request}\" is a invalid request";
}
private async void Handle(string request, StreamWriter writer)
{
try
{
Console.WriteLine($"Received request: {request}");
string response = Response(request);
Console.WriteLine($"Computed response is: {response}");
await writer.WriteLineAsync(response);
}
catch (Exception)
{
//
}
}
}
另外,我想知道,如果我想让它在我的外部 IP 上工作,以便来自不同 IP 的人可以使用它,我应该更改什么?
My client part is closing after I do a request to server a second
time, but without errors, it just goes away:
原因是您的客户端调用了异步方法 client.Connect()
,但没有 (a)wait 这个方法,因此在主线程上继续执行到下一行,Console.Read()
,它只会在您第二次按 [ENTER] 之前阻塞(第一个 [ENTER] 被 Console.ReadLine()
在 Process()
方法)。然后主线程无事可做,主线程(以及整个客户端应用程序)退出。
作为旁注,最好命名所有异步方法,使其名称以 'Async' 结尾,以便此类方法的调用者知道它的异步行为,并且不要忘记 (a)wait 方法。因此,您应该将 Connect
重命名为 ConnectAsync
,将 Process
重命名为 ProcessAsync
。
解决方案是将 Connect
方法的 return 类型更改为 Task
,使方法可等待(强烈建议将异步方法更改为 return void
无论如何):
public async Task ConnectAsync()
并在 Main
方法中添加 .Wait()
,这会阻塞主线程,直到 ConnectAsync()
退出。
client.ConnectAsync().Wait();
在 C# 7.1 中,您也可以改用 async Main:
static async Task Main(string[] args)
{
...
await client.ConnectAsync();
...
}
Plus, I would like to know, if I want to make it work on my external
IP, so ppl from different IPs can use it, what should I change?
只要确保如果服务器有多个 IP 地址,TcpListener
会侦听正确的 IP 地址,并在防火墙中启用端口或应用程序。
我第二次向服务器发出请求后,我的客户端正在关闭,但没有错误,它就消失了:
class Client
{
static void Main(string[] args)
{
try
{
Console.Title = "Client";
AsyncClient client = new AsyncClient(60101);
client.Connect();
Console.Read();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.Read();
}
}
}
public class AsyncClient
{
private IPAddress ipAddress;
private int port;
/// <summary>
/// Connects to the local IPAddress.
/// </summary>
/// <param name="port"></param>
public AsyncClient(int port)
{
this.port = port;
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
this.ipAddress = null;
for (int i = 0; i < ipHostInfo.AddressList.Length; i++)
{
if (ipHostInfo.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
{
this.ipAddress = ipHostInfo.AddressList[i];
break;
}
}
if (this.ipAddress == null)
throw new Exception("No IPv4 address has been found");
}
public AsyncClient(string ip, int port)
{
this.port = port;
IPAddress.TryParse(ip, out ipAddress);
}
public async void Connect()
{
int attempts = 0;
TcpClient client = new TcpClient();
while (!client.Connected)
{
try
{
attempts++;
client.Connect(this.ipAddress, this.port);
Console.Clear();
Console.WriteLine("Connected");
await Process(client);
}
catch (SocketException)
{
Console.Clear();
Console.WriteLine("Connection Attempts: {0}", attempts);
}
}
}
public async Task Process(TcpClient tcpClient)
{
try
{
NetworkStream stream = tcpClient.GetStream();
StreamWriter writer = new StreamWriter(stream);
StreamReader reader = new StreamReader(stream);
writer.AutoFlush = true;
while (true)
{
Console.WriteLine("Enter a Request: ");
await writer.WriteLineAsync(Console.ReadLine());
string response = await reader.ReadLineAsync();
if (response != null)
Console.WriteLine(response);
else
break;
}
}
catch (Exception)
{
//
}
finally
{
if (!tcpClient.Connected)
{
for (int i = 5; i >= 1; i--)
{
Console.WriteLine($"Connection lost, trying to reconnect in {i}");
Thread.Sleep(1000);
}
Connect();
}
}
}
}
下面是服务端代码,仅供学习使用。我正在尝试学习如何使用套接字,在尝试了 "begin" 方法等许多不同的方法之后,我觉得我终于找到了正确的方法,因为与其他人一样我遇到了问题并发访问、关闭连接等,但这次我相信我做对了。 是我错了还是这次我的代码真的很好?
class Server
{
static void Main(string[] args)
{
try
{
Console.Title = "Server";
AsyncServer server = new AsyncServer(60101);
server.Run();
Console.Read();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.Read();
}
}
}
public class AsyncServer
{
private IPAddress ipAddress;
private int port;
public AsyncServer(int port)
{
this.port = port;
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
this.ipAddress = null;
for (int i = 0; i < ipHostInfo.AddressList.Length; i++)
{
if (ipHostInfo.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
{
this.ipAddress = ipHostInfo.AddressList[i];
break;
}
}
if (this.ipAddress == null)
throw new Exception("No IPv4 address for server");
}
public async void Run()
{
TcpListener listener = new TcpListener(this.ipAddress, this.port);
listener.Start();
Console.WriteLine($"Server is now online on Port: {this.port}");
Console.WriteLine("Hit <Enter> to stop the service");
while (true)
{
try
{
TcpClient tcpClient = await listener.AcceptTcpClientAsync();
Process(tcpClient);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
private async void Process(TcpClient tcpClient)
{
string clientEndPoint = tcpClient.Client.RemoteEndPoint.ToString();
Console.WriteLine($"Received connection request from {clientEndPoint}");
try
{
NetworkStream networkStream = tcpClient.GetStream();
StreamReader reader = new StreamReader(networkStream);
StreamWriter writer = new StreamWriter(networkStream);
writer.AutoFlush = true;
while (true)
{
string request = await reader.ReadLineAsync();
if (request != null)
Handle(request, writer);
else
break;
}
}
catch (Exception)
{
//
}
finally
{
if (tcpClient.Connected)
tcpClient.Close();
Console.WriteLine($"{clientEndPoint} has closed the connection, aborting operation");
}
}
private string Response(string request)
{
Thread.Sleep(10000);
if (request.ToLower() == "get time")
return DateTime.Now.ToLongTimeString();
else
return $"\"{request}\" is a invalid request";
}
private async void Handle(string request, StreamWriter writer)
{
try
{
Console.WriteLine($"Received request: {request}");
string response = Response(request);
Console.WriteLine($"Computed response is: {response}");
await writer.WriteLineAsync(response);
}
catch (Exception)
{
//
}
}
}
另外,我想知道,如果我想让它在我的外部 IP 上工作,以便来自不同 IP 的人可以使用它,我应该更改什么?
My client part is closing after I do a request to server a second time, but without errors, it just goes away:
原因是您的客户端调用了异步方法 client.Connect()
,但没有 (a)wait 这个方法,因此在主线程上继续执行到下一行,Console.Read()
,它只会在您第二次按 [ENTER] 之前阻塞(第一个 [ENTER] 被 Console.ReadLine()
在 Process()
方法)。然后主线程无事可做,主线程(以及整个客户端应用程序)退出。
作为旁注,最好命名所有异步方法,使其名称以 'Async' 结尾,以便此类方法的调用者知道它的异步行为,并且不要忘记 (a)wait 方法。因此,您应该将 Connect
重命名为 ConnectAsync
,将 Process
重命名为 ProcessAsync
。
解决方案是将 Connect
方法的 return 类型更改为 Task
,使方法可等待(强烈建议将异步方法更改为 return void
无论如何):
public async Task ConnectAsync()
并在 Main
方法中添加 .Wait()
,这会阻塞主线程,直到 ConnectAsync()
退出。
client.ConnectAsync().Wait();
在 C# 7.1 中,您也可以改用 async Main:
static async Task Main(string[] args)
{
...
await client.ConnectAsync();
...
}
Plus, I would like to know, if I want to make it work on my external IP, so ppl from different IPs can use it, what should I change?
只要确保如果服务器有多个 IP 地址,TcpListener
会侦听正确的 IP 地址,并在防火墙中启用端口或应用程序。