Entry-level C# Socket programming implementation

I didn’t have time to pay attention to the blog because I was busy before. I’m very sorry that I couldn’t answer your questions in time. I have made some improvements to the socket server and client in the tweets for some of the problems you encountered.
The changes are as follows (if you read this blog for the first time, you can ignore this text):

  1. For multiple clients connecting to the server, there will be a client overwriting problem: the List is used to store the clients connected to the server, and the client information (ip and port number) is different. The client ip may be the same but the port number is different. Similarly, put it into the comBox component, and you can choose the client you want to send information in the comBox.
    [Add a knowledge point here: use listen(value) on the server side to listen to the client, the value value is the number of clients you want to connect to, if the accept() method is not used, what is the value value, the server side can only Can connect to value clients; if the accept() method is used, the client queue will be stored elsewhere, and the value value in listen(value) will be invalid, and the server can connect to clients exceeding the value value]
  2. For the problem of sending Chinese garbled characters: use Encoding.UTF8.GetBytes() to encode, and use Encoding.UTF8.GetString() to decode accordingly.
  3. Minor changes were made to the UI, the client list was added on the server side, and the receive box clear button was added.
  4. Code has been updated.

Explanation : This tweet focuses on explaining the implementation of Socket programming in C#. There is a GIF animation of the complete implementation. You can take a look at it first. There are not many introductions to the principles of Socket, and there may be many deficiencies. In terms of principles, you can find them here. See other information.
In this part of Socket programming, I mainly introduce some related classes, methods and implementation steps used in Socket programming, and introduce them step by step, in order to deepen the impression and facilitate everyone to understand and reproduce this simple software. To expand the function by yourself or embed it in other projects, the socket-related classes and methods may be a bit redundant, so you can jump directly to the complete implementation code to study and learn.

1. Socket socket

Definition of Socket

Socket (Socket) is an abstraction of endpoints for two-way communication between application processes on different hosts in the network . A socket is one end of process communication on the network, providing a mechanism for application layer processes to exchange data using network protocols. From the perspective of its position , the socket connects to the application process and connects to the network protocol stack . It is the interface for the application program to communicate through the network protocol, and the interface for the application program to interact with the network protocol stack . The location of the Socket is roughly It is as follows. We can think of Socket as a communication pipeline built by two Socket objects . The two ends of the pipeline are the two Socket objects, and the connection of this pipeline is the application process of the two hosts . Assuming that the application processes on the two hosts A and B want to send data to each other, then we can use Socket to build a pipeline connecting the processes of hosts A and B. The process of host A uses the Socket object to throw the data into the pipeline, and then host B The process can use the Socket object to take out the data. Conversely, when the B host process wants to send data to the A host process, the data can be sent and received simply by operating the Socket object. Students who have studied computer networks know that data transmission on the Internet is not as simple as the above. According to the classic five-layer protocol architecture (as shown in the figure below), a message needs to be transmitted from one process to another . Encapsulation to Lower Layer Protocols
insert image description here
Protocols used in the five-layer architecture

insert image description here
, and then converted into a bit stream through the physical layer, through multiple routes, and finally reach the destination host [the IP to be used here is used to calibrate a specific host on the network], and then go through layer-by-layer analysis on the destination host and finally according to the port The number transmits the message to the destination process, and using Socket does not require us to encapsulate information layer by layer, and we don't care how the information is transmitted on the network. We only need to bind the IP address and application process number, and use the Socket object to It can realize the sending and receiving of data between processes, which greatly reduces our work, which is very nice.
[ Recommend a computer network course here, with pictures and texts to explain computer network is very easy to understand ]
https://www.bilibili.com/video/BV1c4411d7jb?share_source=copy_web
computer network system

Two, Socket programming

Through the above brief introduction, we can roughly understand that the role of Socket is to complete the data transmission between two applications. We only need to know the IP addresses of the two hosts to communicate and the port numbers of the processes , and then we can use Socket to let the two A process communicates, and then we enter the topic, use C# for Socket programming, and complete the communication between two local processes

1. Effect display

insert image description here

2. Basic flowchart of Socket communication

insert image description here
According to the above flow chart, we can know that using socket to achieve communication generally needs to complete the following steps:
Server side:
Step 1: Create a Socket object for communication
Step 2: Use bind to bind IP address and port number
Step 3 : Use listen to monitor the client.
Step 4: Use accept to interrupt the program until the client is connected.
Step 5: Receive a request from the client.
Step 6: Return the data required by the client.
Step 7: If it is received that the client has closed the connection server-side

Client:
Step 1: Create a Socket object for communication
Step 2: Connet the server according to the specified IP and port
Step 3: After the connection is successful, send a data request to the server
Step 4: Receive the request data returned by the server
Step 5: If you still need to request data, continue to send the request.
Step 6: If you don’t need to request data, close the client and send a close connection message to the server

3.Common classes and methods for Socket programming

related class

(1) IPAddress: Contains an IP address [provides an Internet Protocol (IP) address]

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

(2) IPEndPoint: Contains a pair of IP address and port number

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

(3) Encoding.ASCII: encoding conversion

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

(4) Get the current time

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

Socket programming function [function + example]:

(1)Socket()

  • To create a Socket object, the constructor needs to input three parameters. Examples of creating client and server Socket objects are as follows
Socket ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Socket ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

Socket constructor parameters

  • AddressFamily specifies the addressing scheme that Socket uses to resolve addresses
  • SocketType defines the type of Socket to open
  • ProtocolType : Informs the Windows Sockets API of the requested protocol

(2)Bind()

  • Bind a local IP and port number, the parameter is an IPEndPoint object bound to the IP and port number
ServerSocket.Bind(new IPEndPoint(ip,port));
或者
IPEndPoint ipEndPoint = new IPEndPoint(ip,port)
ServerSocket.Bind(ipEndPoint);

(3)Listen()

  • Let Socket listen for incoming connections, the parameter is the capacity of the specified listening queue,
ServerSocket.Listen(10);

(4)Connect()

  • Establish a connection to a remote host
ClientSocket.Connect(ipEndPoint);

(5)Accept()

  • Receive a connection and return a new Socket, Accept will interrupt the program until there is a client connection
Socket socket = ServerSocket.Accept();

(6)Send()

  • Output data to Socket
//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)Receive()

  • Read data from Socket
byte[] receive = new byte[1024];
int length = ClientSocket.Receive(receive); // length 接收字节数组长度
int length = socket.Receive(receive);

(8)Close()

  • Close the Socket and destroy the connection
socket.Close()
ClientSocket.Close()
type function
server socket Bind()
Listen()
Accept()
client socket Connect()
public socket Receive()
Send()
Close()

4. Programming implementation steps

(1) UI design

Service-Terminal:
insert image description here

client:
insert image description here

The following is the name value I set for each component. The name value set by the client and server is the same except for Button.

components Name
TextBox textBox_Addr
textBox_Port
RichTextBox richTextBox_Receive
richTextBox_Send
Button button_Accpet
button_Connect
button_Close
button_Send
ComBox comBox_Clients

(2) Server side:

Implementation steps:
Step 1: Create a Socket object for listening connections;

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

Step 2: Create an IPEndPoint object with the specified port number and server ip;

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);	

Step 3: Use the Bind() method of the socket object to bind IPEndPoint;

ServerSocket.Bind(iPEndPoint);

Step 4: Start listening with the Listen() method of the socket object;

ServerSocket.Listen(10);

Step 5: Establish a connection with the client, and use the Accept() method of the socket object to create a socket object for communicating with the client; (because accept will interrupt the program, I use threads to achieve client access)

Socket socket = socketAccept.Accept();

Step 6: Receive information from the client (I use threads to receive data)

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

Step 7: Send information to the client

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

Step 8: Close the Socket

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

The specific implementation code:
(1) Because of the use of threads, in order to prevent errors, the detection is turned off during initialization

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

(2) To establish a connection, first create a ServerSocket to set all the parameters required for the connection and monitor the connection, and then create a thread to accept the client's connection

 		/*****************************************************************/
        #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) After connecting to the client, use the thread to receive the client data, and the parameter is the Socket object to Accept

/*****************************************************************/
        #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) Send data to the client

        /*****************************************************************/
        #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) Close the server side

/*****************************************************************/
        #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) Client:

The first step: create a Socket object;

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

Step 2: Create an IPEndPoint object with the specified port number and server ip;

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

Step 3: Use the Connect() method of the socket object to send a connection request to the server with the EndPoint object established above as a parameter;

ClientSocket.Connect(iPEndPoint);

Step 4: If the connection is successful, use the Send() method of the socket object to send information to the server;

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

Step 5: Use the Receive() method of the socket object to receive the server information;

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

Step 6: Close the socket after the communication is over;

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

Specific code implementation
(1) Using threads, in order to prevent errors, turn off detection during initialization

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

(2) Establish a connection with the server

/*****************************************************************/
        #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) If the connection is successful, create a thread to receive data from the server

/*****************************************************************/
        #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) Send data to the server (event binding on the send button)

/*****************************************************************/
        #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) Close the client

		/*****************************************************************/
        #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
        /*****************************************************************/

Five, the complete code of the server side

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. Complete client code

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
        /*****************************************************************/
    }
}

Guess you like

Origin blog.csdn.net/weixin_49959955/article/details/123798500