.NET编程——利用C#实现TCP协议的异步通信Socket套接字(WinForm)

2023-11-16

本文将介绍利用基于TCP通信协议的Socket实现服务器与客户端之间的数据传输。


目录

前言

计算机通信

创建服务器

服务器通信

创建客户端

客户端通信


前言

        TCP/IP(Transmission Control Protocol/Internet Protocol)是一种传输控制协议/网间协议,TCP属于传输层、IP属于网络层,而套接字(Socket)是应用层和传输层之间的一个抽象类,基于传输层暴露的接口进行应用层开发,例如连接Connect()、监听Listen()、发送Send()等等。

        可见Socket与TCP/IP没有必然的联系,实际上Socket不仅限于支持TCP/IP还支持在HTTP、UDP等协议,只不过TCP/IP是使用最广泛的协议之一,提供了可靠的传输服务。

        因此本文选择介绍TCP通信协议的Socket实现服务器与客户端之间的数据传输。


计算机通信

        本地通信:一个进程对应一个标示PID,本地进程通信中PID唯一表示本地中的一个进程;

        网络通信:然而PID只在本地唯一,网络中的两个进程PID冲突几率很大,也就诞生了一个主机对应一个IP地址,网络进程通信中IP地址+协议+端口号唯一表示网络中的一个进程;

        只有一个进程对应一个唯一的标示,即该标示唯一表示一个进程,才能保证端与端之间能够可靠的传输数据,本文介绍的TCP通信协议属于网络通信,也就可以利用一个云端搭建服务器实现我们现实生活中几乎每天都在使用的上网聊天功能,下图是实现TCP通信协议的Socket的过程。 

        套接字(Socket):Socket = IP地址:端口号,在C#中利用Socket类中的LocalEndPoint服务器的终结点和RemoteEndPoint客户端的终结点来表示唯一的套接字对象,可通过调用成员函数来监听Listen()、发送Send()和接受Receive()等,该方法可靠性好,但由于协议复杂,通信效率不高。


创建服务器

1.程序框架 

Server窗体负责消息交互,Sign窗体负责创建服务器,UsersDataBase负责存储注册的用户信息。

2.窗体设计 

可输入端口,显示端口有效范围,点击按键即可创建服务器。

3.控件选择

根据窗口设计选择相应的控件,包含按键以及提示文字标签等。

其中fix为控制程序焦点的TextBox控件,保证启动程序时聚焦到该控件上,具体设置如下:

step1 将控制程序焦点的TextBox控件缩小到不可见,将TabIndex修改为0

step2 将输入端口的TextBox控件中的TabIndex修改为大于0

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.Tasks;
using System.Windows.Forms;

namespace Server
{
    public partial class CreatForm : Form
    {
        public CreatForm()
        {
            InitializeComponent();
        }
        /* 当打开窗体时运行 */
        private void CreatForm_Load(object sender, EventArgs e)
        {
            Control.CheckForIllegalCrossThreadCalls = false;  //取消对跨线程访问的检查
            portTextBox.ForeColor = Color.Gray;  //设置文本颜色
            portTextBox.Text = "0-65535";   //显示默认文字
        }
        /* 当点击创建服务器按键时运行 */
        private void btnCreat_Click(object sender, EventArgs e)
        {
            IPAddress ip = IPAddress.Any;  //监听所有活动网卡
            if (Convert.ToInt32(portTextBox.Text) > 0 && Convert.ToInt32(portTextBox.Text) < 65535)  //端口号有效范围为0-65535
            {
                IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(portTextBox.Text));  //创建监听对象
                ServerForm serverForm = new ServerForm(port, this);  //创建并初始化服务器窗体实例
                //参数1:监听的端口号,参数2:本窗体
                this.Hide();  //隐藏当前窗体
                serverForm.ShowDialog();  //显示服务器窗体
            }
            else
            {
                MessageBox.Show("端口号无效!");
            }
        }
        /* 聚焦到portTextBox控件时运行 */
        private void portTextBox_Enter(object sender, EventArgs e)
        {
            if (portTextBox.Text != "")
            {
                portTextBox.Text = "";	//清空默认文字
            }
            portTextBox.ForeColor = Color.Black;  //设置文本颜色
        }

    }
}

注:

1.启动时输入端口的TextBox控件中显示默认文字,发生聚焦事件时清空默认文字并设置颜色;

2.点击创建客户端按键后,只能隐藏该窗体,不能关闭该窗体,显示的窗体是子窗口,该窗口为主窗口,若关闭主窗口,所有窗口都会被关闭;


服务器通信

1.程序框架 

Server窗体负责消息交互,Sign窗体负责创建服务器,UsersDataBase负责存储注册的用户信息。

2.窗体设计 

可控制监听的启停,显示服务器的状态与监听的端口号,显示收发的消息,点击按键可发送消息。

3.控件选择

根据窗体设计选择相应的控件,包含按键、显示状态、端口号以及消息等。

4.程序设计

step1 跨窗口传入参数,传入上级窗口以及端口号

public ServerForm(IPEndPoint tempPort,Form tempForm)
        {
            /* 跨窗口传入参数 */
            port = tempPort;  //端口号 
            signForm = tempForm;  //上级窗体
            InitializeComponent();
        }

step2 更新窗体信息,创建监听套接字(socketWatch),与本机地址及端口进行绑定,创建监听线程(thListen),启动函数是Listen()传入参数为监听套接字(socketWatch)

/* 当时点击开始监听按键时运行 */
        private void btnStartListen_Click(object sender, EventArgs e)
        {  
            try
            {
                /* 更新窗口信息 */
                statusLabel.Text = "已启动";
                btnStartListen.Enabled = false;
                btnStopListen.Enabled = true;
                socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //创建TCP通信协议Socket
                /* 监听客户端 */
                if(watchFlag == 0)
                {
                    socketWatch.Bind(port);  //开始监听
                    watchFlag++;
                    socketWatch.Listen(10);  //socketWatch为被动连接,最大连接数为10
                    thListen = new Thread(Listen);  //设置thListen线程的启动函数为监听函数Listen()
                    thListen.IsBackground = true;  //设置thListen线程为后台线程
                    thListen.Start(socketWatch); //启动thListen线程并传值给Listen()
                }
            }
            catch(Exception ex)  //异常捕获
            {
                MessageBox.Show("正在监听!");
            }
        }

step3 Listen()函数中利用Accept()函数阻塞线程直至有客户端连接,创建与该客户端对应的套接字(socket),创建接受线程(thReceive),启动函数是Receive()传入参数为此次连接的套接字(socket)

/* 监听客户端连接 */
        void Listen(object obj)
        {
            Socket socket = obj as Socket;
            while (true)
            {
                try
                {
                    /* 阻塞线程直到有客户端连接,传输层完成了TCP三次握手 */
                    socket = socketWatch.Accept(); //接受到客户端Client的连接请求,返回一个负责和客户端通讯的socket
                    /// 在没有接受到连接请求前,位于Accept()下面的代码是不会被执行的,也就是线程阻塞
                    socketList.Add(socket);  //添加到套接字列表
                    thReceive = new Thread(Receive); //设置thReceive线程的启动函数为接受函数Receive()
                    thReceive.IsBackground = true;  //设置thReceive线程为后台线程
                    thReceive.Start(socket); //启动thReceive线程并传socket给Receive()
                    /// 客户端连接成功后,服务端应该收到客户端发来的消息,该消息是位传输的
                }
                catch(Exception ex)  //异常捕获
                {
                    MessageBox.Show(ex.Message);  //显示异常信息
                    break;
                }
            }
        }

<在没有收到客户端连接之前会被Accept()函数阻塞,这段代码并不是一个死循环>

step4 此时服务器(Server)与客户端(Client)已建立了连接,并用接受线程(thReceive)来接受客户端(Clinet)所发送的二进制数据流,将Stream流数据解码成字符串,并在窗体中显示消息

/* 服务器Server接收客户端Client发来的消息 */
        void Receive(object obj)
        {
            Socket socket = obj as Socket;
            while (true)
            {
                try
                {
                    byte[] buffer = new byte[1024 * 1024 * 3];  //约定缓存长度解决粘包问题
                    int r = socket.Receive(buffer);  //接受客户端Client缓存
                    if (r == 0) //接受到空消息
                    {
                        break;
                    }
                    else
                    {
                        string str = Encoding.UTF8.GetString(buffer, 0, r);  //缓存解码为字符串
                        ShowMsg(socket.RemoteEndPoint.ToString() + ":" + str);  //显示接受到的消息
                    }
                }
                catch (Exception ex)  //异常捕获
                {
                    MessageBox.Show(ex.Message);  //显示异常信息
                    break;
                }
            }
        }

<ShowMsg为自定义函数用于显示消息在窗体中> 

step5 此时服务器(Server)与客户端(Client)已建立了连接,利用socket类中的Send()函数发送消息给客户端(Client),将字符串编码成Stream流数据,并在窗体中显示消息

/* 当时点击发送按键时运行 */
        private void btnSend_Click(object sender, EventArgs e)
        {
            if (socketList.Count == 0)
            {
                MessageBox.Show("没有客户端连接!");
                sendMsgTextBox.Text = "";  //清除消息
                return;
            }
            int index = 0;
            while (index < socketList.Count)
            {
                Send(socketList[index]);
                index++;
            }

        }
        /* 服务器Server给客户端Client发送消息 */
        void Send(object obj)
        {
            try
            {
                Socket socket = obj as Socket;
                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sendMsgTextBox.Text);  //将泛型转换为数组
                socket.Send(buffer);  //发送缓存至客户端Client
                ShowMsg(socket.LocalEndPoint.ToString() + ":" + sendMsgTextBox.Text);  //显示发送的消息
                sendMsgTextBox.Text = "";  //清除消息}

            }
            catch
            {
                MessageBox.Show("客户端未连接!");
            }
        }

5.源码 

按照step1-step5完成程序设计,源码如下:

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 Server
{
    public partial class ServerForm : Form
    {
        /*  变量定义
         *  port:服务器的网络终结点,即IP:端口号
         *  socketWatch:监听客户端的套接字
         *  watchFlag:监听哨兵
         *  socketList:存放套接字的列表
         *  thListen:监听客户端线程
         *  thReceive:接受消息线程
         */
        IPEndPoint port;
        Form signForm = new Form();
        Socket socketWatch;
        int watchFlag = 0;
        List<Socket> socketList = new List<Socket>();
        Thread thListen;
        Thread thReceive;
        public ServerForm(IPEndPoint tempPort,Form tempForm)
        {
            /* 跨窗口传入参数 */
            port = tempPort;  //端口号 
            signForm = tempForm;  //上级窗口
            InitializeComponent();
        }
        /* 当打开窗体时运行 */
        private void ServerForm_Load(object sender, EventArgs e)
        {
            Control.CheckForIllegalCrossThreadCalls = false;  //取消对跨线程访问的检查
            portLabel.Text = "端口号:" + port.Port.ToString();  //显示端口号
            btnStartListen.Enabled = true;
            btnStopListen.Enabled = false;
        }
        /* 当关闭窗体时运行 */
        private void ServerForm_Closed(object sender, FormClosedEventArgs e)
        {
            System.Environment.Exit(0);
        }
        /* 当时点击开始监听按键时运行 */
        private void btnStartListen_Click(object sender, EventArgs e)
        {  
            try
            {
                /* 更新窗口信息 */
                statusLabel.Text = "已启动";
                btnStartListen.Enabled = false;
                btnStopListen.Enabled = true;
                socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //创建TCP通信协议Socket
                /* 监听客户端 */
                if(watchFlag == 0)
                {
                    socketWatch.Bind(port);  //开始监听
                    watchFlag++;
                    socketWatch.Listen(10);  //socketWatch为被动连接,最大连接数为10
                    thListen = new Thread(Listen);  //设置thListen线程的启动函数为监听函数Listen()
                    thListen.IsBackground = true;  //设置thListen线程为后台线程
                    thListen.Start(socketWatch); //启动thListen线程并传值给Listen()
                }
            }
            catch(Exception ex)  //异常捕获
            {
                MessageBox.Show("正在监听!");
            }
        }
        /* 当时点击停止监听按键时运行 */
        private void btnStopListen_Click(object sender, EventArgs e)
        {
            /* 更新窗口信息 */
            statusLabel.Text = "未启动";
            btnStartListen.Enabled = true;
            btnStopListen.Enabled = false;
            /* 关闭Socket和Thread */
            if (socketList.Count > 0)
            {
                socketWatch.Shutdown(SocketShutdown.Both);
                socketWatch.Close();
                socketWatch.Dispose();
                //socketWatch = null;
                socketList.Clear();
                watchFlag = 0;
            }
        }
        /* 监听客户端连接 */
        void Listen(object obj)
        {
            Socket socket = obj as Socket;
            while (true)
            {
                try
                {
                    /* 阻塞线程直到有客户端连接,传输层完成了TCP三次握手 */
                    socket = socketWatch.Accept(); //接受到客户端Client的连接请求,返回一个负责和客户端通讯的socket
                    /// 在没有接受到连接请求前,位于Accept()下面的代码是不会被执行的,也就是线程阻塞
                    socketList.Add(socket);  //添加到套接字列表
                    thReceive = new Thread(Receive); //设置thReceive线程的启动函数为接受函数Receive()
                    thReceive.IsBackground = true;  //设置thReceive线程为后台线程
                    thReceive.Start(socket); //启动thReceive线程并传socket给Receive()
                    /// 客户端连接成功后,服务端应该收到客户端发来的消息,该消息是位传输的
                }
                catch(Exception ex)  //异常捕获
                {
                    MessageBox.Show(ex.Message);  //显示异常信息
                    break;
                }
            }
        }
        /* 服务器Server接收客户端Client发来的消息 */
        void Receive(object obj)
        {
            Socket socket = obj as Socket;
            while (true)
            {
                try
                {
                    byte[] buffer = new byte[1024 * 1024 * 3];  //约定缓存长度解决粘包问题
                    int r = socket.Receive(buffer);  //接受客户端Client缓存
                    if (r == 0) //接受到空消息
                    {
                        break;
                    }
                    else
                    {
                        string str = Encoding.UTF8.GetString(buffer, 0, r);  //缓存解码为字符串
                        ShowMsg(socket.RemoteEndPoint.ToString() + ":" + str);  //显示接受到的消息
                    }
                }
                catch (Exception ex)  //异常捕获
                {
                    MessageBox.Show(ex.Message);  //显示异常信息
                    break;
                }
            }
        }
        /* 当时点击发送按键时运行 */
        private void btnSend_Click(object sender, EventArgs e)
        {
            if (socketList.Count == 0)
            {
                MessageBox.Show("没有客户端连接!");
                sendMsgTextBox.Text = "";  //清除消息
                return;
            }
            int index = 0;
            while (index < socketList.Count)
            {
                Send(socketList[index]);
                index++;
            }

        }
        /* 服务器Server给客户端Client发送消息 */
        void Send(object obj)
        {
            try
            {
                Socket socket = obj as Socket;
                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sendMsgTextBox.Text);  //将泛型转换为数组
                socket.Send(buffer);  //发送缓存至客户端Client
                ShowMsg(socket.LocalEndPoint.ToString() + ":" + sendMsgTextBox.Text);  //显示发送的消息
                sendMsgTextBox.Text = "";  //清除消息}

            }
            catch
            {
                MessageBox.Show("客户端未连接!");
            }
        }
        void ShowMsg(string str)
        {
            try
            {
                showMsgTextBox.AppendText(str + "\r\n");
            }
            catch (Exception ex)  //异常捕获
            {
                MessageBox.Show(ex.Message);  //显示异常信息
            }
        }


    }
}

创建客户端

1.程序框架 

Client窗体负责消息交互,Connect窗体负责连接服务器,Login窗体负责登录账户。

2.窗体设计 

可输入服务器IP与端口,点击按键即可连接服务器。

3.控件选择

根据窗体设计选择相应的控件,包含按键以及提示文字标签等。

4.程序设计

点击连接服务器按键后,打开客户端通讯窗口,并将端口号传入该窗口。

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

namespace Client
{
    public partial class ConnectForm : Form
    {
        /*  变量定义
         *  name:登录的用户名
         *  connStr:连接数据库标识
         */
        static string name;  //用户名
        static string connStr = @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=D:\Code\TCP\Server\UsersDataBase.mdf;Integrated Security=True";  //连接数据库标识
        public ConnectForm(string tempStr)
        {
            name = tempStr;
            InitializeComponent();
        }
        /* 当打开窗体时运行 */
        private void Connect_Load(object sender, EventArgs e)
        {
            Control.CheckForIllegalCrossThreadCalls = false;  //取消对跨线程访问的检查
        }
        /* 当关闭窗体时运行 */
        private void ConnectForm_Closing(object sender, FormClosingEventArgs e)
        {
            /// 更新IsOnline为下线状态
            string sqlUpdate = string.Format("update [User] set IsOnline='{0}' where Name='{1}'", 0, name);  //SQL语句,更新IsOnline为下线状态
            /* 创建对象时使用using可以在使用完该对象后,自动释放资源 */
            using (SqlConnection conn = new SqlConnection(connStr))  //创建数据库连接类
            {
                using (SqlCommand cmdUpdate = new SqlCommand(sqlUpdate, conn))  //创建数据库命令类
                {
                    conn.Open();  //打开数据库连接
                    cmdUpdate.ExecuteNonQuery();  //执行SQL语句
                    ///执行非查询命令时使用ExecuteNonQuery,会返回影响的行数
                    conn.Close();  //关闭数据库连接
                    this.Hide();  //隐藏当前窗体
                }
            }
        }
        /* 当关闭窗体后运行 */
        private void ConnectForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            System.Environment.Exit(0);
        }
        /* 当点击连接服务器后运行 */
        private void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                /* 申请socket连接指定服务器的地址和端口 */
                IPAddress ip = IPAddress.Parse(IPTextBox.Text);  //IPAddress包含了一个IP地址,IPEndPoin包含了一对IP地址和端口
                IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(portTextBox.Text));  //创建监听对象
                ClientForm clientForm = new ClientForm(port, name);  //创建并初始化服务器窗体实例
                //参数1:监听的端口号,参数2:登录的用户名
                this.Hide();  //隐藏当前窗体
                clientForm.ShowDialog();  //显示客户端窗体
            }
            catch(Exception ex)  //异常捕获
            {
                MessageBox.Show(ex.Message);  //显示异常信息
            }
        }
    }
}

客户端通信

1.程序框架 

Client窗体负责消息交互,Connect窗体负责连接服务器,Login窗体负责登录账户。

2.窗体设计 

可控制连接的通断,显示客户端的状态与连接的服务器IP与端口号、客户端端口号,显示收发的消息,点击按键可发送消息。

3.控件选择

根据窗体设计选择相应的控件,包含按键、显示状态、端口号以及消息等。

4.程序设计

step1 跨窗口传入参数,传入上级窗口以及端口号

public ClientForm(IPEndPoint tempPort,string tempStr)
        {
            /* 跨窗口传入参数 */
            port = tempPort;  //端口号
            name = tempStr;  //用户名
            InitializeComponent();
        }

step2 更新窗体信息,创建此次连接套接字(socket),根据服务器的IP与端口号连接,创建接受线程(thReceive),启动函数是Receive()传入参数为此次连接的套接字(socket)

/* 当时点击连接按键时运行 */
        private void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                /* 更新窗口信息 */
                statusLabel.Text = "已连接";
                btnConnect.Enabled = false;
                btnDisconnect.Enabled = true;
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //创建TCP通信协议Socket
                if(connFlag == 0)
                {
                    /* 连接服务器 */
                    socket.Connect(port);
                    connFlag++;
                    clientPortLabel.Text = "客户端端口:" + (socket.LocalEndPoint as IPEndPoint).Port.ToString();
                    thReceive = new Thread(Receive);  //设置thReceive线程的启动函数为接受函数Receive()
                    thReceive.IsBackground = true;  //设置thReceive线程为后台线程
                    thReceive.Start(socket);  //启动thReceive线程并传socket给Receive()
                }
            }
            catch
            {
                /* 更新窗口信息 */
                statusLabel.Text = "未连接";
                btnConnect.Enabled = true;
                btnDisconnect.Enabled = false;
                socket.Close();
                socket = null;
                MessageBox.Show("服务器未上线!无法连接");
            }
           
        }

step3 此时服务器(Server)与客户端(Client)已建立了连接,并用接受线程(thReceive)来接受服务器(Server)所发送的二进制数据流,将Stream流数据解码成字符串,并在窗体中显示消息

/* 客户端Client接收服务器Server发来的消息 */
        void Receive(object obj)
        {
            Socket socket = obj as Socket;
            while (true)
            {
                try
                {
                    //约定缓存长度解决粘包问题
                    byte[] buffer = new byte[1024 * 1024 * 5];
                    int r = socket.Receive(buffer);
                    if (r == 0) //没有发送消息
                    {
                        break;
                    }
                    else
                    {
                        string str = Encoding.UTF8.GetString(buffer, 0, r);  //缓存解码为字符串
                        ShowMsg(socket.RemoteEndPoint.ToString() + ":" + str);  //显示接受到的消息
                    }
                }
                catch  //异常捕获
                {
                    this.btnDisconnect.Click += new System.EventHandler(this.btnDisconnect_Click);  //触发断开按键事件
                    break;
                }
            }
        }

<ShowMsg为自定义函数用于显示消息在窗体中> 

step4 此时服务器(Server)与客户端(Client)已建立了连接,利用socket类中的Send()函数发送消息给服务器(Server),将字符串编码成Stream流数据,并在窗体中显示消息

/* 当时点击发送按键时运行 */
        private void btnSend_Click(object sender, EventArgs e)
        {
            if (socket == null)
            {
                MessageBox.Show("没有连接服务器!");
                sendMsgTextBox.Text = "";  //清除消息
                return;
            }
            else
            {
                Send(socket);
            }
        }
/* 客户端Client给服务器Server发送窗口消息 */
        void Send(object obj)
        {
            try
            {
                Socket socket = obj as Socket;
                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sendMsgTextBox.Text);  //将泛型转换为数组
                socket.Send(buffer);  //发送缓存至服务器Server
                ShowMsg(socket.LocalEndPoint.ToString() + ":" + sendMsgTextBox.Text);  //显示发送的消息
                sendMsgTextBox.Text = "";  //清除消息
            }
            catch  //异常捕获
            {
                MessageBox.Show("服务器未上线!");
            }
        }

5.源码 

按照step1-step4完成程序设计,源码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
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 ClientForm : Form
    {
        /*  变量定义
         *  name:登录的用户名
         *  port:服务器的网络终结点,即IP:端口号
         *  socket:此次连接的套接字
         *  connFlag:连接哨兵
         *  thReceive:接受消息线程
         *  connStr:连接数据库标识
         */
        static string name;
        static IPEndPoint port;
        Socket socket;
        int connFlag = 0;
        Thread thReceive;
        static string connStr = @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=D:\Code\TCP\Server\UsersDataBase.mdf;Integrated Security=True";  //连接数据库标识
        public ClientForm(IPEndPoint tempPort,string tempStr)
        {
            /* 跨窗口传入参数 */
            port = tempPort;  //端口号
            name = tempStr;  //用户名
            InitializeComponent();
        }
        /* 当打开窗体时运行 */
        private void ClientForm_Load(object sender, EventArgs e)
        {
            Control.CheckForIllegalCrossThreadCalls = false;  //取消对跨线程访问的检查
            serverIPLabel.Text = "服务器IP:" + port.Address.ToString();
            serverPortLabel.Text = "服务器端口:" + port.Port.ToString();
            btnConnect.Enabled = true;
            btnDisconnect.Enabled = false;
        }
        /* 当关闭窗体时运行 */
        private void ClientForm_Closing(object sender, FormClosingEventArgs e)
        {
            /// 更新IsOnline为下线状态
            string sqlUpdate = string.Format("update [User] set IsOnline='{0}' where Name='{1}'", 0, name);  //SQL语句,更新IsOnline为下线状态
            /* 创建对象时使用using可以在使用完该对象后,自动释放资源 */
            using (SqlConnection conn = new SqlConnection(connStr))  //创建数据库连接类
            {
                using (SqlCommand cmdUpdate = new SqlCommand(sqlUpdate, conn))  //创建数据库命令类
                {
                    conn.Open();  //打开连接
                    //执行非查询命令时使用ExecuteNonQuery,会返回影响的行数
                    cmdUpdate.ExecuteNonQuery();  //执行SQL语句
                    conn.Close();  //关闭数据库连接
                    this.Hide();  //隐藏当前窗体
                }
            }
        }
        /* 当关闭窗体后运行 */
        private void ClientForm_Closed(object sender, FormClosedEventArgs e)
        {
            System.Environment.Exit(0);
        }
        /* 当时点击连接按键时运行 */
        private void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                /* 更新窗口信息 */
                statusLabel.Text = "已连接";
                btnConnect.Enabled = false;
                btnDisconnect.Enabled = true;
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //创建TCP通信协议Socket
                if(connFlag == 0)
                {
                    /* 连接服务器 */
                    socket.Connect(port);
                    connFlag++;
                    clientPortLabel.Text = "客户端端口:" + (socket.LocalEndPoint as IPEndPoint).Port.ToString();
                    thReceive = new Thread(Receive);  //设置thReceive线程的启动函数为接受函数Receive()
                    thReceive.IsBackground = true;  //设置thReceive线程为后台线程
                    thReceive.Start(socket);  //启动thReceive线程并传socket给Receive()
                }
            }
            catch
            {
                /* 更新窗口信息 */
                statusLabel.Text = "未连接";
                btnConnect.Enabled = true;
                btnDisconnect.Enabled = false;
                socket.Close();
                socket = null;
                MessageBox.Show("服务器未上线!无法连接");
            }
        }
        /* 当时点击断开按键时运行 */
        private void btnDisconnect_Click(object sender, EventArgs e)
        {
            /* 更新窗口信息 */
            statusLabel.Text = "未连接";
            btnConnect.Enabled = true;
            btnDisconnect.Enabled = false;
            /* 关闭Socket和Thread */
            if (socket != null)
            {
                socket.Shutdown(SocketShutdown.Both);
                socket.Close();
                socket.Dispose();
                //socket = null;
                connFlag = 0;
            }
        }
        /* 客户端Client接收服务器Server发来的消息 */
        void Receive(object obj)
        {
            Socket socket = obj as Socket;
            while (true)
            {
                try
                {
                    //约定缓存长度解决粘包问题
                    byte[] buffer = new byte[1024 * 1024 * 5];
                    int r = socket.Receive(buffer);
                    if (r == 0) //没有发送消息
                    {
                        break;
                    }
                    else
                    {
                        string str = Encoding.UTF8.GetString(buffer, 0, r);  //缓存解码为字符串
                        ShowMsg(socket.RemoteEndPoint.ToString() + ":" + str);  //显示接受到的消息
                    }
                }
                catch  //异常捕获
                {
                    this.btnDisconnect.Click += new System.EventHandler(this.btnDisconnect_Click);  //触发断开按键事件
                    break;
                }
            }
        }
        /* 当时点击发送按键时运行 */
        private void btnSend_Click(object sender, EventArgs e)
        {
            if (socket == null)
            {
                MessageBox.Show("没有连接服务器!");
                sendMsgTextBox.Text = "";  //清除消息
                return;
            }
            else
            {
                Send(socket);
            }
        }
        /* 客户端Client给服务器Server发送窗口消息 */
        void Send(object obj)
        {
            try
            {
                Socket socket = obj as Socket;
                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sendMsgTextBox.Text);  //将泛型转换为数组
                socket.Send(buffer);  //发送缓存至服务器Server
                ShowMsg(socket.LocalEndPoint.ToString() + ":" + sendMsgTextBox.Text);  //显示发送的消息
                sendMsgTextBox.Text = "";  //清除消息
            }
            catch  //异常捕获
            {
                MessageBox.Show("服务器未上线!");
            }
        }
        void ShowMsg(string str)
        {
            try
            {
                showMsgTextBox.AppendText(str + "\r\n");
            }
            catch (Exception ex)  //异常捕获
            {
                MessageBox.Show(ex.Message);  //显示异常信息
            }
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

.NET编程——利用C#实现TCP协议的异步通信Socket套接字(WinForm) 的相关文章

  • Directory.Delete 之后 Directory.Exists 有时返回 true ?

    我有非常奇怪的行为 我有 Directory Delete tempFolder true if Directory Exists tempFolder 有时 Directory Exists 返回 true 为什么 可能是资源管理器打开了
  • 确保 StreamReader 不会挂起等待数据

    下面的代码读取从 tcp 客户端流读取的所有内容 并且在下一次迭代中它将仅位于 Read 上 我假设正在等待数据 我如何确保它不会在没有任何内容可供读取时返回 我是否必须设置低超时 并在失败时响应异常 或者有更好的办法吗 TcpClient
  • 使用 LINQ2SQL 在 ASP.NET MVC 中的各种模型存储库之间共享数据上下文

    我的应用程序中有 2 个存储库 每个存储库都有自己的数据上下文对象 最终结果是我尝试将从一个存储库检索到的对象附加到从另一个存储库检索到的对象 这会导致异常 Use 构造函数注入将 DataContext 注入每个存储库 public cl
  • 如何创建包含 IPv4 地址的文本框? [复制]

    这个问题在这里已经有答案了 如何制作一个这样的文本框 我想所有的用户都见过这个并且知道它的功能 您可以使用带有 Mask 的 MaskedTestBox000 000 000 000 欲了解更多信息 请参阅文档 http msdn micr
  • 如何区分用户点击链接和页面自动重定向?

    拥有 C WebBrowser control http msdn microsoft com en us library system windows forms webbrowser aspx在我的 WinForms 应用程序中 并意识
  • 将 Word 文档另存为图像

    我正在使用下面的代码将 Word 文档转换为图像文件 但是图片显得太大 内容不适合 有没有办法渲染图片或将图片保存到合适的尺寸 private void btnConvert Click object sender EventArgs e
  • 在 C 中初始化变量

    我知道有时如果你不初始化int 如果打印整数 您将得到一个随机数 但将所有内容初始化为零似乎有点愚蠢 我问这个问题是因为我正在评论我的 C 项目 而且我对缩进非常直接 并且它可以完全编译 90 90 谢谢 Stackoverflow 但我想
  • 在 Visual Studio 2010 中从 Fortran 调用 C++ 函数

    我想从 Fortran 调用 C 函数 为此 我在 Visual Studio 2010 中创建了一个 FORTRAN 项目 之后 我将一个 Cpp 项目添加到该 FORTRAN 项目中 当我要构建程序时出现以下错误 Error 1 unr
  • qdbusxml2cpp 未知类型

    在使用 qdbusxml2cpp 程序将以下 xml 转换为 Qt 类时 我收到此错误 qdbusxml2cpp c ObjectManager a ObjectManager ObjectManager cpp xml object ma
  • 是否有实用的理由使用“if (0 == p)”而不是“if (!p)”?

    我倾向于使用逻辑非运算符来编写 if 语句 if p some code 我周围的一些人倾向于使用显式比较 因此代码如下所示 if FOO p some code 其中 FOO 是其中之一false FALSE 0 0 0 NULL etc
  • 在一个平台上,对于所有数据类型,所有数据指针的大小是否相同? [复制]

    这个问题在这里已经有答案了 Are char int long 甚至long long 大小相同 在给定平台上 不能保证它们的大小相同 尽管在我有使用经验的平台上它们通常是相同的 C 2011 在线草稿 http www open std
  • 我可以使用 moq Mock 来模拟类而不是接口吗?

    正在经历https github com Moq moq4 wiki Quickstart https github com Moq moq4 wiki Quickstart 我看到它 Mock 一个接口 我的遗留代码中有一个没有接口的类
  • 使用自定义堆的类似 malloc 的函数

    如果我希望使用自定义预分配堆构造类似 malloc 的功能 那么 C 中最好的方法是什么 我的具体问题是 我有一个可映射 类似内存 的设备 已将其放入我的地址空间中 但我需要获得一种更灵活的方式来使用该内存来存储将随着时间的推移分配和释放的
  • C#:帮助理解 UML 类图中的 <>

    我目前正在做一个项目 我们必须从 UML 图编写代码 我了解 UML 类图的剖析 但我无法理解什么 lt
  • 内部 while 循环不工作

    这是我项目网页上的代码片段 这里我想显示用户选择的类别 然后想显示属于该类别的主题 在那里 用户可以拥有多个类别 这没有问题 我可以在第一个 while 循环中打印所有这些类别 问题是当我尝试打印主题时 结果只显示一行 但每个类别中有更多主
  • 使用管道时,如果子进程数量大于处理器数量,进程是否会被阻塞?

    当子进程数量很大时 我的程序停止运行 我不知道问题是什么 但我猜子进程在运行时以某种方式被阻止 下面是该程序的主要工作流程 void function int process num int i initial variables for
  • 使用 %d 打印 unsigned long long

    为什么我打印以下内容时得到 1 unsigned long long int largestIntegerInC 18446744073709551615LL printf largestIntegerInC d n largestInte
  • 按 Esc 按键关闭 Ajax Modal 弹出窗口

    我已经使用 Ajax 显示了一个面板弹出窗口 我要做的是当用户按 Esc 键时关闭该窗口 这可能吗 如果有人知道这一点或以前做过这一点 请帮助我 Thanks 通过以下链接 您可以通过按退出按钮轻松关闭窗口 http www codepro
  • 从列表中选择项目以求和

    我有一个包含数值的项目列表 我需要使用这些项目求和 我需要你的帮助来构建这样的算法 下面是一个用 C 编写的示例 描述了我的问题 int sum 21 List
  • 如何将 PostgreSql 与 EntityFramework 6.0.2 集成? [复制]

    这个问题在这里已经有答案了 我收到以下错误 实体框架提供程序类型的 实例 成员 Npgsql NpgsqlServices Npgsql 版本 2 0 14 2 文化 中性 PublicKeyToken 5d8b90d52f46fda7 没

随机推荐

  • Ubuntu mysql配置root用户远程登录

    查看mysql默认密码登录数据库 cat etc mysql debian cnf 查看root用户数据 use mysql select User Host authentication string from user 增加root用户
  • Android中Context详解 ---- 你所不知道的Context

    大家好 今天给大家介绍下我们在应用开发中最熟悉而陌生的朋友 Context类 说它熟悉 是应为我们在开发中 时刻的在与它打交道 例如 Service BroadcastReceiver Activity等都会利用到Context的相关方法
  • AngularJS 2调用.net core WebAPI的几个坑

    前几天 按照AngularJS2的英雄指南教程走了一遍 教程网址是http origin angular live docs ts latest tutorial 在步骤完成后 又更进一步 在英雄增删改的时候 直接调用 net core的W
  • 关于 clock_gettime() 的一个问题以及解决方法

    在新的2 6x内核上 编译使用这个函数的程序的时候 会发现 如果 gcc lpthead 无法链接成功 原因在于 libpthread so中没有这个函数的实现 但是 libpthread a中有 还有一个librt so librt a中
  • 堆和栈的通俗解释【转】

    数据结构的栈和堆 首先在数据结构上要知道堆栈 尽管我们这么称呼它 但实际上堆栈是两种数据结构 堆和栈 堆和栈都是一种数据项按序排列的数据结构 栈就像装数据的桶或箱子 我们先从大家比较熟悉的栈说起吧 它是一种具有后进先出性质的数据结构 也就是
  • 医院RFID药物跟踪管理解决方案

    1 技术背景 基于 2018年2月6日 药物调配商QuVa Pharma 与Kit Check合作 为其医院药房客户 提供基于RFID药物跟踪管理解决方案 QuVa提供的系统包括Kit Check的 无源 超高频 UHF RFID 标签 该
  • WEB基础-变形动画

    字体图标 IconFont 图标字体也叫字体图标 就是字体做的图标 可以通过设置字体的方式改变图标的样式 受到近些年 扁平化设计 的影响 越来越多的图标都开始使用 IconFont 下载字体图标 首先打开IconFont 阿里巴巴矢量图标库
  • websocket详解

    之前利用websocket以及jQuery做了一个聊天通讯应用 最近在总结整个过程中的一些问题 也借此机会聊聊websocket协议 webSocket本身不存在跨域问题 所以可以利用webSocket来进行非同源之间的通信 webSock
  • pytorch打印模型梯度

    简介 有时候在调试模型训练过程时 我们需要打印模型中参数的梯度 去查看是否存在梯度消失或者梯度爆炸的问题 可以通过在backward之后查看params的grad属性来确认 参考代码如下所示 import torch 定义模型 class
  • Python3 面向对象编程

    好记性不如烂笔头 对之前阅读书籍进行梳理与总结 此文为 Python3面向对象编程 阅读笔记 文章目录 第一章 面向对象设计 第二章 Python对象 第三章 对象相似时 第四章 异常捕获 第五章 何时使用面向对象编程 第六章 Python
  • C# 导出 Excel 方法

    第一种 使用 Microsoft Office Interop Excel dll public void ExportExcel DataTable dt if dt null Microsoft Office Interop Excel
  • 关键词生成器在线-在线免费关键词生成器

    关键词生成 什么是关键词生成 关键词生成就是根据你输入的一个关键词生成成千上百的核心关键词 围绕着你输入的核心词来生成的 优先生成大量用户搜索的关键词 今天就给大家分享一款免费关键词生成工具 关键词生成的来源主要是用户都在搜索的词 相关搜索
  • CentOS-8-x86_64-1905安装

    CentOS 8 x86 64 1905安装 安装前准备 VMware Workstation 点击下载虚拟机软件 VMware Workstation是一款功能强大的桌面虚拟计算机软件 提供用户可在单一的桌面上同时运行不同的操作系统 和进
  • Crazy Thairs【树状数组+高精度+DP思想】

    题目链接 POJ 3378 题意 有N个点 问的是要求组成一个长度为5的上升子序列的组成有多少种 最搞事情的是这道题不用取模 所以 是一定会爆long long的 首先 很容易想到一点就是我们可以开一个dp maxN 5 表示的是 dp i
  • Vagrant学习笔记:搭建K8s集群

    通常情况下 我们在使用VMware VirtualBox这一类虚拟机软件创建虚拟开发环境时 往往需要经历寻找并下载操作系统的安装镜像文件 然后根据该镜像文件启动的安装向导一步一步地安装与配置操作系统 最后还需要从零开始安装开发与运维工具 整
  • 计算机考研复试机试怎么速成,西安电子大学计算机考研复试机试(2019)+ 学长讲解(6)+ 作业...

    学长讲的就是算法笔记的入门算法 作业1 链接 https www nowcoder com questionTerminal 0f64518fea254c0187ccf0ea05019672 来源 牛客网 有一个网络日志 记录了网络中计算任
  • Java各种集合判空总结

    目录 集合判空 CollectionUtils isEmpty推荐 原始判断 isEmpty 其他 数组判空 集合判空 CollectionUtils isEmpty推荐 这个使用到了spring的工具类 需要提前引入依赖 import o
  • 机器学习2018-12-28

    机器学习 组成 主要任务 分类 classification 将实例数据划分到合适的类别中 应用实例 判断网站是否被黑客入侵 二分类 手写数字的自动识别 多分类 回归 regression 主要用于预测数值型数据 应用实例 股价价格波动的预
  • 用编程器免拆夹子刷斐讯K2 K2P解决难搞固件 刷BREED 无损原EEPROM

    文章中放的几个地址都是思路来源 感谢各路大神原帖子的思路 因为我刷机时候没有拍照 只好借用各位大佬的图来说明步骤 我做一下整理会放出本篇刷机流程 很简单 K2 22 6 532 231软件版本已经无解 UBOOT等可以软刷的方式都被堵死 编
  • .NET编程——利用C#实现TCP协议的异步通信Socket套接字(WinForm)

    本文将介绍利用基于TCP通信协议的Socket实现服务器与客户端之间的数据传输 目录 前言 计算机通信 创建服务器 服务器通信 创建客户端 客户端通信 前言 TCP IP Transmission Control Protocol Inte