首先回答问题:
问:在使用Socket发送数据时,是否需要将所有内容转换为字符串?我想做的是从一台计算机向另外两台计算机发送一个变量,以便在所有计算机上同时开始一个进程。
答:不需要将所有内容转换为字符串。您可以发送byte[]
,这可能是您想要的。
问:我想实现的是使用套接字从服务器向客户端发送布尔变量。
答:您是指boolean
还是byte
?因为您从Socket
获得的基本变量类型是byte
。您始终可以通过以下方式从发送方/接收方将byte
更改为bool
:
bool val = byteToCheck > 0;
答2:由于您的服务器是Console
应用程序,我建议查看十六进制string
到byte[]
转换。这样,您可以编写string
,但将其解释为byte[]
。检查this。整个思路非常简单。即:您键入string
,但它将作为byte[]
发送。由于它是byte[]
,因此可以在其中具有任何值。
这里我提供了解决方案来处理您的(1)多个客户端、(2)Async
连接&接受&接收,但有(3)同步发送,以及(4)从十六进制字符串
转换为byte[]
(结构和思路),最后但并非最不重要的是(5)带有用户输入的工作代码(供您更改此部分)进行测试!
我会使用简单的Socket
类来解决这个问题,因为这是我最熟悉的解决方案。但如果您使用TcpListener.Server
(它是Socket
类的底层网络),您也可以采用类似的方法。并且,如您所需,我会使用ASync
来完成。
在服务器和客户端中都需要执行几个步骤才能实现您想要的内容:
服务器
Make your Socket
as class field rather than method field, since you will use if everywhere and you need multiple methods to achieve what you want. And initialize it as soon as you started your main routine.
const int PORT_NO = 2201;
const string SERVER_IP = "127.0.0.1";
static Socket serverSocket;
static void Main(string[] args) {
Console.WriteLine("Listening...");
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO));
serverSocket.Listen(4);
}
Since the server will serve many clients, I will recommend you to use ASync
rather than Sync
for the process. Initialize your Socket
by using BeginAccept
rather than using Accept
, put acceptCallback
in your BeginAccept
static void Main(string[] args) {
Console.WriteLine("Listening...");
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO));
serverSocket.Listen(4);
serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null);
}
Define acceptCallback
, which is where you will go when you accept a Socket
. Put EndAccept
there.
private void acceptCallback(IAsyncResult result) { //if the buffer is old, then there might already be something there...
System.Net.Sockets.Socket socket = null;
try {
socket = serverSocket.EndAccept(result); // To get your client socket
//do something later
} catch (Exception e) { // this exception will happen when "this" is be disposed...
//do something later
}
}
I would typically list my client sockets, and do something on client disposal (that is unlisted it) - but this depends on the need. In this case, you seem to need it. And don't forget to create buffers, etc... This is for buffering the incoming data.
Start to accept something received from the client, using another ASync
BeginReceive
on the client Socket
(and now you need receiveCallback
). Then, very important, repeat your BeginAccept
to accept other clients!
private const int BUFFER_SIZE = 4096;
private static byte[] buffer = new byte[BUFFER_SIZE];
private static List<Socket> clientSockets = new List<Socket>();
private static void acceptCallback(IAsyncResult result) {
Socket socket = null;
try {
socket = serverSocket.EndAccept(result);
clientSockets.Add(socket);
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null);
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
Define your receiveCallback
, that is, when you receive something from your client. This part could be quite tricky because of failures! But basically, what you need for now is simply EndReceive
and again, very important, to repeat the BeginReceive
from the same client such that you can receive its next message!
const int MAX_RECEIVE_ATTEMPT = 10;
static int receiveAttempt = 0;
private static void receiveCallback(IAsyncResult result) {
Socket socket = null;
try {
socket = (Socket)result.AsyncState;
if (socket.Connected) {
int received = socket.EndReceive(result);
if (received > 0) {
byte[] data = new byte[received];
Buffer.BlockCopy(buffer, 0, data, 0, data.Length);
Console.WriteLine(Encoding.UTF8.GetString(data));
receiveAttempt = 0;
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
} else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) {
++receiveAttempt;
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
} else {
Console.WriteLine("receiveCallback fails!");
receiveAttempt = 0;
}
}
} catch (Exception e) {
Console.WriteLine("receiveCallback fails with exception! " + e.ToString());
}
}
And suppose you want to reply your sender after you receive the message, simply do this in the if (received > 0)
part:
if (received > 0) {
byte[] data = new byte[received];
Console.WriteLine(Encoding.UTF8.GetString(data));
string msg = "I receive your message on: " + DateTime.Now;
socket.Send(Encoding.ASCII.GetBytes(msg));
Console.WriteLine("I sent this message to the client: " + msg);
receiveAttempt = 0;
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
}
And after putting a little more things in your main routine, you are done(!) - IF you do not ask for sending to client as byte[]
And now, if you want to send something to all your clients as byte[]
you simply need to list all your client (see step 4-5). See this and convert the result
string
above (remember to type it in hex string
format as required) to byte[]
then send it to all the clients using your client socket list (here is where it is needed!):
static void Main(string[] args) {
Console.WriteLine("Listening...");
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO));
serverSocket.Listen(4);
serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null);
string result = "";
do {
result = Console.ReadLine();
if (result.ToLower().Trim() != "exit") {
byte[] bytes = null;
foreach(Socket socket in clientSockets)
socket.Send(bytes);
}
} while (result.ToLower().Trim() != "exit");
}
在这里,你基本上已经完成了你的服务器。接下来是客户端。
客户端:
Similarly, put the Socket
class in the class context rather than method context and initialize it as soon as you start your program
const int PORT_NO = 2201;
const string SERVER_IP = "127.0.0.1";
static Socket clientSocket;
static void Main(string[] args) {
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
Then start to connect by ASync
BeginConnect
. I would normally go further by LoopConnect
just for failure handling like this.
static void loopConnect(int noOfRetry, int attemptPeriodInSeconds) {
int attempts = 0;
while (!clientSocket.Connected && attempts < noOfRetry) {
try {
++attempts;
IAsyncResult result = clientSocket.BeginConnect(IPAddress.Parse(SERVER_IP), PORT_NO, endConnect, null);
result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(attemptPeriodInSeconds));
System.Threading.Thread.Sleep(attemptPeriodInSeconds * 1000);
} catch (Exception e) {
Console.WriteLine("Error: " + e.ToString());
}
}
if (!clientSocket.Connected) {
Console.WriteLine("Connection attempt is unsuccessful!");
return;
}
}
Similar concept to what you do to the server BeginAccept
you need to define endConnectCallback
for the ASync
BeginConnect
you use. But here, unlike server which needs to re-calling BeginAccept
, once you are connected, you do not need to do any new BeginConnect
since you only need to be connected once.
You may want to declare buffer
etc. Then, after you connect, don't forget the next ASync
BeginReceive
to handle the message retrieval part (similar with the server)
private const int BUFFER_SIZE = 4096;
private static byte[] buffer = new byte[BUFFER_SIZE];
private static void endConnectCallback(IAsyncResult ar) {
try {
clientSocket.EndConnect(ar);
if (clientSocket.Connected) {
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), clientSocket);
} else {
Console.WriteLine("End of connection attempt, fail to connect...");
}
} catch (Exception e) {
Console.WriteLine("End-connection attempt is unsuccessful! " + e.ToString());
}
}
Naturally, you need to define your receiveCallback
, just like what you did for the server. And yes, it is as you have guessed, it is almost identical to what you did for the server!
You can do anything you want with your data. Note that the data you receive is actually in byte[]
, not string
. So you can do anything with it. But for example's sake, I will just use string
to display.
const int MAX_RECEIVE_ATTEMPT = 10;
static int receiveAttempt = 0;
private static void receiveCallback(IAsyncResult result) {
System.Net.Sockets.Socket socket = null;
try {
socket = (System.Net.Sockets.Socket)result.AsyncState;
if (socket.Connected) {
int received = socket.EndReceive(result);
if (received > 0) {
receiveAttempt = 0;
byte[] data = new byte[received];
Buffer.BlockCopy(buffer, 0, data, 0, data.Length);
Console.WriteLine("Server: " + Encoding.UTF8.GetString(data));
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
} else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) {
++receiveAttempt;
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
} else {
Console.WriteLine("receiveCallback is failed!");
receiveAttempt = 0;
clientSocket.Close();
}
}
} catch (Exception e) {
Console.WriteLine("receiveCallback is failed! " + e.ToString());
}
}
And at the very very last... Yes, again, as you have already guessed, you just need to do something on your main routine - suppose you want to use it to send data. Because you use Console
but you want it to send things as byte[]
, you need to do the conversion (see the explanation in server 9.). And afterwards you are completely done!!
static void Main(string[] args) {
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
loopConnect(3, 3);
string result = "";
do {
result = Console.ReadLine();
if (result.ToLower().Trim() != "exit") {
byte[] bytes = Encoding.ASCII.GetBytes(result);
clientSocket.Send(bytes);
}
} while (result.ToLower().Trim() != "exit");
}
结果:
这里是您需要的内容!我测试了发送string
以进行显示,但我已经提供了需要更改为byte[]
时所需的内容。
![enter image description here](https://istack.dev59.com/yX416.webp)
![enter image description here](https://istack.dev59.com/7zKjY.webp)
您的测试代码:
服务器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace TcpListenerConsoleApplication {
class Program {
const int PORT_NO = 2201;
const string SERVER_IP = "127.0.0.1";
static Socket serverSocket;
static void Main(string[] args) {
Console.WriteLine("Listening...");
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO));
serverSocket.Listen(4);
serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null);
string result = "";
do {
result = Console.ReadLine();
} while (result.ToLower().Trim() != "exit");
}
private const int BUFFER_SIZE = 4096;
private static byte[] buffer = new byte[BUFFER_SIZE];
private static void acceptCallback(IAsyncResult result) {
Socket socket = null;
try {
socket = serverSocket.EndAccept(result);
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null);
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
const int MAX_RECEIVE_ATTEMPT = 10;
static int receiveAttempt = 0;
private static void receiveCallback(IAsyncResult result) {
Socket socket = null;
try {
socket = (Socket)result.AsyncState;
if (socket.Connected) {
int received = socket.EndReceive(result);
if (received > 0) {
byte[] data = new byte[received];
Buffer.BlockCopy(buffer, 0, data, 0, data.Length);
Console.WriteLine(Encoding.UTF8.GetString(data));
string msg = "I receive your message on: " + DateTime.Now;
socket.Send(Encoding.ASCII.GetBytes(msg));
Console.WriteLine("I sent this message to the client: " + msg);
receiveAttempt = 0;
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
} else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) {
++receiveAttempt;
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
} else {
Console.WriteLine("receiveCallback fails!");
receiveAttempt = 0;
}
}
} catch (Exception e) {
Console.WriteLine("receiveCallback fails with exception! " + e.ToString());
}
}
}
}
客户端。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace TcpClientConsoleApplication {
class Program {
const int PORT_NO = 2201;
const string SERVER_IP = "127.0.0.1";
static Socket clientSocket;
static void Main(string[] args) {
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
loopConnect(3, 3);
string result = "";
do {
result = Console.ReadLine();
if (result.ToLower().Trim() != "exit") {
byte[] bytes = Encoding.ASCII.GetBytes(result);
clientSocket.Send(bytes);
}
} while (result.ToLower().Trim() != "exit");
}
static void loopConnect(int noOfRetry, int attemptPeriodInSeconds) {
int attempts = 0;
while (!clientSocket.Connected && attempts < noOfRetry) {
try {
++attempts;
IAsyncResult result = clientSocket.BeginConnect(IPAddress.Parse(SERVER_IP), PORT_NO, endConnectCallback, null);
result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(attemptPeriodInSeconds));
System.Threading.Thread.Sleep(attemptPeriodInSeconds * 1000);
} catch (Exception e) {
Console.WriteLine("Error: " + e.ToString());
}
}
if (!clientSocket.Connected) {
Console.WriteLine("Connection attempt is unsuccessful!");
return;
}
}
private const int BUFFER_SIZE = 4096;
private static byte[] buffer = new byte[BUFFER_SIZE];
private static void endConnectCallback(IAsyncResult ar) {
try {
clientSocket.EndConnect(ar);
if (clientSocket.Connected) {
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), clientSocket);
} else {
Console.WriteLine("End of connection attempt, fail to connect...");
}
} catch (Exception e) {
Console.WriteLine("End-connection attempt is unsuccessful! " + e.ToString());
}
}
const int MAX_RECEIVE_ATTEMPT = 10;
static int receiveAttempt = 0;
private static void receiveCallback(IAsyncResult result) {
System.Net.Sockets.Socket socket = null;
try {
socket = (System.Net.Sockets.Socket)result.AsyncState;
if (socket.Connected) {
int received = socket.EndReceive(result);
if (received > 0) {
receiveAttempt = 0;
byte[] data = new byte[received];
Buffer.BlockCopy(buffer, 0, data, 0, data.Length);
Console.WriteLine("Server: " + Encoding.UTF8.GetString(data));
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
} else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) {
++receiveAttempt;
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
} else {
Console.WriteLine("receiveCallback is failed!");
receiveAttempt = 0;
clientSocket.Close();
}
}
} catch (Exception e) {
Console.WriteLine("receiveCallback is failed! " + e.ToString());
}
}
}
}
最终说明(编辑)
由于上述代码是使用控制台应用程序
运行的,因此必须使用static main void
关键字运行。因此,上面定义的客户端Socket
是静态
类型。这可能会防止客户端Socket
被定义多次,因为每次它被“定义”,由于它是同一个名为Program
的class
,它将引用同一Socket
(尽管根据OP的实验,这并不总是如此:他可以在同一台计算机上成功运行多个客户端)。
然而,要克服这个问题并不难。只需将客户端应用程序移植到非初始化为静态
类的平台(例如WinForms
),所有上述代码仍将按正常方式运行。或者,如果必须使用控制台应用程序
,并且出现问题,请复制客户端应用程序并重新定义它,使用不同的命名空间
或不同的class
名称来避免由于相同的命名空间
或class
而定义相同的Socket
。
但在解决这个问题时,最重要的部分是明智地使用
Async
和
Sync
来解决所给出的问题。
此话题的续篇可以在这里找到。