質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.35%

  • C#

    9680questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

C# Socket通信の切断について

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 248

akiya_0608

score 5

質問

C#のソケット通信について質問なのですが、クライアント側が強制終了した場合,サーバー側まで強制終了されてしまうのでどうにか出来ないでしょうか。

サーバーとクライアントが通信しているときにクライアント側でControl+Cで強制終了させてみたところサーバー側が固まってしまいます。
何かアイデアを頂けると幸いです。

サーバー:

class Program
{   
    static void Main(string[] args)
    {
        //Server
        BaseServer listener = new BaseServer();
        listener.Init("127.0.0.1", 12345);
        var task = listener.StartAccept();

        while (true)
        {
            listener.BeginUpdate();

            if (listener.clientList.Count > 0)
            {
                foreach (TcpRecvServer client in listener.clientList)
                {
                    while (client.RecvDataListCount() > 0)
                    {
                        byte[] data = client.GetRecvDataList();
                        if(client.Send(data, data.Length)<0)
                        {
                            break;
                        }
                        Console.WriteLine("data合計={0}", client.RecvDataListCount());
                    }
                }
            }

            listener.EndUpdate();
        }

    }

}               
class TcpRecvServer
{
    public const int BUFSIZE = 2048;
    object lockObj = new object();
    public Socket socket { get; }
    public byte[] ReceiveBuffer;
    public List<byte> recvTempDataList = new List<byte>();
    public List<byte[]> recvDataList = new List<byte[]>();
    public bool deleteFlg { get; private set; } = false;
    public bool threadDeleteFlg { get; private set; } = false;
    public TcpRecvServer(Socket _socket)
    {
        socket = _socket;
        ReceiveBuffer = new byte[BUFSIZE];
    }
    public byte[] GetRecvDataList()
    {
        byte[] returnData;
        lock (lockObj)
        {
            returnData = recvDataList[0];
            recvDataList.RemoveAt(0);
        }
        return returnData;
    }

    public void AddRecvDateList(byte[] _data)
    {
        lock (lockObj)
        {
            recvDataList.Add(_data);
        }
    }

    public int RecvDataListCount()
    {
        int count;
        lock (lockObj)
        {
             count= recvDataList.Count;
        }
        return count;
    }


    public int Send(byte[] _sendData, int _dataSize)
    {
        int sendSize=-1;

        byte[] sendHeader;

        //header設定
        sendHeader = BitConverter.GetBytes(_dataSize);
        //sendBytes作成
        byte[] sendBytes = new byte[sendHeader.Length + _dataSize];
        sendHeader.CopyTo(sendBytes, 0);
        _sendData.CopyTo(sendBytes, sendHeader.Length);

        try
        {
            if (socket.Connected)
            {
                sendSize=socket.Send(sendBytes, sendBytes.Length, 0);
            }
            else
            {
                Console.WriteLine("sendError");
            }
        }
        catch(System.ObjectDisposedException)
        {
            //閉じた時
            /*
            System.Console.WriteLine("閉じました。");
            this.OnDeleteFlg();
            */          
        }
        catch (SocketException e)
        {
            Console.WriteLine("ソケットが切断されています。");
            Console.WriteLine(e);
        }

        return sendSize;
    }

    public void OnDeleteFlg()
    {
        lock (lockObj)
        {
            deleteFlg = true;
        }
    }
    public void OnThreadDeleteFlg()
    {
        lock (lockObj)
        {
            deleteFlg = true;
        }
    }

}


class BaseServer
{
    public List<TcpRecvServer> clientList { get;} = new List<TcpRecvServer>();
    private List<TcpRecvServer> addClientList = new List<TcpRecvServer>();
    private readonly object lockObj=new Object();
    Socket listener;

    public BaseServer() { }


    public void BeginUpdate()
    {
        //addClientListのための排他制御
        lock (lockObj)
        {
            if (addClientList.Count > 0)
            {
                clientList.AddRange(addClientList);
                addClientList = new List<TcpRecvServer>();
            }
        }
    }

    public void EndUpdate()
    {
        if (clientList.Count <= 0) return;

        List<TcpRecvServer> deleteList = new List<TcpRecvServer>();
        foreach (TcpRecvServer client in clientList)
        {
            if (client.deleteFlg == true)
            {
                deleteList.Add(client);
            }
        }
        foreach (TcpRecvServer client in deleteList)
        {
            clientList.Remove(client);
        }
    }
    public void Init(string _ip, int _port)
    {
        //ListenするIPアドレス
        IPAddress ipAdd = IPAddress.Parse(_ip);

        //TcpListenerオブジェクトを作成する
        IPEndPoint ipe = new IPEndPoint(ipAdd, _port);
        listener = new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        listener.Bind(ipe);
        listener.Listen(1000);

    }

    public async Task StartAccept()
    {
        await Task.Run(() =>
        {
            while (true)
            {
                TcpRecvServer temp =new TcpRecvServer(listener.Accept());
                lock (lockObj)
                {
                    addClientList.Add(temp);
                    Console.WriteLine("接続あり");
                }
                StartReceive(temp);
            }
        });
    }


    //===========================================================================
    //recv関係
    //===========================================================================
    //データ受信スタート
    private static void StartReceive(TcpRecvServer _server)
    {
        //非同期受信を開始
        _server.socket.BeginReceive(_server.ReceiveBuffer,
                0,
                _server.ReceiveBuffer.Length,
                System.Net.Sockets.SocketFlags.None,
                new System.AsyncCallback(ReceiveDataCallback),
                _server);
    }


    //BeginReceiveのコールバック
    private static void ReceiveDataCallback(System.IAsyncResult ar)
    {
        //状態オブジェクトの取得
        TcpRecvServer server = (TcpRecvServer)ar.AsyncState;

        //読み込んだ長さを取得
        int len = 0;
        try
        {
            if(server.socket.Connected)len = server.socket.EndReceive(ar);
        }
        catch (System.ObjectDisposedException)
        {
            //閉じた時
            System.Console.WriteLine("閉じました。");
            server.OnDeleteFlg();
            return;
        }

        //切断されたか調べる
        if (len <= 0)
        {
            System.Console.WriteLine("切断されました。");
            server.socket.Close();
            server.OnDeleteFlg();
            return;
        }

        //受信したデータを蓄積する
        Array.Resize(ref server.ReceiveBuffer, len);
        server.recvTempDataList.AddRange(server.ReceiveBuffer);

        //受信用配列のサイズを元に戻す
        Array.Resize(ref server.ReceiveBuffer, TcpRecvServer.BUFSIZE);

        //データの整形
        while (server.recvTempDataList.Count>sizeof(int))
        {
            int byteSize = (int)server.recvTempDataList[0];
            if (server.recvTempDataList.Count > byteSize+sizeof(int))
            {
                byte[] addData;
                addData = server.recvTempDataList.GetRange(sizeof(int), byteSize).ToArray();
                server.AddRecvDateList(addData);
                server.recvTempDataList.RemoveRange(0, sizeof(int) + byteSize);
            }
            else
            {
                break;
            }
        }

        //再び受信開始
        if(server.socket.Connected)server.socket.BeginReceive(server.ReceiveBuffer,
                0,
                server.ReceiveBuffer.Length,
                System.Net.Sockets.SocketFlags.None,
                new System.AsyncCallback(ReceiveDataCallback),
                server);
    }

}

実装環境

mac
visual studio for mac

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

check解決した方法

0

Releaseモードで実行することで解決致しました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

クライアント側ではなくサーバー側を修正してください。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/06/26 14:36

    サーバー側のプログラムを質問に記述してみましたが、どの辺りを修正するべきでしょうか

    キャンセル

  • 2019/06/26 14:51

    原因を探るには結果を知ることが大きなヒントになると思います。強制終了されるのと固まるのとでは結果が違います。

    次にサーバーの方を IDE からデバッグ起動し、クライアントを接続してから一時停止してください。ショートカットはデフォルトで [Shift]+[F5] です。

    そして [F11] で一行ずつステップ実行していくと、どこで不具合が生じるのかが特定できます。その際、例外が発生するのであれば、何という例外なのかもメモしておいてください。

    これで不具合の原因となる箇所とその種類が特定できるはずです。それを見て、より詳細な調査の方針を決めることになります。

    キャンセル

  • 2019/06/26 23:43

    Zuishinさんのおっしゃられるデバッグも既にしておりそこから私の推測では、class BaseServerのReceiveDataCallbackで恐らくエラーが出ているのだと思います。
    というのもmain関数内の処理は一通りコメントアウトしてもエラーが出るのですがStartReceiveをコメントアウトするとエラーが出なくなるからです。
    ここでいうエラーは「ソースは利用できません。このモジュールのデバッグ情報にはソース情報がありません。」という文言でこれが表示されてプログラムが止まっています。
    ちなみにReceiveDataCallbackにブレイクポイントを貼ると別スレッドで切断処理をされてしまうのでエラーが出てくれません。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • C#

    9680questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。