Ответ 1
Сначала ответы на вопросы:
Q: Нужно ли преобразовывать все в строку?... В общем, что я хотите отправить переменную с одного компьютера на два других, чтобы процесс для запуска одновременно на всех компьютерах.
A: Нет, нет необходимости конвертировать все в строку при отправке используя
Socket
. Вы можете отправитьbyte[]
, который вы, скорее всего, захотите.В: Я хочу добиться отправки логической переменной с сервера клиенту с сокетами
A: Вы имеете в виду
boolean
илиbyte
? Потому что основной тип переменной, который вы получит отSocket
isbyte
. Вы всегда можете изменитьbyte
наbool
со стороны отправителя/получателя, выполнив как
bool val = byteToCheck > 0 ? true : false
A2: И поскольку ваш разорвать
Console
приложение, я рекомендую принять посмотрите на преобразование hexstring
вbyte[]
. Таким образом, вы могли бы напишите что-нибудь вstring
, но интерпретируйте его какbyte[]
. Проверьте . Вся эта идея здесь довольно проста. То есть: вы вводитеstring
, но он будет отправлен какbyte[]
. И поскольку этоbyte[]
вы можете иметь любое значение в нем.
И здесь я представляю свое решение для обработки ваших (1) нескольких клиентов, (2) Async
подключиться, принять и получить, но с (3) отправить синхронизацию, а также (4) конвертировать из hex string
в byte[]
(структура и идея) и последний, но не менее важный (5) рабочий код с пользовательским вводом (для вас, чтобы изменить эту часть) для тестирования!
Я бы решил эту проблему, используя простой класс Socket
, так как это решение, с которым я больше всего знаком. Но вы всегда можете сделать это, если используете ваш TcpListener.Server
(который является базовой сетью класса Socket
). И, как вы пожелаете, я сделал бы это с помощью Async
.
Для достижения желаемого уровня как на вашем сервере, так и на вашем клиенте необходимо выполнить несколько шагов:
Сервер
-
Создайте поле
Socket
как поле класса, а не поле метода, так как вы будете использовать, если повсюду, и вам нужно несколько методов для достижения того, чего вы хотите. И инициализируйте его, как только вы начнете свою основную процедуру.const int PORT_NO = 2201; const string SERVER_IP = "127.0.0.1"; static Socket serverSocket; //put here as static static void Main(string[] args) { //---listen at the specified IP and port no.--- Console.WriteLine("Listening..."); serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO)); serverSocket.Listen(4); //the maximum pending client, define as you wish //your next main routine }
-
Поскольку сервер будет обслуживать множество клиентов, я рекомендую вам использовать
Async
, а неSync
для процесса. ИнициализируйтеSocket
с помощьюBeginAccept
вместо использованияAccept
, поместитеacceptCallback
вBeginAccept
static void Main(string[] args) { //---listen at the specified IP and port no.--- Console.WriteLine("Listening..."); serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO)); serverSocket.Listen(4); //the maximum pending client, define as you wish serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null); //other stuffs }
-
Определите
acceptCallback
, где вы будете идти, когда вы принимаетеSocket
. ПоложимEndAccept
.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 } }
-
Я бы обычно перечислял свои клиентские сокеты и делал что-то по распоряжению клиентами (это не указано в списке), но это зависит от необходимости. В этом случае вам это кажется нужным. И не забудьте создать буферы и т.д. Это для буферизации входящих данных.
-
Начните принимать что-то, полученное от клиента, используя другой
Async
BeginReceive
на клиентеSocket
(и теперь вам нужноreceiveCallback
). Затем очень важно, повторитеBeginAccept
, чтобы принять других клиентов!private const int BUFFER_SIZE = 4096; private static byte[] buffer = new byte[BUFFER_SIZE]; //buffer size is limited to BUFFER_SIZE per message private static List<Socket> clientSockets = new List<Socket>(); //may be needed by you private static void acceptCallback(IAsyncResult result) { //if the buffer is old, then there might already be something there... Socket socket = null; try { socket = serverSocket.EndAccept(result); // The objectDisposedException will come here... thus, it is to be expected! //Do something as you see it needs on client acceptance such as listing clientSockets.Add(socket); //may be needed later socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null); //to receive another client } catch (Exception e) { // this exception will happen when "this" is be disposed... //Do something here Console.WriteLine(e.ToString()); } }
-
Определите свой
receiveCallback
, то есть когда вы получаете что-то от своего клиента. Эта часть может быть довольно сложной из-за сбоев! Но в принципе, вам сейчас нужно простоEndReceive
и снова очень важно, чтобы повторитьBeginReceive
с того же клиента, чтобы вы могли получить его следующее сообщение!const int MAX_RECEIVE_ATTEMPT = 10; static int receiveAttempt = 0; //this is not fool proof, obviously, since actually you must have multiple of this for multiple clients, but for the sake of simplicity I put this private static void receiveCallback(IAsyncResult result) { Socket socket = null; try { socket = (Socket)result.AsyncState; //this is to get the sender if (socket.Connected) { //simple checking int received = socket.EndReceive(result); if (received > 0) { byte[] data = new byte[received]; //the data is in the byte[] format, not string! Buffer.BlockCopy(buffer, 0, data, 0, data.Length); //There are several way to do this according to https://stackoverflow.com/questions/5099604/any-faster-way-of-copying-arrays-in-c in general, System.Buffer.memcpyimpl is the fastest //DO SOMETHING ON THE DATA IN byte[] data!! Yihaa!! Console.WriteLine(Encoding.UTF8.GetString(data)); //Here I just print it, but you need to do something else receiveAttempt = 0; //reset receive attempt socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive } else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //fail but not exceeding max attempt, repeats ++receiveAttempt; //increase receive attempt; socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive } else { //completely fails! Console.WriteLine("receiveCallback fails!"); //don't repeat beginReceive receiveAttempt = 0; //reset this for the next connection } } } catch (Exception e) { // this exception will happen when "this" is be disposed... Console.WriteLine("receiveCallback fails with exception! " + e.ToString()); } }
-
И предположим, что вы хотите ответить отправителю после получения сообщения, просто сделайте это в части
if (received > 0)
:if (received > 0) { byte[] data = new byte[received]; //the data is in the byte[] format, not string! //DO SOMETHING ON THE DATA int byte[]!! Yihaa!! Console.WriteLine(Encoding.UTF8.GetString(data)); //Here I just print it, but you need to do something else //Message retrieval part //Suppose you only want to declare that you receive data from a client to that client string msg = "I receive your message on: " + DateTime.Now; socket.Send(Encoding.ASCII.GetBytes(msg)); //Note that you actually send data in byte[] Console.WriteLine("I sent this message to the client: " + msg); receiveAttempt = 0; //reset receive attempt socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive }
-
И после того, как вы поместили немного больше вещей в свою основную процедуру, вы закончили (!) - IF, вы не запрашиваете отправки клиенту как
byte[]
-
И теперь, если вы хотите отправить что-то всем своим клиентам как
byte[]
, вам просто нужно указать весь ваш клиент (см. шаг 4-5). См. this и преобразуйтеresult
string
выше (не забудьте ввести его в формате hexstring
, если требуется), вbyte[]
, затем отправьте его на все клиенты, использующие список ваших сокетов клиента (здесь, где это необходимо!):static void Main(string[] args) { //---listen at the specified IP and port no.--- Console.WriteLine("Listening..."); serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO)); serverSocket.Listen(4); //the maximum pending client, define as you wish serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null); //normally, there isn't anything else needed here string result = ""; do { result = Console.ReadLine(); if (result.ToLower().Trim() != "exit") { byte[] bytes = null; //you can use `result` and change it to `bytes` by any mechanism which you want //the mechanism which suits you is probably the hex string to byte[] //this is the reason why you may want to list the client sockets foreach(Socket socket in clientSockets) socket.Send(bytes); //send everything to all clients as bytes } } while (result.ToLower().Trim() != "exit"); }
И здесь вы более или менее выполняете свой сервер. Далее ваш клиент
Клиент:
-
Аналогичным образом поместите класс
Socket
в контекст класса, а не в контекст метода, и инициализируйте его, как только вы запустите свою программуconst int PORT_NO = 2201; const string SERVER_IP = "127.0.0.1"; static Socket clientSocket; //put here static void Main(string[] args) { //Similarly, start defining your client socket as soon as you start. clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //your other main routines }
-
Затем подключитесь к
Async
BeginConnect
. Я обычно пошел бы дальшеLoopConnect
только для обработки ошибок, как это.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; } }
-
Похожая концепция того, что вы делаете на сервере
BeginAccept
, вам нужно определитьendConnectCallback
для используемогоAsync
BeginConnect
. Но здесь в отличие от сервера, который необходимо перезвонитьBeginAccept
, после того, как вы подключены, вам не нужно делать никаких новыхBeginConnect
, так как вам нужно только подключиться один раз. -
Вы можете захотеть объявить
buffer
и т.д. Затем, после того, как вы подключитесь, не забудьте следующийAsync
BeginReceive
обрабатывать часть извлечения сообщений (аналогично серверу)private const int BUFFER_SIZE = 4096; private static byte[] buffer = new byte[BUFFER_SIZE]; //buffer size is limited to BUFFER_SIZE per message 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()); } }
-
Естественно, вам нужно определить свой
receiveCallback
, как и то, что вы сделали для сервера. И да, это, как вы догадались, почти идентично тому, что вы сделали для сервера! -
Вы можете делать все, что хотите, своими данными. Обратите внимание, что данные, которые вы получаете, фактически находятся в
byte[]
, а неstring
. Таким образом, вы можете сделать с ней все. Но , например, саке, я просто используюstring
для отображения.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); //copy the data from your buffer //DO ANYTHING THAT YOU WANT WITH data, IT IS THE RECEIVED PACKET! //Notice that your data is not string! It is actually byte[] //For now I will just print it out 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) { //not exceeding the max attempt, try again ++receiveAttempt; socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); } else { //completely fails! Console.WriteLine("receiveCallback is failed!"); receiveAttempt = 0; clientSocket.Close(); } } } catch (Exception e) { // this exception will happen when "this" is be disposed... Console.WriteLine("receiveCallback is failed! " + e.ToString()); } }
-
И в самом последнем... Да, опять же, как вы уже догадались, вам просто нужно что-то сделать в своей основной рутине - предположите, что вы хотите использовать его для отправки данных. Поскольку вы используете
Console
, но хотите, чтобы он отправлял вещи какbyte[]
, вам необходимо выполнить преобразование (см. Пояснение на сервере 9.). И после этого вы полностью закончите!static void Main(string[] args) { //Similarly, start defining your client socket as soon as you start. clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); loopConnect(3, 3); //for failure handling string result = ""; do { result = Console.ReadLine(); //you need to change this part if (result.ToLower().Trim() != "exit") { byte[] bytes = Encoding.ASCII.GetBytes(result); //Again, note that your data is actually of byte[], not string //do something on bytes by using the reference such that you can type in HEX STRING but sending thing in bytes clientSocket.Send(bytes); } } while (result.ToLower().Trim() != "exit"); }
Результаты:
Здесь вы идете! Я протестировал его, отправив string
для отображения, но я уже установил, что нужно, когда вы хотите изменить его на byte[]
Код для вашего теста:
Сервер
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) {
//---listen at the specified IP and port no.---
Console.WriteLine("Listening...");
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO));
serverSocket.Listen(4); //the maximum pending client, define as you wish
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]; //buffer size is limited to BUFFER_SIZE per message
private static void acceptCallback(IAsyncResult result) { //if the buffer is old, then there might already be something there...
Socket socket = null;
try {
socket = serverSocket.EndAccept(result); // The objectDisposedException will come here... thus, it is to be expected!
//Do something as you see it needs on client acceptance
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null); //to receive another client
} catch (Exception e) { // this exception will happen when "this" is be disposed...
//Do something here
Console.WriteLine(e.ToString());
}
}
const int MAX_RECEIVE_ATTEMPT = 10;
static int receiveAttempt = 0; //this is not fool proof, obviously, since actually you must have multiple of this for multiple clients, but for the sake of simplicity I put this
private static void receiveCallback(IAsyncResult result) {
Socket socket = null;
try {
socket = (Socket)result.AsyncState; //this is to get the sender
if (socket.Connected) { //simple checking
int received = socket.EndReceive(result);
if (received > 0) {
byte[] data = new byte[received]; //the data is in the byte[] format, not string!
Buffer.BlockCopy(buffer, 0, data, 0, data.Length); //There are several way to do this according to https://stackoverflow.com/questions/5099604/any-faster-way-of-copying-arrays-in-c in general, System.Buffer.memcpyimpl is the fastest
//DO SOMETHING ON THE DATA int byte[]!! Yihaa!!
Console.WriteLine(Encoding.UTF8.GetString(data)); //Here I just print it, but you need to do something else
//Message retrieval part
//Suppose you only want to declare that you receive data from a client to that client
string msg = "I receive your message on: " + DateTime.Now;
socket.Send(Encoding.ASCII.GetBytes(msg)); //Note that you actually send data in byte[]
Console.WriteLine("I sent this message to the client: " + msg);
receiveAttempt = 0; //reset receive attempt
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive
} else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //fail but not exceeding max attempt, repeats
++receiveAttempt; //increase receive attempt;
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive
} else { //completely fails!
Console.WriteLine("receiveCallback fails!"); //don't repeat beginReceive
receiveAttempt = 0; //reset this for the next connection
}
}
} catch (Exception e) { // this exception will happen when "this" is be disposed...
Console.WriteLine("receiveCallback fails with exception! " + e.ToString());
}
}
}
}
Client
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; //put here
static void Main(string[] args) {
//Similarly, start defining your client socket as soon as you start.
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
loopConnect(3, 3); //for failure handling
string result = "";
do {
result = Console.ReadLine(); //you need to change this part
if (result.ToLower().Trim() != "exit") {
byte[] bytes = Encoding.ASCII.GetBytes(result); //Again, note that your data is actually of byte[], not string
//do something on bytes by using the reference such that you can type in HEX STRING but sending thing in bytes
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]; //buffer size is limited to BUFFER_SIZE per message
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); //There are several way to do this according to https://stackoverflow.com/questions/5099604/any-faster-way-of-copying-arrays-in-c in general, System.Buffer.memcpyimpl is the fastest
//DO ANYTHING THAT YOU WANT WITH data, IT IS THE RECEIVED PACKET!
//Notice that your data is not string! It is actually byte[]
//For now I will just print it out
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) { //not exceeding the max attempt, try again
++receiveAttempt;
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
} else { //completely fails!
Console.WriteLine("receiveCallback is failed!");
receiveAttempt = 0;
clientSocket.Close();
}
}
} catch (Exception e) { // this exception will happen when "this" is be disposed...
Console.WriteLine("receiveCallback is failed! " + e.ToString());
}
}
}
}
Последние комментарии (Изменить)
Поскольку приведенный выше код выполняется с использованием Console Application
, он должен выполняться с ключевым словом static main void
. И таким образом клиент Socket
, определенный выше, имеет тип static
. Это может препятствовать тому, чтобы клиент Socket
определялся несколько раз, как каждый раз, когда он "определен", поскольку он имеет тот же class
с именем Program
, он будет ссылаться на тот же Socket
(хотя это может не быть всегда, по крайней мере, в соответствии с экспериментом OP: он может успешно запускать несколько клиентов на одном компьютере).
Тем не менее, преодолеть это не так сложно. Просто подключите клиентское приложение к платформе, которая не инициирована как static
class (например, WinForms
), и все вышеприведенные коды будут работать в обычном режиме. В качестве альтернативы, если он должен быть запущен с помощью Console Applications
, и возникает проблема, просто скопируйте клиентское приложение и переопределите его с помощью другого namespace
или другого class
имени, чтобы избежать определения идентичного Socket
из-за идентичных namespace
или class
.
Но наиболее важной частью этого решения является использование Async
и Sync
мудро для решения данной проблемы.
Продолжение этой темы можно найти здесь