C#でのTCPネットワーキング-自転車





ご挨拶!



プログラミングに専念する一連の投稿を続けます。今回は、.Netアプリケーション間のTCP接続を介したネットワーク通信について説明します。 この記事は、初心者または.Netに関連するネットワークにまだ出会っていない人に役立つかもしれません。 完全に機能する例が添付されています: http : //yadi.sk/d/1OxmAFuCN3kmc



カットの下の詳細。



なぜこの記事が必要なのですか?


もちろん、現時点では、同じWCFのネットワーク用に多数のさまざまなライブラリを使用できますが、異なるプログラミング言語を含む2つのアプリケーションを接続する機能は有用です。



理論のビット


ネットワーク接続は実際にはストリームであり、クライアントはバイトを書き込み、サーバーは読み取りを行います。

したがって、送信側でシリアル化し、受信側でデシリアライズする必要があるコマンドのメカニズムを実装する必要があります。



私の実装


一般に、コマンドは、「ToBytes」と「FromBytes」の2つのメソッドと、受信側に渡すプロパティのセットを持つオブジェクトです。

シングルコマンド
public class SingleCommand: BaseCommand { public int IntField { get; set; } public decimal DecimalField { get; set; } //     public override byte[] ToBytes() { //   const int messageLenght = sizeof(int) + sizeof(decimal); //        var messageData = new byte[messageLenght]; using (var stream = new MemoryStream(messageData)) { //     var writer = new BinaryWriter(stream); writer.Write(IntField); writer.Write(DecimalField); return messageData; } } //    ,             public static SingleCommand FromBytes(byte[] bytes) { using (var ms = new MemoryStream(bytes)) { var br = new BinaryReader(ms); var command = new SingleCommand(); command.IntField = br.ReadInt32(); command.DecimalField = br.ReadDecimal(); return command; } } }
      
      







文字列などの可変長プロパティを含むコマンドを送信する必要がある場合、この文字列の長さをプロパティで指定する必要があります。

StringCommand
 public class StringCommand : BaseCommand { //   private int StringFieldLenght { get; set; } // public string StringField { get; set; } public override byte[] ToBytes() { //     byte[] stringFieldBytes = CommandUtils.GetBytes(StringField); // -   StringFieldLenght = stringFieldBytes.Length; //     int messageLenght = sizeof(int) + StringFieldLenght; var messageData = new byte[messageLenght]; using (var stream = new MemoryStream(messageData)) { var writer = new BinaryWriter(stream); //     writer.Write(StringFieldLenght); //   writer.Write(stringFieldBytes); return messageData; } } public static StringCommand FromBytes(byte[] bytes) { using (var ms = new MemoryStream(bytes)) { var br = new BinaryReader(ms); var command = new StringCommand(); //     command.StringFieldLenght = br.ReadInt32(); //          command.StringField = CommandUtils.GetString(br.ReadBytes(command.StringFieldLenght)); return command; } } }
      
      







受信側がどのチームから来たのかを知るためには、コマンドを送信する前に番号を送信するヘッダーを送信する必要があります(この例では、この瞬間は見逃され、受信は1つのコマンドでのみ実行されます)およびコマンドのタイプ:

コマンドヘッダー
 public struct CommandHeader { //  ,   CommandTypeEnum public int Type { get; set; } //   public int Count { get; set; } public static int GetLenght() { return sizeof(int) * 2; } public static CommandHeader FromBytes(byte[] bytes) { using (var ms = new MemoryStream(bytes)) { var br = new BinaryReader(ms); var currentObject = new CommandHeader(); currentObject.Type = br.ReadInt32(); currentObject.Count = br.ReadInt32(); return currentObject; } } public byte[] ToBytes() { var data = new byte[GetLenght()]; using (var stream = new MemoryStream(data)) { var writer = new BinaryWriter(stream); writer.Write(Type); writer.Write(Count); return data; } } }
      
      







私の場合、サーバーは次のアルゴリズムに従ってクライアントと対話します。

1.クライアントが接続を作成します。

2.コマンドを送信します

3.応答を受け取ります。

4.接続を閉じます。

5.サーバーからの応答が来なかった場合、タイムアウトにより切断されます。



サーバーへのコマンドの送信:

サーバーにコマンドを送信するメソッドを呼び出す
 //     var stringCommand = new StringCommand { StringField = stringCommandTextBox.Text }; //    CommandSender.SendCommandToServer("127.0.0.1", stringCommand, CommandTypeEnum.StringCommand);
      
      







サーバー送信コマンドメソッドの本体
 public static void SendCommandToServer(string serverIp, BaseCommand command, CommandTypeEnum typeEnum) { //  ,      var commandHeader = new CommandHeader { Count = 1, Type = (int)typeEnum }; //     byte[] commandBytes = CommandUtils.ConcatByteArrays(commandHeader.ToBytes(), command.ToBytes()); //   SendCommandToServer(serverIp, Settings.Port, commandBytes); } private static void SendCommandToServer(string ipAddress, int port, byte[] messageBytes) { var client = new TcpClient(); try { client.Connect(ipAddress, port); // 4      byte[] messageBytesWithEof = CommandUtils.AddCommandLength(messageBytes); NetworkStream networkStream = client.GetStream(); networkStream.Write(messageBytesWithEof, 0, messageBytesWithEof.Length); //      MessageHandler.HandleClientMessage(client); } catch (SocketException exception) { Trace.WriteLine(exception.Message + " " + exception.InnerException); } }
      
      







サーバー側のクライアントからコマンドを受信する
 public class CommandListener { private readonly TcpListener _tcpListener; private Thread _listenThread; private bool _continueListen = true; public CommandListener() { //      _tcpListener = new TcpListener(IPAddress.Any, Settings.Port); } public void Start() { //      _listenThread = new Thread(ListenForClients); _listenThread.Start(); } private void ListenForClients() { _tcpListener.Start(); while (_continueListen) { TcpClient client = _tcpListener.AcceptTcpClient(); //        var clientThread = new Thread(HandleClientCommand); clientThread.Start(client); } _tcpListener.Stop(); } private void HandleClientCommand(object client) { //  MessageHandler.HandleClientMessage(client); } public void Stop() { _continueListen = false; _tcpListener.Stop(); _listenThread.Abort(); } }
      
      







受信したコマンドの処理:
 public static void HandleClientMessage(object client) { var tcpClient = (TcpClient)client; //     tcpClient.ReceiveTimeout = 3; //  NetworkStream clientStream = tcpClient.GetStream(); var ms = new MemoryStream(); var binaryWriter = new BinaryWriter(ms); var message = new byte[tcpClient.ReceiveBufferSize]; var messageLenght = new byte[4]; int readCount; int totalReadMessageBytes = 0; //    clientStream.Read(messageLenght, 0, 4); //    int messageLength = CommandUtils.BytesToInt(messageLenght); //          while ((readCount = clientStream.Read(message, 0, tcpClient.ReceiveBufferSize)) != 0) { binaryWriter.Write(message, 0, readCount); totalReadMessageBytes += readCount; if (totalReadMessageBytes >= messageLength) break; } if (ms.Length > 0) { //   Parse(ms.ToArray(), tcpClient); } } private static void Parse(byte[] bytes, TcpClient tcpClient) { if (bytes.Length >= CommandHeader.GetLenght()) { CommandHeader commandHeader = CommandHeader.FromBytes(bytes); IEnumerable<byte> nextCommandBytes = bytes.Skip(CommandHeader.GetLenght()); var commandTypeEnum = (CommandTypeEnum)commandHeader.Type; if (commandTypeEnum == CommandTypeEnum.MessageAccepted) { if (OnMessageAccepted != null) OnMessageAccepted(); } else { BaseCommand baseCommand = BytesToCommands[commandTypeEnum].Invoke(nextCommandBytes.ToArray()); switch (commandTypeEnum) { case CommandTypeEnum.StringCommand: if (OnStringCommand != null) OnStringCommand((StringCommand)baseCommand, tcpClient); break; case CommandTypeEnum.SingleCommand: if (OnSingleCommand != null) OnSingleCommand((SingleCommand)baseCommand, tcpClient); break; case CommandTypeEnum.FileCommand: if (OnSingleCommand != null) OnFileCommand((FileCommand)baseCommand, tcpClient); break; case CommandTypeEnum.SaveUserCommand: if (OnSingleCommand != null) OnSaveUserCommand((SaveUserCommand)baseCommand, tcpClient); break; } } } }
      
      







Javaインタラクション


このコマンドは、1つの値をサーバーに渡します

チーム
 package com.offviewclient.network.commands; import java.io.*; public class IntCommand implements Serializable { public int IntNumber; public static int GetLenght() { return 4 ; } public static IntCommand FromBytes(byte[] bytes) throws IOException { ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); DataInputStream ois = new DataInputStream(inputStream); IntCommand commandType = new IntCommand(); commandType.IntNumber = ois.readInt(); return commandType; } public byte[] ToBytes() throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream oos = new DataOutputStream (bos); oos.writeInt(this.IntNumber); byte[] yourBytes = bos.toByteArray(); oos.close(); bos.close(); return yourBytes; } }
      
      







コマンドの送信とサーバーからの応答の受信(作業ドラフトのコード):
  private void SendPacket(byte[] packetBytes) throws IOException { byte[] packetBytesWithEOF = CommandUtils.AddCommandLength(packetBytes); Socket socket = new Socket(serverIP, port); socket.setSoTimeout(5000); OutputStream socketOutputStream = socket.getOutputStream(); socketOutputStream.write(packetBytesWithEOF); byte[] answerBytes = ReadAnswerBytes(socket); socket.close(); Parse(answerBytes); } private byte[] ReadAnswerBytes(Socket socket) throws IOException { InputStream out = socket.getInputStream(); DataInputStream dis = new DataInputStream(out); ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream binaryWriter = new DataOutputStream (bos); int readCount; byte[] message = new byte[10000]; byte[] messageLength = new byte[4]; dis.read(messageLength , 0, 4); int messageLength = CommandUtils.BytesToInt(messageLength); int totalReadMessageBytes = 0; while ((readCount = dis.read(message, 0, 10000)) != 0) { binaryWriter.write(message, 0, readCount); totalReadMessageBytes += readCount; if(totalReadMessageBytes >= messageLength) break; } return bos.toByteArray(); } private void Parse(byte[] messageBytes) throws IOException { if (messageBytes.length >= CommandHeader.GetLenght()) { CommandHeader commandType = CommandHeader.FromBytes(messageBytes); int skipBytes = commandType.GetLenght(); if(commandType.Type == CommandTypeEnum.MESSAGE_ACCEPTED) { RiseMessageAccepted(); } if(commandType.Type == CommandTypeEnum.SLIDE_PAGE_BYTES) { List<byte[]> drawableList = new Vector<byte[]>(); for(int i = 0; i< commandType.Count; i++) { PresentationSlideCommand presentationSlideCommand = PresentationSlideCommand.FromBytes(messageBytes, skipBytes); drawableList.add(presentationSlideCommand.FileBytes); skipBytes += presentationSlideCommand.GetLenght(); } RiseMessageAcceptSlideEvent(drawableList); } } }
      
      







Javaと.Netの相互作用における重要なポイント:.Netに関して基本タイプのバイトを保存するjavaは逆です。したがって、.Net側では、 IPAddress.HostToNetworkOrderメソッドを呼び出してすべての数値を展開する必要があります。



使用する


例として、ユーザーを保存するコマンドを受け入れるようサーバーに教えます。 ユーザーデータには姓と名が含まれます。

これを行うには、次のものが必要です。

1.新しいチームをリストに追加します

 public enum CommandTypeEnum : int { StringCommand = 1, MessageAccepted = 2, SingleCommand = 3, FileCommand = 4, SaveUserCommand = 5 //     }
      
      





2.コマンドで送信されたデータを追加します。

 public class SaveUserCommand : BaseCommand { private int FirstNameLenght { get; set; } public string FirstName { get; set; } //  private int SecondNameLenght { get; set; } public string SecondName { get; set; } // public override byte[] ToBytes() { byte[] firstNamebytes = CommandUtils.GetBytes(FirstName); FirstNameLenght = firstNamebytes.Length; byte[] secondNamebytes = CommandUtils.GetBytes(SecondName); SecondNameLenght = secondNamebytes.Length; int messageLenght = sizeof(int) * 2 + FirstNameLenght + SecondNameLenght; //   var messageData = new byte[messageLenght]; using (var stream = new MemoryStream(messageData)) { var writer = new BinaryWriter(stream); writer.Write(FirstNameLenght); writer.Write(firstNamebytes); writer.Write(SecondNameLenght); writer.Write(secondNamebytes); return messageData; } } public static SaveUserCommand FromBytes(byte[] bytes) { using (var ms = new MemoryStream(bytes)) { var br = new BinaryReader(ms); var command = new SaveUserCommand(); command.FirstNameLenght = br.ReadInt32(); command.FirstName = CommandUtils.GetString(br.ReadBytes(command.FirstNameLenght)); command.SecondNameLenght = br.ReadInt32(); command.SecondName = CommandUtils.GetString(br.ReadBytes(command.SecondNameLenght)); return command; } } }
      
      





3. MessageHandlerを新しいケースで補完します。

 case CommandTypeEnum.SaveUserCommand: if (OnSingleCommand != null) OnSaveUserCommand((SaveUserCommand)baseCommand, tcpClient); break;
      
      





4.デシリアライゼーションデリゲートの新しいマッピングでMessageHandlerを補完します

 private static void FillBytesToCommandsDictionary() { BytesToCommands.Add(CommandTypeEnum.StringCommand, StringCommand.FromBytes); BytesToCommands.Add(CommandTypeEnum.SingleCommand, SingleCommand.FromBytes); BytesToCommands.Add(CommandTypeEnum.FileCommand, FileCommand.FromBytes); BytesToCommands.Add(CommandTypeEnum.SaveUserCommand, SaveUserCommand.FromBytes); }
      
      





5.クライアント側で新しいコマンドを送信します。

 var saveUserCommand = new SaveUserCommand { FirstName = firstNameTextBox.Text, SecondName = secondNameTextBox.Text }; CommandSender.SendCommandToServer(serverIpTextBox.Text, saveUserCommand, CommandTypeEnum.SaveUserCommand);
      
      





6.サーバー側で受け入れます。

 MessageHandler.OnSaveUserCommand += CommandListener_OnSaveUserCommand; private static void CommandListener_OnSaveUserCommand(SaveUserCommand saveUserCommand, TcpClient tcpClient) { Console.WriteLine("SaveUserCommand accepted, FirstName:{0}, SecondName:{1}", saveUserCommand.FirstName, saveUserCommand.SecondName); CommandSender.SendMessageAcceptedToClient(tcpClient); }
      
      







これがすべて誰かに役立つことを願っています。 Yandexドライブのデモプロジェクト: http : //yadi.sk/d/1OxmAFuCN3kmc

みんなありがとう!



All Articles