エントリーレベルの C# ソケット プログラミングの実装

以前忙しくてブログに注目する時間がありませんでした。ご質問に間に合わず、大変申し訳ありませんでした。ツイート内のソケットサーバーとクライアントにいくつかの改善を加えました。あなたが遭遇した問題について。
変更点は次のとおりです(このブログを初めて読む場合は、このテキストは無視してください)。

  1. サーバーに複数のクライアントが接続している場合、クライアントの上書きの問題が発生します: サーバーに接続されているクライアントを保存するためにリストが使用され、クライアント情報 (IP とポート番号) が異なります。クライアント IP は同じである可能性がありますが、ポート番号が異なりますが、同様にcomBoxコンポーネントに入力すると、comBox内で情報を送信するクライアントを選択できます。
    [ここにナレッジポイントを追加します。サーバー側で listen(value) を使用してクライアントをリッスンします。value value は接続するクライアントの数です。accept() メソッドが使用されていない場合、値は何ですか? value の場合、サーバー側は value クライアントにのみ接続できます。accept() メソッドが使用される場合、クライアント キューは別の場所に保存され、listen(value) の value 値は無効になり、サーバーはクライアントに接続できます。値を超える]
  2. 中国語の文字化け送信の問題については、Encoding.UTF8.GetBytes() を使用してエンコードし、Encoding.UTF8.GetString() を使用してデコードします。
  3. UIに若干の変更が加えられ、サーバー側にクライアントリストが追加され、受信ボックスクリアボタンが追加されました。
  4. コードが更新されました。

説明: このツイートは、C# での Socket プログラミングの実装を説明することに重点を置いています。完全な実装の GIF アニメーションがあります。最初にそれを見てください。Socket の原理の紹介はあまり多くはありませんが、多くの場合があります。原則に関しては、ここで見つけることができます。その他の情報を参照してください。
ソケット プログラミングのこの部分では、このシンプルなソフトウェアの印象を深め、誰もが理解して再現できるように、ソケット プログラミングで使用されるいくつかの関連するクラス、メソッド、実装手順を主に紹介し、段階的に紹介します。自分で関数を作成したり、他のプロジェクトに埋め込んだりする場合、ソケット関連のクラスとメソッドは少し冗長になる可能性があるため、完全な実装コードに直接ジャンプして学習することができます。

1.ソケットソケット

ソケットの定義

ソケット (Socket) は、ネットワーク内の異なるホスト上のアプリケーション プロセス間の双方向通信のためのエンドポイントを抽象化したものですソケットはネットワーク上のプロセス通信の一端であり、アプリケーション層プロセスがネットワーク プロトコルを使用してデータを交換するためのメカニズムを提供します。位置の観点から見ると、ソケットはアプリケーション プロセスに接続し、ネットワーク プロトコル スタックに接続します。これは、アプリケーション プログラムがネットワーク プロトコルを介して通信するためのインターフェイスであり、アプリケーション プログラムがネットワーク プロトコルと対話するためのインターフェイスです。 stack . Socketの位置は大まかに以下の通りです。
ここに画像の説明を挿入
5層アーキテクチャで使用されるプロトコル
Socket は2 つの Socket オブジェクトによって構築される通信パイプラインと考えることができます。パイプラインの両端は 2 つの Socket オブジェクトであり、このパイプラインの接続は2 つのホストのアプリケーション プロセスです。2 つのホスト A と B 上のアプリケーション プロセスが相互にデータを送信したいと仮定すると、Socket を使用してホスト A と B のプロセスを接続するパイプラインを構築できます。ホスト A のプロセスは、Socket オブジェクトを使用してデータをパイプラインに流し、ホストBのプロセスはSocketオブジェクトを使ってデータを取り出すことができ、逆にホストBのプロセスがホストAのプロセスにデータを送りたい場合には、ホストBのプロセスを操作するだけでデータの送受信が可能です。ソケットオブジェクト。
ここに画像の説明を挿入
コンピュータ ネットワークを学んだ学生は、インターネット上のデータ送信が上記ほど単純ではないことを知っています。古典的な 5 層プロトコル アーキテクチャ (下図を参照) によれば、メッセージはあるプロセスから別のプロセスに送信される必要があります。. 下位層プロトコルへのカプセル化、その後、複数のルートを介して物理層を介してビット ストリームに変換され、最終的に宛先ホスト (ここで使用される IP はネットワーク上の特定のホストを調整するために使用されます) に到達し、層ごとに処理されます。宛先ホストでのレイヤー分析、そして最終的にポートに応じた番号によるメッセージの宛先プロセスへの送信、ソケットを使用することで情報をレイヤーごとにカプセル化する必要がなく、情報がネットワーク上でどのように送信されるかは気にしません。 IP アドレスとアプリケーションのプロセス番号をバインドし、Socketオブジェクトを使用するだけでプロセス間でのデータの送受信が実現できるので、作業が大幅に軽減され、とても良いです。
[ここでは、コンピュータネットワークを写真とテキストでわかりやすく説明するコンピュータネットワークコースをお勧めします]
https://www.bilibili.com/video/BV1c4411d7jb?share_source=copy_web
コンピュータネットワークシステム

2、ソケットプログラミング

上記の簡単な説明から、Socket の役割は 2 つのアプリケーション間のデータ送信を完了することであることが大まかに理解できます。必要なのは、通信する 2 つのホストの IP アドレスとプロセスのポート番号だけです。 Socket を使用して 2 つの A プロセスが通信できるようにします。次にトピックに入り、C# を使用して Socket プログラミングを行い、2 つのローカル プロセス間の通信を完了します。

1.エフェクト表示

ここに画像の説明を挿入

2. ソケット通信の基本フローチャート

ここに画像の説明を挿入
上記のフローチャートによると、ソケットを使用して通信を実現するには、一般的に次の手順を完了する必要があることがわかります。
サーバー側:
ステップ 1: 通信用の Socket オブジェクトを作成する
ステップ 2: バインドを使用して IP アドレスとポート番号をバインドする
ステップ 3 : listen を使用してクライアントを監視します。
ステップ 4: accept を使用して、クライアントが接続されるまでプログラムを中断します。 ステップ
5: クライアントからリクエストを受信します。
ステップ 6: クライアントが必要とするデータを返します
。 ステップ 7: クライアントが接続を閉じた場合サーバ側

クライアント:
ステップ 1: 通信用の Socket オブジェクトを作成します。
ステップ 2: 指定された IP およびポートに従ってサーバーに接続します。
ステップ 3: 接続が成功したら、データ要求をサーバーに送信します。
ステップ 4: サーバーから返された要求データを受信します。サーバー
ステップ 5: データを要求する必要がある場合は、引き続き要求を送信します
ステップ 6: データを要求する必要がない場合は、クライアントを閉じて、接続終了メッセージをサーバーに送信します

3.ソケットプログラミングの共通クラスとメソッド

関連クラス

(1) IPAddress: IP アドレスが含まれます[インターネット プロトコル (IP) アドレスを提供します]

//这里的IP是long类型的,这里使用Parse()可以将string类型数据转成IPAddress所需要的数据类型
IPAddress IP = IPAddress.Parse();

(2) IPEndPoint: IP アドレスとポート番号のペアが含まれます

/*public IPEndPoint(IPAddress address, int port);*/
IPEndPoint endPoint = new IPEndPoint(ip,port);	//处理IP地址和端口的封装类

(3) Encoding.ASCII:エンコード変換

Encoding.ASCII.GetBytes()	//将字符串转成字节
Encoding.ASCII.GetString()	//将字节转成字符串

(4) 現在時刻を取得する

DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ")

ソケットプログラミング関数 [関数 + 例]:

(1)ソケット()

  • Socket オブジェクトを作成するには、コンストラクターで 3 つのパラメーターを入力する必要があります。クライアントおよびサーバーの Socket オブジェクトの作成例は次のとおりです。
Socket ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Socket ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

ソケットコンストラクターのパラメーター

  • AddressFamily は、 Socket がアドレスを解決するために使用するアドレス指定スキームを指定します。
  • SocketType は開くソケットのタイプを定義します
  • ProtocolType : 要求されたプロトコルを Windows Sockets API に通知します

(2)バインド()

  • ローカル IP とポート番号をバインドします。パラメータは、IP とポート番号にバインドされた IPEndPoint オブジェクトです。
ServerSocket.Bind(new IPEndPoint(ip,port));
或者
IPEndPoint ipEndPoint = new IPEndPoint(ip,port)
ServerSocket.Bind(ipEndPoint);

(3)聞く()

  • Socket が着信接続をリッスンできるようにします。パラメーターは、指定されたリッスン キューの容量です。
ServerSocket.Listen(10);

(4)接続()

  • リモートホストへの接続を確立する
ClientSocket.Connect(ipEndPoint);

(5)承諾()

  • 接続を受信して​​新しいソケットを返します。Accept はクライアント接続が確立されるまでプログラムを中断します。
Socket socket = ServerSocket.Accept();

(6)送信()

  • データをソケットに出力する
//Encoding.ASCII.GetBytes()将字符串转成字节
//byte[] message = Encoding.ASCII.GetBytes("Connect the Server"); //通信时实际发送的是字节数组,所以要将发送消息转换字节
byte[] message = Encoding.UTF8.GetBytes("Connect the Server");	//防止中文乱码使用该方法对字符串进行编码
ClientSocket.Send(message);
socket.Send(message);

(7)受信()

  • ソケットからデータを読み取る
byte[] receive = new byte[1024];
int length = ClientSocket.Receive(receive); // length 接收字节数组长度
int length = socket.Receive(receive);

(8)閉じる()

  • ソケットを閉じて接続を破棄します
socket.Close()
ClientSocket.Close()
タイプ 関数
サーバーソケット Bind()
Listen()
Accept()
クライアントソケット 接続()
パブリックソケット 受信()
送信()
閉じる()

4. プログラミングの実装手順

(1) UIデザイン

サービスターミナル:
ここに画像の説明を挿入

クライアント:
ここに画像の説明を挿入

以下は私が各コンポーネントに設定した名前の値ですが、ボタンを除いてクライアントとサーバーで設定した名前の値は同じです。

コンポーネント 名前
テキストボックス textBox_Addr
textBox_Port
リッチテキストボックス richTextBox_Receive
richTextBox_Send
ボタン ボタン_承認
ボタン_接続
ボタン_閉じる
ボタン_送信
コムボックス comBox_Clients

(2) サーバー側:

実装手順:
ステップ 1: 接続をリッスンするための Socket オブジェクトを作成します。

Socket ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //创建用于监听连接的套接字

ステップ 2: 指定されたポート番号とサーバー IP を使用して IPEndPoint オブジェクトを作成します。

IPAddress IP = IPAddress.Parse(textBox_Addr.Text);	//获取输入的IP地址
int Port = int.Parse(textBox_Port.Text);	//获取输入的端口号
//IPAddress ip = IPAddress.Any;
IPEndPoint iPEndPoint = new IPEndPoint(IP, Port);	

ステップ 3: ソケット オブジェクトの Bind() メソッドを使用して IPEndPoint をバインドします。

ServerSocket.Bind(iPEndPoint);

ステップ 4: ソケット オブジェクトの Listen() メソッドでリスニングを開始します。

ServerSocket.Listen(10);

ステップ 5: クライアントとの接続を確立し、ソケット オブジェクトの Accept() メソッドを使用して、クライアントと通信するためのソケット オブジェクトを作成します (accept はプログラムを中断するため、クライアント アクセスを実現するためにスレッドを使用します)。

Socket socket = socketAccept.Accept();

ステップ 6: クライアントから情報を受信する (データの受信にはスレッドを使用します)

byte[] recieve = new byte[1024];
int len = socket.Receive(recieve);
richTextBox_Recieve.Text += Encoding.UTF8.GetString(recieve, 0, len);  //将字节数据根据ASCII码转成字符串

ステップ 7: クライアントに情報を送信する

byte[] send = new byte[1024];
send = Encoding.UTF8.GetBytes(richTextBox_Send.Text);    //将字符串转成字节格式发送
socket.Send(send);  //调用Send()向客户端发送数据

ステップ 8: ソケットを閉じる

socket.Close();     //关闭用于通信的套接字          
ServerSocket.Close();   //关闭用于连接的套接字
socketAccept.Close();   //关闭与客户端绑定的套接字
th1.Abort();    //关闭线程1

具体的な実装コード:
(1) スレッドを使用しているため、エラーを防ぐために、初期化中に検出がオフになります。

private void Form1_Load(object sender, EventArgs e)
        {
    
    
            Control.CheckForIllegalCrossThreadCalls = false;    //执行新线程时跨线程资源访问检查会提示报错,所以这里关闭检测
        }

(2) 接続を確立するには、まず ServerSocket を作成して接続に必要なすべてのパラメータを設定し、接続を監視します。次に、クライアントの接続を受け付けるスレッドを作成します。

 		/*****************************************************************/
        #region 连接客户端(绑定按钮事件)
        private void button_Accpet_Click(object sender, EventArgs e)
        {
    
    

            ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //创建用于通信的套接字
            
            richTextBox_Receive.Text += "正在连接...\n";
            button_Accpet.Enabled = false;  //禁止操作接收按钮
           
            //1.绑定IP和Port
            IPAddress IP = IPAddress.Parse(textBox_Addr.Text);
            int Port = int.Parse(textBox_Port.Text);

            //IPAddress ip = IPAddress.Any;
            IPEndPoint iPEndPoint = new IPEndPoint(IP, Port);

            try
            {
    
    
                //2.使用Bind()进行绑定
                ServerSocket.Bind(iPEndPoint);
                //3.开启监听
                //Listen(int backlog); backlog:监听数量 
                ServerSocket.Listen(10);

                /*
                 * tip:
                 * Accept会阻碍主线程的运行,一直在等待客户端的请求,
                 * 客户端如果不接入,它就会一直在这里等着,主线程卡死
                 * 所以开启一个新线程接收客户单请求
                 */

                //开启线程Accept进行通信的客户端socket
                th1 = new Thread(Listen);   //线程绑定Listen函数
                th1.IsBackground = true;    //运行线程在后台执行
                th1.Start(ServerSocket);    //Start里面的参数是Listen函数所需要的参数,这里传送的是用于通信的Socket对象
                Console.WriteLine("1");
            }
            catch
            {
    
    
                MessageBox.Show("服务器出问题了");
            }
        }
        #endregion
        /*****************************************************************/

		/*****************************************************************/
        #region 建立与客户端的连接
        void Listen(Object sk) 
        {
    
              
            try
            {
    
    
               while (true) 
               {
    
    
                    //GNFlag如果为1就进行中断连接,使用在标志为是为了保证能够顺利关闭服务器端
                    /*
                     * 当客户端关闭一次后,如果没有这个标志位会使得服务器端一直卡在中断的位置
                     * 如果服务器端一直卡在中断的位置就不能顺利关闭服务器端
                     */

                    //4.阻塞到有client连接
                    Socket Client = ServerSocket.Accept(); //声明用于与某一个客户端通信的套接字
                    socketsList.Add(Client);               //将连接的客户端存进List
                    
                    //获取客户端信息将不同客户端并存进comBox
                    string client = Client.RemoteEndPoint.ToString();
                    comboBox_Clients.Items.Add(client);
                    
                    CFlag = 0;  //连接成功,将客户端关闭标志设置为0
                    SFlag = 1;  //当连接成功,将连接成功标志设置为1

                    richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + client + "连接成功";
                    richTextBox_Receive.Text += "\r\n";

                    //开启第二个线程接收客户端数据
                    th2 = new Thread(Receive);  //线程绑定Receive函数
                    th2.IsBackground = true;    //运行线程在后台执行
                    th2.Start(Client);    //Start里面的参数是Listen函数所需要的参数,这里传送的是用于通信的Socket对象
               }
            }
            catch 
            {
    
    
                //MessageBox.Show("没有连接上客户端");   
            }
        }
        #endregion
        /*****************************************************************/

(3) クライアントに接続した後、スレッドを使用してクライアント データを受信します。パラメータは Accept する Socket オブジェクトです。

/*****************************************************************/
        #region 接收客户端数据
        void Receive(Object sk)
        {
    
    
            Socket socket = sk as Socket;  //创建用于通信的套接字(这里是线程传过来的client套接字)

            while (true)
            {
    
    
                try
                {
    
    
                    if (CFlag == 0 && SFlag == 1)
                    {
    
    
                        //5.接收数据
                        byte[] recieve = new byte[1024];
                        int len = socket.Receive(recieve);

                        //6.打印接收数据
                        if (recieve.Length > 0)
                        {
    
    
                            //如果接收到客户端停止的标志
                            if (Encoding.UTF8.GetString(recieve, 0, len) == "*close*")
                            {
    
    
                                richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "客户端已退出" + "\n";
                                CFlag = 1;      //将客户端关闭标志设置为1

                                break;      //退出循环
                            }

                            //打印接收数据
                            richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
                            richTextBox_Receive.Text += "\r\n";
                            richTextBox_Receive.Text += "接收" + socket.RemoteEndPoint.ToString() + ":";
                            //richTextBox_Receive.Text += Encoding.ASCII.GetString(recieve, 0, len);  //将字节数据根据ASCII码转成字符串
                            richTextBox_Receive.Text += Encoding.UTF8.GetString(recieve, 0, len);   //接收中文不会乱码
                            
                        }
                    }
                    else
                    {
    
    
                        break;  //跳出循环
                    } 
                }
                catch
                {
    
    
                    MessageBox.Show("收不到信息");
                }  
            }  
        }
        #endregion
/*****************************************************************/

(4) クライアントにデータを送信する

        /*****************************************************************/
        #region 向客户端发送数据
        private void button_Send_Click(object sender, EventArgs e)
        {
    
    
            //SFlag标志连接成功,同时当客户端是打开的时候才能发送数据
            if(SFlag == 1 && CFlag == 0)
            {
    
    
                byte[] send = new byte[1024];
                send = Encoding.UTF8.GetBytes(richTextBox_Send.Text);    //将字符串转成字节格式发送
                socket.Send(send);  //调用Send()向客户端发送数据

                //打印发送时间和发送的数据
                richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "发送:";
                richTextBox_Receive.Text += richTextBox_Send.Text + "\n";
                richTextBox_Send.Clear();   //清除发送框
            } 
        }
        #endregion
        /*****************************************************************/

(5) サーバー側を閉じる

/*****************************************************************/
        #region 关闭服务器端
        private void button_Close_Click(object sender, EventArgs e)
        {
    
    
            //若连接上了客户端需要关闭线程2和socket,没连接上客户端直接关闭线程1和其他套接字
            if(CFlag == 1)
            {
    
    
                th2.Abort();        //关闭线程2
                socket.Close();     //关闭用于通信的套接字
            }
            
            ServerSocket.Close();   //关闭用于连接的套接字
            socketAccept.Close();   //关闭与客户端绑定的套接字
            th1.Abort();    //关闭线程1

            CFlag = 0;  //将客户端标志重新设置为0,在进行连接时表示是打开的状态
            SFlag = 0;  //将连接成功标志程序设置为0,表示退出连接
            button_Accpet.Enabled = true;
            richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
            richTextBox_Receive.Text += "服务器已关闭" + "\n";
            MessageBox.Show("服务器已关闭");
        }
        #endregion
        /*****************************************************************/

(3) クライアント:

最初のステップ: Socket オブジェクトを作成します。

Socket ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //声明负责通信的套接字

ステップ 2: 指定されたポート番号とサーバー IP を使用して IPEndPoint オブジェクトを作成します。

IPAddress IP = IPAddress.Parse(textBox_Addr.Text);      //获取设置的IP地址
int Port = int.Parse(textBox_Port.Text);       //获取设置的端口号
IPEndPoint iPEndPoint = new IPEndPoint(IP,Port);    //指定的端口号和服务器的ip建立一个IPEndPoint对象

ステップ 3: ソケット オブジェクトの Connect() メソッドを使用して、上記で確立した EndPoint オブジェクトをパラメータとして接続要求をサーバーに送信します。

ClientSocket.Connect(iPEndPoint);

ステップ 4: 接続が成功した場合は、ソケット オブジェクトの Send() メソッドを使用してサーバーに情報を送信します。

byte[] send = new byte[1024];
send = Encoding.UTF8.GetBytes(richTextBox_Send.Text);  //将文本内容转换成字节发送
ClientSocket.Send(send);    //调用Send()函数发送数据

ステップ 5: ソケット オブジェクトの Receive() メソッドを使用してサーバー情報を受信します。

byte[] receive = new byte[1024];
ClientSocket.Receive(receive);  //调用Receive()接收字节数据

ステップ 6: 通信が終了したらソケットを閉じます。

ClientSocket.Close();   //关闭套接字

具体的なコード実装
(1) スレッドを使用し、エラーを防ぐために初期化時の検出をオフにする

private void Form1_Load(object sender, EventArgs e)
{
    
    
	Control.CheckForIllegalCrossThreadCalls = false;    //执行新线程时跨线程资源访问检查会提示报错,所以这里关闭检测
}

(2) サーバーとの接続を確立する

/*****************************************************************/
        #region 连接服务器端
        private void button_Connect_Click(object sender, EventArgs e)
        {
    
    
            ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //声明负责通信的套接字
            
            richTextBox_Recieve.Text += "正在连接...\n";
            
            IPAddress IP = IPAddress.Parse(textBox_Addr.Text);      //获取设置的IP地址
            int Port = int.Parse(textBox_Port.Text);       //获取设置的端口号
            IPEndPoint iPEndPoint = new IPEndPoint(IP,Port);    //指定的端口号和服务器的ip建立一个IPEndPoint对象

            try
            {
    
      
                ClientSocket.Connect(iPEndPoint);       //用socket对象的Connect()方法以上面建立的IPEndPoint对象做为参数,向服务器发出连接请求
                SFlag = 1;  //若连接成功将标志设置为1
                    
                richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + textBox_Addr.Text + "连接成功" + "\n";
                button_Connect.Enabled = false;     //禁止操作连接按钮
                
                //开启一个线程接收数据
                th1 = new Thread(Receive);
                th1.IsBackground = true;
                th1.Start(ClientSocket);    
            }
            catch 
            {
    
    
                MessageBox.Show("服务器未打开");
            }    
        }
        #endregion
        /*****************************************************************/

(3) 接続に成功したら、サーバーからデータを受信するスレッドを作成します。

/*****************************************************************/
        #region 接收服务器端数据
        void Receive(Object sk)
        {
    
    
            Socket socketRec = sk as Socket;

            while (true)
            {
    
    
                //5.接收数据
                byte[] receive = new byte[1024];
                ClientSocket.Receive(receive);  //调用Receive()接收字节数据

                //6.打印接收数据
                if (receive.Length > 0)
                {
    
    
                    richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "接收:";   //打印接收时间
                    richTextBox_Recieve.Text += Encoding.UTF8.GetString(receive);  //将字节数据根据ASCII码转成字符串
                    richTextBox_Recieve.Text += "\r\n";
                }
            }
        }
        #endregion
        /*****************************************************************/

(4) サーバーへのデータ送信(送信ボタンへのイベントバインディング)

/*****************************************************************/
        #region 向服务器端发送数据
        private void button_Send_Click(object sender, EventArgs e)
        {
    
    
            //只有连接成功了才能发送数据,接收部分因为是连接成功才开启线程,所以不需要使用标志
            if (SFlag == 1)
            {
    
    
                byte[] send = new byte[1024];
                send = Encoding.UTF8.GetBytes(richTextBox_Send.Text);  //将文本内容转换成字节发送
                ClientSocket.Send(send);    //调用Send()函数发送数据

                richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "发送:";   //打印发送数据的时间
                richTextBox_Recieve.Text += richTextBox_Send.Text + "\n";   //打印发送的数据
                richTextBox_Send.Clear();   //清空发送框
            }
        }
        #endregion
        /*****************************************************************/

(5) クライアントを終了する

		/*****************************************************************/
        #region 关闭客户端
        private void buttonClose_Click(object sender, EventArgs e)
        {
    
    
            //保证是在连接状态下退出
            if (SFlag == 1)
            {
    
    
                byte[] send = new byte[1024];
                send = Encoding.ASCII.GetBytes("*close*");  //关闭客户端时给服务器发送一个退出标志
                ClientSocket.Send(send);
                
                th1.Abort();    //关闭线程
                ClientSocket.Close();   //关闭套接字
                
                button_Connect.Enabled = true;  //允许操作按钮
                SFlag = 0;  //客户端退出后将连接成功标志程序设置为0
                richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
                richTextBox_Recieve.Text += "客户端已关闭" + "\n";
                MessageBox.Show("已关闭连接");
            }
        }
        #endregion
        /*****************************************************************/

5、サーバー側の完全なコード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Server
{
    
    
    public partial class Form1 : Form
    {
    
    
        //这里声明多个套接字是为了在连接,接收数据,发送数据的函数中不发生混乱,同时方便关闭
        public  Socket ServerSocket;    //声明用于监听的套接字
        public static List<Socket> socketsList = new List<Socket>();    //创建一个全局的List用来存放不同的Client套接字

        public static int SFlag = 0;    //连接成功标志
        public static int CFlag = 0;    //客户端关闭的标志

        Thread th1;     //声明线程1
        Thread th2;     //声明线程2

        public Form1()
        {
    
    
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
    
    
            //comboBox_Clients.SelectedIndex = 0;
            Control.CheckForIllegalCrossThreadCalls = false;    //执行新线程时跨线程资源访问检查会提示报错,所以这里关闭检测
        }

        /*****************************************************************/
        #region 连接客户端
        private void button_Accpet_Click(object sender, EventArgs e)
        {
    
    

            ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //创建用于通信的套接字
            
            richTextBox_Receive.Text += "正在连接...\n";
            button_Accpet.Enabled = false;  //禁止操作接收按钮
           
            //1.绑定IP和Port
            IPAddress IP = IPAddress.Parse(textBox_Addr.Text);
            int Port = int.Parse(textBox_Port.Text);

            //IPAddress ip = IPAddress.Any;
            IPEndPoint iPEndPoint = new IPEndPoint(IP, Port);

            try
            {
    
    
                //2.使用Bind()进行绑定
                ServerSocket.Bind(iPEndPoint);
                //3.开启监听
                //Listen(int backlog); backlog:监听数量 
                ServerSocket.Listen(10);

                /*
                 * tip:
                 * Accept会阻碍主线程的运行,一直在等待客户端的请求,
                 * 客户端如果不接入,它就会一直在这里等着,主线程卡死
                 * 所以开启一个新线程接收客户单请求
                 */

                //开启线程Accept进行通信的客户端socket
                th1 = new Thread(Listen);   //线程绑定Listen函数
                th1.IsBackground = true;    //运行线程在后台执行
                th1.Start(ServerSocket);    //Start里面的参数是Listen函数所需要的参数,这里传送的是用于通信的Socket对象
                Console.WriteLine("1");
            }
            catch
            {
    
    
                MessageBox.Show("服务器出问题了");
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 建立与客户端的连接
        void Listen(Object sk) 
        {
    
              
            try
            {
    
    
               while (true) 
               {
    
    
                    //GNFlag如果为1就进行中断连接,使用在标志为是为了保证能够顺利关闭服务器端
                    /*
                     * 当客户端关闭一次后,如果没有这个标志位会使得服务器端一直卡在中断的位置
                     * 如果服务器端一直卡在中断的位置就不能顺利关闭服务器端
                     */

                    //4.阻塞到有client连接
                    Socket Client = ServerSocket.Accept(); //声明用于与某一个客户端通信的套接字
                    socketsList.Add(Client);               //将连接的客户端存进List
                    
                    //获取客户端信息将不同客户端并存进comBox
                    string client = Client.RemoteEndPoint.ToString();
                    comboBox_Clients.Items.Add(client);
                    comboBox_Clients.SelectedIndex = 0;
                    
                    CFlag = 0;  //连接成功,将客户端关闭标志设置为0
                    SFlag = 1;  //当连接成功,将连接成功标志设置为1

                    richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + client + "连接成功";
                    richTextBox_Receive.Text += "\r\n";

                    //开启第二个线程接收客户端数据
                    th2 = new Thread(Receive);  //线程绑定Receive函数
                    th2.IsBackground = true;    //运行线程在后台执行
                    th2.Start(Client);    //Start里面的参数是Listen函数所需要的参数,这里传送的是用于通信的Socket对象
               }
            }
            catch 
            {
    
    
                //MessageBox.Show("没有连接上客户端");   
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 接收客户端数据
        void Receive(Object sk)
        {
    
    
            Socket socket = sk as Socket;  //创建用于通信的套接字(这里是线程传过来的client套接字)

            while (true)
            {
    
    
                try
                {
    
    
                    if (CFlag == 0 && SFlag == 1)
                    {
    
    
                        //5.接收数据
                        byte[] recieve = new byte[1024];
                        int len = socket.Receive(recieve);

                        //6.打印接收数据
                        if (recieve.Length > 0)
                        {
    
    
                            //如果接收到客户端停止的标志
                            if (Encoding.UTF8.GetString(recieve, 0, len) == "*close*")
                            {
    
    
                                richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "客户端已退出" + "\n";
                                CFlag = 1;      //将客户端关闭标志设置为1

                                break;      //退出循环
                            }

                            //打印接收数据
                            richTextBox_Receive.Text += "*" + DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
                            richTextBox_Receive.Text += "接收" + socket.RemoteEndPoint.ToString() + ":";
                            //richTextBox_Receive.Text += Encoding.ASCII.GetString(recieve, 0, len);  //将字节数据根据ASCII码转成字符串
                            richTextBox_Receive.Text += Encoding.UTF8.GetString(recieve, 0, len);   //接收中文不会乱码
                            richTextBox_Receive.Text += "\r\n";
                        }
                    }
                    else
                    {
    
    
                        break;  //跳出循环
                    } 
                }
                catch
                {
    
    
                    MessageBox.Show("收不到信息");
                }  
            }  
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 向客户端发送数据
        private void button_Send_Click(object sender, EventArgs e)
        {
    
    
            //SFlag标志连接成功,同时当客户端是打开的时候才能发送数据
            if(SFlag == 1 && CFlag == 0)
            {
    
    
                byte[] send = new byte[1024];
                //send = Encoding.ASCII.GetBytes(richTextBox_Send.Text);    //将字符串转成字节格式发送
                send = Encoding.UTF8.GetBytes(richTextBox_Send.Text);
                /*
                 * 上面将每一个连接的client的套接字信息(ip和端口号)存放进了combox
                 * 我们可以在combox中选择需要通信的客户端
                 * 通过comboBox_Clients.SelectedIndex获取选择的index,此index对于List中的socket对象
                 * 从而实现对选择的客户端发送信息
                 */
                int i = comboBox_Clients.SelectedIndex;
                string client = comboBox_Clients.Text;
                socketsList[i].Send(send); //调用Send()向客户端发送数据

                //打印发送时间和发送的数据
                richTextBox_Receive.Text += "*" +  DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "向" + client +  "发送:";
                richTextBox_Receive.Text += richTextBox_Send.Text + "\n";
                richTextBox_Send.Clear();   //清除发送框
            } 
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 关闭服务器端
        private void button_Close_Click(object sender, EventArgs e)
        {
    
    
            //若连接上了客户端需要关闭线程2和socket,没连接上客户端直接关闭线程1和其他套接字
            if(CFlag == 1)
            {
    
    
                th2.Abort();        //关闭线程2
                foreach(Socket s in socketsList)
                    s.Close();     //关闭用于通信的套接字
            }
            
            ServerSocket.Close();   //关闭用于连接的套接字
            //socketAccept.Close();   //关闭与客户端绑定的套接字
            th1.Abort();    //关闭线程1

            CFlag = 0;  //将客户端标志重新设置为0,在进行连接时表示是打开的状态
            SFlag = 0;  //将连接成功标志程序设置为0,表示退出连接
            button_Accpet.Enabled = true;
            richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
            richTextBox_Receive.Text += "服务器已关闭" + "\n";
            MessageBox.Show("服务器已关闭");
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 点击enter发送数据
        private void richTextBox_Send_KeyDown(object sender, KeyEventArgs e)
        {
    
    
            if (e.KeyCode == Keys.Enter)//如果输入的是回车键  
            {
    
    
                this.button_Send_Click(sender, e);//触发button事件  
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 点击清除接收框
        private void button_Clear_Click(object sender, EventArgs e)
        {
    
    
            richTextBox_Receive.Clear();
        }
        #endregion
        /*****************************************************************/
    }
}

4. クライアントコードを完成させる

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Client
{
    
    
    public partial class Form1 : Form
    {
    
    
        public static Socket ClientSocket;  //声明负责通信的socket
        public static int SFlag = 0;    //连接服务器成功标志
        Thread th1;     //声明一个线程

        public Form1()
        {
    
    
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
    
     
            Control.CheckForIllegalCrossThreadCalls = false;    //执行新线程时跨线程资源访问检查会提示报错,所以这里关闭检测
        }

        /*****************************************************************/
        #region 连接服务器端
        private void button_Connect_Click(object sender, EventArgs e)
        {
    
    
            ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //声明负责通信的套接字
            
            richTextBox_Recieve.Text += "正在连接...\n";
            
            IPAddress IP = IPAddress.Parse(textBox_Addr.Text);      //获取设置的IP地址
            int Port = int.Parse(textBox_Port.Text);       //获取设置的端口号
            IPEndPoint iPEndPoint = new IPEndPoint(IP,Port);    //指定的端口号和服务器的ip建立一个IPEndPoint对象

            try
            {
    
      
                ClientSocket.Connect(iPEndPoint);       //用socket对象的Connect()方法以上面建立的IPEndPoint对象做为参数,向服务器发出连接请求
                SFlag = 1;  //若连接成功将标志设置为1
                    
                richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + textBox_Addr.Text + "连接成功" + "\n";
                button_Connect.Enabled = false;     //禁止操作连接按钮
                
                //开启一个线程接收数据
                th1 = new Thread(Receive);
                th1.IsBackground = true;
                th1.Start(ClientSocket);    
            }
            catch 
            {
    
    
                MessageBox.Show("服务器未打开");
            }    
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 接收服务器端数据
        void Receive(Object sk)
        {
    
    
            Socket socketRec = sk as Socket;

            while (true)
            {
    
    
                //5.接收数据
                byte[] receive = new byte[1024];
                ClientSocket.Receive(receive);  //调用Receive()接收字节数据

                //6.打印接收数据
                if (receive.Length > 0)
                {
    
    
                    richTextBox_Recieve.Text += "*" + DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "接收:";   //打印接收时间
                    //richTextBox_Recieve.Text += Encoding.ASCII.GetString(receive);  //将字节数据根据ASCII码转成字符串
                    richTextBox_Recieve.Text += Encoding.UTF8.GetString(receive);     //使用UTF8编码接收中文不会乱码
                    richTextBox_Recieve.Text += "\r\n";
                }
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 向服务器端发送数据
        private void button_Send_Click(object sender, EventArgs e)
        {
    
    
            //只有连接成功了才能发送数据,接收部分因为是连接成功才开启线程,所以不需要使用标志
            if (SFlag == 1)
            {
    
    
                byte[] send = new byte[1024];
                //send = Encoding.ASCII.GetBytes(richTextBox_Send.Text);  //将文本内容转换成字节发送
                send = Encoding.UTF8.GetBytes(richTextBox_Send.Text); //解决中文乱码问题
                ClientSocket.Send(send);    //调用Send()函数发送数据

                richTextBox_Recieve.Text += "*" + DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "发送:";   //打印发送数据的时间
                richTextBox_Recieve.Text += richTextBox_Send.Text + "\n";   //打印发送的数据
                richTextBox_Send.Clear();   //清空发送框
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 关闭客户端
        private void buttonClose_Click(object sender, EventArgs e)
        {
    
    
            //保证是在连接状态下退出
            if (SFlag == 1)
            {
    
    
                byte[] send = new byte[1024];
                send = Encoding.ASCII.GetBytes("*close*");  //关闭客户端时给服务器发送一个退出标志
                ClientSocket.Send(send);
                
                th1.Abort();    //关闭线程
                ClientSocket.Close();   //关闭套接字
                button_Connect.Enabled = true;  //允许操作按钮
                SFlag = 0;  //客户端退出后将连接成功标志程序设置为0
                richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
                richTextBox_Recieve.Text += "客户端已关闭" + "\n";
                MessageBox.Show("已关闭连接");
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 点击enter发送数据
        private void richTextBox_Send_KeyDown(object sender, KeyEventArgs e)
        {
    
    
            if (e.KeyCode == Keys.Enter)//如果输入的是回车键  
            {
    
    
                this.button_Send_Click(sender, e);//触发button事件  
            }
        }

        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 点击清除接收框
        private void button_Clear_Click(object sender, EventArgs e)
        {
    
    
            richTextBox_Recieve.Clear();
        }
        #endregion
        /*****************************************************************/
    }
}

おすすめ

転載: blog.csdn.net/weixin_49959955/article/details/123798500