国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)

這篇具有很好參考價(jià)值的文章主要介紹了.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

在學(xué)習(xí)C#和MySQL實(shí)現(xiàn)注冊(cè)登錄和TCP協(xié)議的Socket通信后,本文將介紹如何利用Socket類中的異步通信函數(shù)來實(shí)現(xiàn)本地聊天室功能,

引言

Socket通信限制了客戶端與客戶端之間的通信,客戶端只能接收來自服務(wù)器的消息而不能接收到客戶端發(fā)送的消息,因此服務(wù)器最佳的選擇是起到一個(gè)中轉(zhuǎn)的作用,A客戶端發(fā)送消息給服務(wù)器,服務(wù)器將接收到的A客戶端發(fā)來的消息發(fā)送給B客戶端,當(dāng)然也可以是某部分客戶端或者全部客戶端,所有的客戶端都要和這個(gè)服務(wù)器建立連接,服務(wù)器和客戶端都只有兩個(gè)功能——發(fā)送接收消息。

數(shù)據(jù)庫設(shè)計(jì)

具體操作步驟可參考.NET編程——利用C#和MySQL實(shí)現(xiàn)注冊(cè)登錄(WinForm)中的數(shù)據(jù)庫設(shè)計(jì)部分,這一部分的主要目的在于創(chuàng)建一個(gè)本地?cái)?shù)據(jù)庫用于寫入和讀取賬號(hào)、密碼與在線狀態(tài)。
.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)

登錄注冊(cè)

窗體設(shè)計(jì)

可輸入賬號(hào)密碼,點(diǎn)擊按鍵即可注冊(cè)、登錄。
.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)

控件選擇

根據(jù)窗體設(shè)計(jì)選擇相應(yīng)的控件,包含按鍵、文本框以及標(biāo)簽等。
.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)
其中TableLayoutPanel控件用于調(diào)整窗體控件的布局,具體可看C#控件自適應(yīng)布局。

程序設(shè)計(jì)

step1-連接數(shù)據(jù)庫

static string connStr = @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=D:\Code\WeTalk\WeTalk\WeTalkDataBase.mdf;Integrated Security=True";  //連接數(shù)據(jù)庫標(biāo)識(shí)
using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
{
    conn.Open();  //打開數(shù)據(jù)庫連接
    /* 執(zhí)行內(nèi)容 */
    conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
}

step2-注冊(cè)

/* 點(diǎn)擊注冊(cè)按鍵時(shí)觸發(fā) */
private void btnSign_Click(object sender, EventArgs e)
{
    /* 獲取控件里的值 */
    string account = txtAccount.Text;
    string password = txtPassword.Text;
    if (account == "" || password == "")
    {
        MessageBox.Show("請(qǐng)輸入完整");
        clearText();  //清空文本
        return;
    }
    try
    {
        string sqlSelect = string.Format("select count(*) from [User] where Account='{0}'", account);  //SQL語句,選擇表User中Account為account的行
        /// 創(chuàng)建對(duì)象時(shí)使用using可以在使用完該對(duì)象后,自動(dòng)釋放資源
        using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
        {
            using (SqlCommand cmdSelect = new SqlCommand(sqlSelect, conn))  //創(chuàng)建數(shù)據(jù)庫命令類
            {
                conn.Open();  //打開數(shù)據(jù)庫連接
                if ((int)cmdSelect.ExecuteScalar() > 0)
                {
                    conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                    MessageBox.Show("用戶已存在!");
                    clearText();  //清空文本
                }
                else
                {
                    if (account == "admin" && password != "admin")
                    {
                        conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                        MessageBox.Show("注冊(cè)失敗");
                        clearText();  //清空文本
                        return;
                    }
                    string sqlInsert = string.Format("insert into [User] (Account,Password,IsOnline)values('{0}','{1}',{2})",account, password, 0);,
                    //SQL語句,將參數(shù)Name、Password、IsOnline插入到表User中
                    SqlCommand cmdInsert = new SqlCommand(sqlInsert, conn);
                    cmdInsert.ExecuteNonQuery();  //執(zhí)行SQL語句
                    conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                    MessageBox.Show("注冊(cè)成功");
                    clearText();  //清空文本
                }
            }
        }
    }
    catch (Exception ex)  //異常捕獲
    {
        MessageBox.Show(ex.ToString());  //顯示異常信息
    }
}

step3-登錄

/* 點(diǎn)擊登錄按鍵時(shí)觸發(fā) */
private void btnLogin_Click(object sender, EventArgs e)
{
    /* 獲取控件里的值 */
    string account = txtAccount.Text;
    string password = txtPassword.Text;
    if (account == "" || password == "")
    {
        MessageBox.Show("請(qǐng)輸入完整!");
        clearText();  //清空文本
        return;
    }
    string sqlSelect = string.Format("select Password,IsOnline from [User] where Account='{0}'", account);  //SQL語句,選擇PassWord和IsOnline
    /* 創(chuàng)建對(duì)象時(shí)使用using可以在使用完該對(duì)象后,自動(dòng)釋放資源 */
    using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
    {
        using (SqlCommand cmdSelect = new SqlCommand(sqlSelect, conn))  //創(chuàng)建數(shù)據(jù)庫命令類
        {
            conn.Open();  //打開數(shù)據(jù)庫連接
            SqlDataReader sqlRead = cmdSelect.ExecuteReader(); //遍歷數(shù)據(jù)庫
            //cmdSelect.ExecuteScalar();  //執(zhí)行SQL語句
            if (!sqlRead.Read())
            {
                conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                MessageBox.Show("賬號(hào)不存在!請(qǐng)重新輸入");
                clearText();  //清空文本
            }
            else if (sqlRead["IsOnline"].ToString().Trim() == "1" && account != "admin")
            {
                conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                MessageBox.Show("賬號(hào)已登錄!請(qǐng)重新輸入");
                clearText();  //清空文本
            }
            else if (sqlRead["Password"].ToString().Trim() == password || (account == "admin" && password == "admin"))
            {
                conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                MessageBox.Show("登錄成功!");
                string sqlUpdate = string.Format("update [User] set IsOnline={0} where Account='{1}'", 1, account);  //SQL語句,更新IsOnline為上線狀態(tài)
                using (SqlCommand cmdUpdate = new SqlCommand(sqlUpdate, conn))  //創(chuàng)建數(shù)據(jù)庫命令類
                {
                    conn.Open();  //打開數(shù)據(jù)庫連接
                    //執(zhí)行非查詢命令時(shí)使用ExecuteNonQuery,會(huì)返回影響的行數(shù)
                    cmdUpdate.ExecuteNonQuery();  //執(zhí)行SQL語句
                    conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                    clearText();  //清空文本
                    FormTalk formTalk = new FormTalk(account, this);  //跨窗體傳入?yún)?shù)
                    this.Hide();  //隱藏當(dāng)前窗體
                    formTalk.ShowDialog();  //顯示聊天窗體
                }
            }
            else
            {
                conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                MessageBox.Show("密碼錯(cuò)誤!請(qǐng)重新輸入");
                clearText();
            }
        }
    }
}

源碼

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

namespace Client
{
    public partial class FormLogin : Form
    {
        // 連接數(shù)據(jù)庫標(biāo)示
        const string connStr = @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=D:\Code\WeTalk\WeTalk\WeTalkDataBase.mdf;Integrated Security=True";  //連接數(shù)據(jù)庫標(biāo)識(shí)
        public FormLogin()
        {
            InitializeComponent();
        }
        /* 打開窗體時(shí)觸發(fā) */
        private void FormLogin_Load(object sender, EventArgs e)
        {
            clearText();
            //clearDataBase();
        }
        /* 清空數(shù)據(jù)庫函數(shù) */
        private void clearDataBase()
        {
            string sqlDelete = "delete from [User]";  //SQL語句,清空表User內(nèi)容
            using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
            {
                using (SqlCommand cmdDelete = new SqlCommand(sqlDelete, conn)) //創(chuàng)建數(shù)據(jù)庫命令類
                {
                    conn.Open();  //打開數(shù)據(jù)庫連接
                    //執(zhí)行非查詢命令時(shí)使用ExecuteNonQuery,會(huì)返回影響的行數(shù)
                    cmdDelete.ExecuteNonQuery();  //執(zhí)行SQL語句
                    conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                    MessageBox.Show("管理員重置了所有賬號(hào)");
                }
            }
        }
        /* 清空輸入框內(nèi)容函數(shù) */
        private void clearText()
        {
            /* Action委托修改控件內(nèi)容 */
            this.Invoke(new Action(() =>
            {
                txtAccount.Text = "";
                txtPassword.Text = "";
            }));
        }
        /* 點(diǎn)擊注冊(cè)按鍵時(shí)觸發(fā) */
        private void btnSign_Click(object sender, EventArgs e)
        {
            /* 獲取控件里的值 */
            string account = txtAccount.Text;
            string password = txtPassword.Text;
            if (account == "" || password == "")
            {
                MessageBox.Show("請(qǐng)輸入完整");
                clearText();  //清空文本
                return;
            }
            try
            {
                string sqlSelect = string.Format("select count(*) from [User] where Account='{0}'", account);  //SQL語句,選擇表User中Account為account的行
                /// 創(chuàng)建對(duì)象時(shí)使用using可以在使用完該對(duì)象后,自動(dòng)釋放資源
                using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
                {
                    using (SqlCommand cmdSelect = new SqlCommand(sqlSelect, conn))  //創(chuàng)建數(shù)據(jù)庫命令類
                    {
                        conn.Open();  //打開數(shù)據(jù)庫連接
                        if ((int)cmdSelect.ExecuteScalar() > 0)
                        {
                            conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                            MessageBox.Show("用戶已存在!");
                            clearText();  //清空文本
                        }
                        else
                        {
                            if (account == "admin" && password != "admin")
                            {
                                conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                                MessageBox.Show("注冊(cè)失敗");
                                clearText();  //清空文本
                                return;
                            }
                            string sqlInsert = string.Format("insert into [User] (Account,Password,IsOnline)values('{0}','{1}',{2})",account, password, 0);
                            //SQL語句,將參數(shù)Name、Password、IsOnline插入到表User中
                            SqlCommand cmdInsert = new SqlCommand(sqlInsert, conn);
                            cmdInsert.ExecuteNonQuery();  //執(zhí)行SQL語句
                            conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                            MessageBox.Show("注冊(cè)成功");
                            clearText();  //清空文本
                        }
                    }
                }
            }
            catch (Exception ex)  //異常捕獲
            {
                MessageBox.Show(ex.ToString());  //顯示異常信息
            }
        }
        /* 點(diǎn)擊登錄按鍵時(shí)觸發(fā) */
        private void btnLogin_Click(object sender, EventArgs e)
        {
            /* 獲取控件里的值 */
            string account = txtAccount.Text;
            string password = txtPassword.Text;
            if (account == "" || password == "")
            {
                MessageBox.Show("請(qǐng)輸入完整!");
                clearText();  //清空文本
                return;
            }
            string sqlSelect = string.Format("select Password,IsOnline from [User] where Account='{0}'", account);  //SQL語句,選擇PassWord和IsOnline
            /* 創(chuàng)建對(duì)象時(shí)使用using可以在使用完該對(duì)象后,自動(dòng)釋放資源 */
            using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
            {
                using (SqlCommand cmdSelect = new SqlCommand(sqlSelect, conn))  //創(chuàng)建數(shù)據(jù)庫命令類
                {
                    conn.Open();  //打開數(shù)據(jù)庫連接
                    SqlDataReader sqlRead = cmdSelect.ExecuteReader(); //遍歷數(shù)據(jù)庫
                    //cmdSelect.ExecuteScalar();  //執(zhí)行SQL語句
                    if (!sqlRead.Read())
                    {
                        conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                        MessageBox.Show("賬號(hào)不存在!請(qǐng)重新輸入");
                        clearText();  //清空文本
                    }
                    else if (sqlRead["IsOnline"].ToString().Trim() == "1" && account != "admin")
                    {
                        conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                        MessageBox.Show("賬號(hào)已登錄!請(qǐng)重新輸入");
                        clearText();  //清空文本
                    }
                    else if (sqlRead["Password"].ToString().Trim() == password || (account == "admin" && password == "admin"))
                    {
                        conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                        MessageBox.Show("登錄成功!");
                        string sqlUpdate = string.Format("update [User] set IsOnline={0} where Account='{1}'", 1, account);  //SQL語句,更新IsOnline為上線狀態(tài)
                        using (SqlCommand cmdUpdate = new SqlCommand(sqlUpdate, conn))  //創(chuàng)建數(shù)據(jù)庫命令類
                        {
                            conn.Open();  //打開數(shù)據(jù)庫連接
                            //執(zhí)行非查詢命令時(shí)使用ExecuteNonQuery,會(huì)返回影響的行數(shù)
                            cmdUpdate.ExecuteNonQuery();  //執(zhí)行SQL語句
                            conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                            clearText();  //清空文本
                            FormTalk formTalk = new FormTalk(account, this);  //跨窗體傳入?yún)?shù)
                            this.Hide();  //隱藏當(dāng)前窗體
                            formTalk.ShowDialog();  //顯示聊天窗體
                        }
                    }
                    else
                    {
                        conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                        MessageBox.Show("密碼錯(cuò)誤!請(qǐng)重新輸入");
                        clearText();
                    }
                }
            }
        }
    }
}

創(chuàng)建聊天室(服務(wù)器)

服務(wù)器起中轉(zhuǎn)作用,無需輸入發(fā)送的消息,將接受到的消息原封不動(dòng)的返回給所有客戶端即可。

窗體設(shè)計(jì)

顯示服務(wù)器的IP與端口號(hào)。
.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)

控件選擇

根據(jù)窗體設(shè)計(jì)選擇文本框控件。
.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)
其中TableLayoutPanel控件用于調(diào)整窗體控件的布局,具體可看C#控件自適應(yīng)布局。

程序設(shè)計(jì)

step1-定義變(常)量

#region 變(常)量定義
/*  buffer:存放接受消息緩存的數(shù)組
 *  listSocketWorker:存放套接字的列表
 *  MAXVALUE:服務(wù)器最大連接數(shù)
 *  PORT:服務(wù)器的網(wǎng)絡(luò)終結(jié)點(diǎn),即IP:端口號(hào)
 *  socketWatch:監(jiān)聽客戶端連接的服務(wù)器套接字
 */
byte[] buffer = new byte[1024 * 1024];  //創(chuàng)建接受消息緩存數(shù)組并約定緩存長度解決粘包問題
private List<Socket> listSocketWorker = new List<Socket>();  //創(chuàng)建列表存儲(chǔ)套接字
const int MAXVALUE = 10;  //設(shè)定最大連接數(shù)
const int PORT = 8888;  //設(shè)定端口號(hào)
private Socket socketServer = null;  //創(chuàng)建全局變量服務(wù)器套接字
#endregion

step2-創(chuàng)建服務(wù)器并監(jiān)聽客戶端連接

創(chuàng)建了一個(gè)btnOpen_Click()委托函數(shù),后續(xù)可以直接或者利用委托觸發(fā)創(chuàng)建服務(wù)器。

/* 窗體打開時(shí)觸發(fā) */
private void FormOpen_Load(object sender, EventArgs e)
{
    btnOpen_Click(this, null);  //觸發(fā)打開服務(wù)器按鍵
}
/* 點(diǎn)擊打開按鍵時(shí)觸發(fā) */
private void btnOpen_Click(object sender, EventArgs e)
{
    socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //實(shí)例化TCP通信協(xié)議Socket
    /* 監(jiān)聽客戶端 */
    IPAddress _ip = IPAddress.Any;  //監(jiān)聽所有活動(dòng)網(wǎng)卡
    IPEndPoint _port = new IPEndPoint(_ip, PORT);  //創(chuàng)建監(jiān)聽對(duì)象
    try
    {
        socketServer.Bind(_port);  //開始監(jiān)聽
        socketServer.Listen(10);  //服務(wù)器最大連接數(shù)為10
        socketServer.BeginAccept(new AsyncCallback(AcceptCallback), socketServer);  //異步接受連接
        ///參數(shù)1:回調(diào)函數(shù),參數(shù)2:傳入回調(diào)函數(shù)的參數(shù)
    }
    catch  //異常捕獲
    {
        MessageBox.Show("服務(wù)器已上線");
    }
}

step3-接受連接回調(diào)

利用Socket類中的BeginAccept()函數(shù)開始接受客戶端的連接,此時(shí)會(huì)自動(dòng)生成一個(gè)線程去接受客戶端的連接,接受到客戶端的連接后返回請(qǐng)求的對(duì)象給回調(diào)函數(shù)。
.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)

/* 接受連接異步回調(diào)處理 */
private void AcceptCallback(IAsyncResult ia)
{
    socketServer = ia.AsyncState as Socket;  //將傳入回調(diào)函數(shù)的參數(shù)實(shí)例化為Socket類
    Socket socketWorker = socketServer.EndAccept(ia);  //連接成功并返回一個(gè)套接字用于通信
    if (socketWorker != null)
    {
        /* Action委托修改lblState控件內(nèi)容 */
        this.Invoke(new Action(() =>
        {
            lblState.Text = socketWorker.LocalEndPoint.ToString();
        }));
    }
    Receive(socketWorker);
    socketServer.BeginAccept(new AsyncCallback(AcceptCallback), socketServer);  //再次異步接受連接
    /// 利用BeginAccept異步接受連接時(shí)會(huì)自動(dòng)創(chuàng)建線程并自動(dòng)結(jié)束線程
}

step4-接受消息

為了保證BeginReceive()函數(shù)在接受客戶端的消息時(shí)再次接受到消息時(shí)接受消息緩存錯(cuò)誤,利用線程鎖保證消息被完整轉(zhuǎn)發(fā)出去。

private void Receive(Socket socketWorker)
{
    if(!listSocketWorker.Contains(socketWorker))
        listSocketWorker.Add(socketWorker);  //將套接字保存到列表中
    socketWorker.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socketWorker);  //異步接受消息
    /// 參數(shù)1:接受消息緩存,參數(shù)2:接受消息緩存偏倚,參數(shù)3:接受消息緩存長度,參數(shù)4:套接字行為,參數(shù)5:回調(diào)函數(shù),參數(shù)6:傳入回調(diào)函數(shù)的參數(shù)
}
/* 接受消息異步回調(diào)處理 */
private void ReceiveCallback(IAsyncResult ia)
{
    Socket socketWorker = ia.AsyncState as Socket;  //將傳入回調(diào)函數(shù)的參數(shù)實(shí)例化為Socket類
    try
    {
        int bytesRead = socketWorker.EndReceive(ia);  //接受消息成功并返回消息長度
        if (bytesRead > 0)  //接受到非空消息
        {
            string context = Encoding.Default.GetString(buffer, 0, bytesRead);  //緩存解碼為字符串
            socketWorker.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socketWorker);  //再次異步接受消息
            /// 利用BeginReceive異步接受消息時(shí)會(huì)自動(dòng)創(chuàng)建線程并自動(dòng)結(jié)束線程
            if (!String.IsNullOrEmpty(context))
            {
                /* 線程鎖——完成代碼塊后執(zhí)行其他線程 */
                lock (socketWorker)
                {
                    foreach (var item in listSocketWorker)  // 接受消息后消息轉(zhuǎn)發(fā)給所有客戶端
                        item.Send(buffer, 0, bytesRead, SocketFlags.None);
                }
            }
        }
    }
    catch  //異常捕獲
    {
        MessageBox.Show("客戶端已下線");
        listSocketWorker.Remove(socketWorker);
    }
}

源碼

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 Server
{
    public partial class FormOpen : Form
    {
        #region 變(常)量定義
        /*  buffer:存放接受消息緩存的數(shù)組
         *  listSocketWorker:存放套接字的列表
         *  MAXVALUE:服務(wù)器最大連接數(shù)
         *  PORT:服務(wù)器的網(wǎng)絡(luò)終結(jié)點(diǎn),即IP:端口號(hào)
         *  socketWatch:監(jiān)聽客戶端連接的服務(wù)器套接字
         */
        byte[] buffer = new byte[1024 * 1024];  //創(chuàng)建接受消息緩存數(shù)組并約定緩存長度解決粘包問題
        private List<Socket> listSocketWorker = new List<Socket>();  //創(chuàng)建列表存儲(chǔ)套接字
        const int MAXVALUE = 10;  //設(shè)定最大連接數(shù)
        const int PORT = 8888;  //設(shè)定端口號(hào)
        private Socket socketServer = null;  //創(chuàng)建全局變量服務(wù)器套接字
        #endregion
        public FormOpen()
        {
            InitializeComponent();
        }
        /* 窗體打開時(shí)觸發(fā) */
        private void FormOpen_Load(object sender, EventArgs e)
        {
            btnOpen_Click(this, null);  //觸發(fā)打開服務(wù)器按鍵
        }
        /* 點(diǎn)擊打開按鍵時(shí)觸發(fā) */
        private void btnOpen_Click(object sender, EventArgs e)
        {
            socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //實(shí)例化TCP通信協(xié)議Socket
            /* 監(jiān)聽客戶端 */
            IPAddress _ip = IPAddress.Any;  //監(jiān)聽所有活動(dòng)網(wǎng)卡
            IPEndPoint _port = new IPEndPoint(_ip, PORT);  //創(chuàng)建監(jiān)聽對(duì)象
            try
            {
                socketServer.Bind(_port);  //開始監(jiān)聽
                socketServer.Listen(10);  //服務(wù)器最大連接數(shù)為10
                socketServer.BeginAccept(new AsyncCallback(AcceptCallback), socketServer);  //異步接受連接
                ///參數(shù)1:回調(diào)函數(shù),參數(shù)2:傳入回調(diào)函數(shù)的參數(shù)
            }
            catch  //異常捕獲
            {
                MessageBox.Show("服務(wù)器已上線");
            }
        }
        /* 接受連接異步回調(diào)處理 */
        private void AcceptCallback(IAsyncResult ia)
        {
            socketServer = ia.AsyncState as Socket;  //將傳入回調(diào)函數(shù)的參數(shù)實(shí)例化為Socket類
            Socket socketWorker = socketServer.EndAccept(ia);  //連接成功并返回一個(gè)套接字用于通信
            if (socketWorker != null)
            {
                /* Action委托修改lblState控件內(nèi)容 */
                this.Invoke(new Action(() =>
                {
                    lblState.Text = socketWorker.LocalEndPoint.ToString();
                }));
            }
            Receive(socketWorker);
            socketServer.BeginAccept(new AsyncCallback(AcceptCallback), socketServer);  //再次異步接受連接
            /// 利用BeginAccept異步接受連接時(shí)會(huì)自動(dòng)創(chuàng)建線程并自動(dòng)結(jié)束線程
        }
        private void Receive(Socket socketWorker)
        {
            if(!listSocketWorker.Contains(socketWorker))
                listSocketWorker.Add(socketWorker);  //將套接字保存到列表中
            socketWorker.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socketWorker);  //異步接受消息
            /// 參數(shù)1:接受消息緩存,參數(shù)2:接受消息緩存偏倚,參數(shù)3:接受消息緩存長度,參數(shù)4:套接字行為,參數(shù)5:回調(diào)函數(shù),參數(shù)6:傳入回調(diào)函數(shù)的參數(shù)
        }
        /* 接受消息異步回調(diào)處理 */
        private void ReceiveCallback(IAsyncResult ia)
        {
            Socket socketWorker = ia.AsyncState as Socket;  //將傳入回調(diào)函數(shù)的參數(shù)實(shí)例化為Socket類
            try
            {
                int bytesRead = socketWorker.EndReceive(ia);  //接受消息成功并返回消息長度
                if (bytesRead > 0)  //接受到非空消息
                {
                    string context = Encoding.Default.GetString(buffer, 0, bytesRead);  //緩存解碼為字符串
                    socketWorker.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socketWorker);  //再次異步接受消息
                    /// 利用BeginReceive異步接受消息時(shí)會(huì)自動(dòng)創(chuàng)建線程并自動(dòng)結(jié)束線程
                    if (!String.IsNullOrEmpty(context))
                    {
                        /* 線程鎖——完成代碼塊后執(zhí)行其他線程 */
                        lock (socketWorker)
                        {
                            foreach (var item in listSocketWorker)  // 接受消息后消息轉(zhuǎn)發(fā)給所有客戶端
                                item.Send(buffer, 0, bytesRead, SocketFlags.None);
                        }
                    }
                }
            }
            catch  //異常捕獲
            {
                MessageBox.Show("客戶端已下線");
                listSocketWorker.Remove(socketWorker);
            }
        }
        private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {

        }
    }
}

創(chuàng)建用戶(客戶端)

既然是聊天室,當(dāng)然能顯示在線的用戶。同時(shí),在退出聊天室時(shí)可保存聊天記錄。當(dāng)聊天區(qū)內(nèi)容過多時(shí)清空聊天區(qū)內(nèi)。最后,管理員可以重置聊天室用戶的賬號(hào)與密碼。

窗體設(shè)計(jì)

可顯示在線用戶和聊天內(nèi)容,點(diǎn)擊按鍵即可發(fā)送消息,重置賬號(hào)與密碼。.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)

控件選擇

根據(jù)窗體設(shè)計(jì)選擇相應(yīng)的控件,包含按鍵、文本框、標(biāo)簽以及菜單欄等。
.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)

其中,菜單欄的設(shè)置在聊天區(qū)內(nèi),故此在txtTalk控件屬性中的ContextMenuStrip需選擇設(shè)計(jì)好的菜單欄,如下圖所示:
.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)
.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)

程序設(shè)計(jì)

step1-定義變(常)量

定義_ip時(shí)需要根據(jù)活躍網(wǎng)卡的IP來設(shè)置,端口號(hào)與服務(wù)器的端口號(hào)保持一致即可。

#region 變(常)量定義
/*  _ip:服務(wù)器IP
 *  _path:存放聊天記錄的路徑
 *  buffer:存放接受消息緩存的數(shù)組
 *  connStr:連接數(shù)據(jù)庫標(biāo)示
 *  lastForm:存放上級(jí)窗體
 *  PORT:服務(wù)器的網(wǎng)絡(luò)終結(jié)點(diǎn),即IP:端口號(hào)
 *  socketClient:與服務(wù)器通信的客戶端套接字
 *  strAccount:用戶名字符串
 *  thUpdateUser:用于更新在線用戶的線程
 */
IPAddress _ip = IPAddress.Parse("10.102.101.212");  //IPAddress包含了一個(gè)IP地址,IPEndPoin包含了一對(duì)IP地址和端口
//IPAddress _ip = IPAddress.Parse("192.168.250.66");  //IPAddress包含了一個(gè)IP地址,IPEndPoin包含了一對(duì)IP地址和端口
string _path = System.AppDomain.CurrentDomain.BaseDirectory + @"聊天記錄\";  //設(shè)置聊天記錄保存路徑
byte[] buffer = new byte[5 * 1024 * 1024];  //創(chuàng)建接受消息緩存數(shù)組并約定緩存長度解決粘包問題
const string connStr = @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=D:\Code\WeTalk\WeTalk\WeTalkDataBase.mdf;Integrated Security=True";  //連接數(shù)據(jù)庫標(biāo)識(shí)
Form lastForm;  //上級(jí)窗體
const int PORT = 8888;  //設(shè)定端口號(hào)
Socket socketClient = null;  //創(chuàng)建全局變量客戶端套接字
public string strAccount;  //創(chuàng)建全局變量用戶名
Thread thUpdateUser;  //創(chuàng)建線程用于更新在線用戶
#endregion

step2-權(quán)限設(shè)置并重置賬戶

管理員才可重置賬戶與密碼,通過修改按鍵的Enabled屬性來設(shè)置權(quán)限。

/* 窗體打開時(shí)觸發(fā) */
private void FormTalk_Load(object sender, EventArgs e)
{
    btnClean.Enabled = false;
    /* Action委托修改控件內(nèi)容 */
    this.Invoke(new Action(() =>
    {
        lblAccount.Text = "用戶名:" + this.strAccount;  //顯示用戶名
        /* 權(quán)限設(shè)置 */
        if (strAccount == "admin")
            btnClean.Enabled = true;
        else
            btnClean.Enabled = false;
    }));
    /*
    	省略…………省略
    */
}
/* 點(diǎn)擊重置按鍵時(shí)觸發(fā) */
private void btnClear_Click(object sender, EventArgs e)
{
    /* 清空數(shù)據(jù)庫 */
    string sqlDelete = "delete from [User]";  //SQL語句,清空User內(nèi)容
    using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
    {
        using (SqlCommand cmdDelete = new SqlCommand(sqlDelete, conn)) //創(chuàng)建數(shù)據(jù)庫命令類
        {
            conn.Open();  //打開數(shù)據(jù)庫連接
            //執(zhí)行非查詢命令時(shí)使用ExecuteNonQuery,會(huì)返回影響的行數(shù)
            cmdDelete.ExecuteNonQuery();  //執(zhí)行SQL語句
            conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
            ShowMsg("管理員重置了所有賬號(hào)");
        }
    }
}

step3-更新在線用戶

創(chuàng)建一個(gè)線程去查詢SQL表中在線的用戶,用戶在線時(shí)添加至listBox控件中去,用戶不在線時(shí)從listBox控件中移除該用戶。

/* 窗體打開時(shí)觸發(fā) */
private void FormTalk_Load(object sender, EventArgs e)
{
    /*
    	省略…………省略
    */
    /* 線程更新在線用戶 */
    thUpdateUser = new Thread(UpdateUser);  //實(shí)例化線程
    thUpdateUser.IsBackground = true;
    thUpdateUser.Start();  //啟動(dòng)線程
    /*
    	省略…………省略
    */
}
/* 更新在線用戶線程函數(shù) */
void UpdateUser()
{
    while (true)  //死循環(huán)
    {
        /* 更新在線對(duì)象 */
        string sqlSelect = string.Format("select Account,IsOnline from [User]");  //SQL語句,選擇表User中Account和IsOnline
        /// 創(chuàng)建對(duì)象時(shí)使用using可以在使用完該對(duì)象后,自動(dòng)釋放資源
        using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
        {
            using (SqlCommand cmdSelect = new SqlCommand(sqlSelect, conn))  //創(chuàng)建數(shù)據(jù)庫命令類
            {
                conn.Open();  //打開數(shù)據(jù)庫連接
                SqlDataReader sqlRead = cmdSelect.ExecuteReader(); //遍歷數(shù)據(jù)庫
                while (sqlRead.Read())
                {
                    int index = lstAccount.FindString(sqlRead["Account"].ToString()); //列表中某值的下標(biāo)
                    // 列表里沒有 但 在線的
                    if (index == -1 && (int)sqlRead["IsOnline"] == 1)
                    {
                        /* Action委托修改lstAccount控件內(nèi)容 */
                        this.Invoke(new Action(() =>
                        {
                            lstAccount.Items.Add(sqlRead["Account"].ToString());
                        }));
                    }
                    // 列表里有 但 不在線的
                    else if (index != -1 && (int)sqlRead["IsOnline"] == 0)
                    {
                        /* Action委托修改lstAccount控件內(nèi)容 */
                        this.Invoke(new Action(() =>
                        {
                            lstAccount.Items.RemoveAt(index);
                        }));
                    }
                } 
                conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
            }
                }
            }
        }

step4-寫入歷史聊天記錄

/* 窗體打開時(shí)觸發(fā) */
private void FormTalk_Load(object sender, EventArgs e)
{
    /*
    	省略…………省略
    */
    try
    {
	  	/*
	   		省略…………省略
	   	*/
        /* 寫入歷史聊天記錄 */
        string log_path = _path + strAccount + "_log.txt";
        if (File.Exists(log_path))
        {
            string[] lines = File.ReadAllLines(log_path);
            foreach (string line in lines)
            {
                this.Invoke(new Action(() =>
                {
                    txtTalk.AppendText(line + "\r\n");
                }));
            }
        }
    }
    /*
    	省略…………省略
    */
}

step5-連接服務(wù)器

/* 窗體打開時(shí)觸發(fā) */
private void FormTalk_Load(object sender, EventArgs e)
{
	/*
		省略…………省略
	*/
    try
    {
        socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //實(shí)例化TCP通信協(xié)議Socket
        IPEndPoint _port = new IPEndPoint(_ip, PORT); //創(chuàng)建連接對(duì)象
        socketClient.SendTimeout = 1000;  //設(shè)置連接時(shí)限
        socketClient.BeginConnect(_port, new AsyncCallback(ConnectCallback), socketClient);  //異步請(qǐng)求連接
        /// 參數(shù)1:服務(wù)器終結(jié)點(diǎn),參數(shù)2:回調(diào)函數(shù),參數(shù)3:傳入回調(diào)函數(shù)的參數(shù)
	/*
		省略…………省略
	*/
    }
    catch { }  //異常捕獲
}
/* 發(fā)送連接異步回調(diào)處理 */
private void ConnectCallback(IAsyncResult ia)
{
    try
    {
        ((Socket)ia.AsyncState).EndConnect(ia);  //結(jié)束連接請(qǐng)求
        socketClient = ia.AsyncState as Socket;  //將傳入回調(diào)函數(shù)的參數(shù)實(shí)例化為Socket類
        byte[] sendBuffer = Encoding.Default.GetBytes(strAccount + ":上線了");  //緩存解碼為字符串
        socketClient.Send(sendBuffer);  //發(fā)送緩存至服務(wù)器
        socketClient.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socketClient);  //異步接受消息
        /// 參數(shù)1:接受消息緩存,參數(shù)2:接受消息緩存偏倚,參數(shù)3:接受消息緩存長度,參數(shù)4:套接字行為,參數(shù)5:回調(diào)函數(shù),參數(shù)6:傳入回調(diào)函數(shù)的參數(shù)
    }
    catch (Exception ex)  //異常捕獲
    {
        ShowMsg(ex.ToString());
    }
} 

step6-接受消息回調(diào)

利用Socket類中的BeginReceive()函數(shù)接受服務(wù)器發(fā)來的消息,此時(shí)會(huì)自動(dòng)生成一個(gè)線程去接受消息,接受到消息后返回請(qǐng)求的對(duì)象給回調(diào)函數(shù),利用EndReceive()函數(shù)去結(jié)束接受消息并返回接受緩存長度。同時(shí),利用線程鎖保證接收到消息被完整的解碼顯示在聊天區(qū)。

/* 接受消息異步回調(diào)處理 */
private void ReceiveCallback(IAsyncResult ia)
{
    socketClient = ia.AsyncState as Socket;  //將傳入回調(diào)函數(shù)的參數(shù)實(shí)例化為Socket類
    if (socketClient == null)
    {
        return;
    }
    try
    {
        int bytesRead = socketClient.EndReceive(ia);  //接受消息成功并返回消息長度 
        if (bytesRead > 0)  //接受到非空消息
        {
            string context = Encoding.Default.GetString(buffer, 0, bytesRead);  //緩存解碼為字符串
            socketClient.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socketClient);  //異步接受消息
            /// 利用BeginReceive異步接受消息時(shí)會(huì)自動(dòng)創(chuàng)建線程并自動(dòng)結(jié)束線程
            if (!String.IsNullOrEmpty(context))
            {
                /* 線程鎖——完成代碼塊后執(zhí)行其他線程 */
                lock (socketClient)
                {
                    string[] strContext = context.Split(':');  //解碼字符串
                    if (strContext.Length > 0 && strContext[0] != strAccount)
                        ShowMsg(context);
                    /// 服務(wù)器轉(zhuǎn)接的消息不是用戶自生發(fā)出的數(shù)據(jù)時(shí)才顯示
                }
            }
        }
    }
    catch(Exception ex)  //異常捕獲
    {
        ShowMsg(ex.ToString());
    }
}

step7-發(fā)送消息與顯示消息

/* 點(diǎn)擊發(fā)送按鍵時(shí)觸發(fā) */
private void btnSend_Click(object sender, EventArgs e)
{
    try
    {
        /* 發(fā)送消息至服務(wù)器 */
        byte[] buffer = Encoding.Default.GetBytes(strAccount + ":" + txtType.Text);  //將泛型轉(zhuǎn)換為數(shù)組
        socketClient.Send(buffer, 0, buffer.Length, SocketFlags.None);  //客戶端發(fā)送消息至服務(wù)器
        /// 參數(shù)1:接受消息緩存,參數(shù)2:接受消息緩存偏倚,參數(shù)3:接受消息緩存長度,參數(shù)4:套接字行為
        /* Action委托修改控件內(nèi)容 */
        this.Invoke(new Action(() =>
        {
            ShowMsg(strAccount + ":" + txtType.Text);
            txtType.Text = "";
        }));
    }
    catch(Exception ex)
    {
        ShowMsg(ex.ToString());
    }
}
/* 顯示消息函數(shù) */
private void ShowMsg(string str)
{
    string time = DateTime.Now.ToString("G");  //獲取當(dāng)前時(shí)間(長日期/時(shí)間)
    /* Action委托修改txtTalk控件內(nèi)容 */
    this.Invoke(new Action(() =>
    {
        txtTalk.AppendText(time + "\r\n" + str + "\r\n");
    }));
}

step8-清空聊天區(qū)

修改主線程中的控件內(nèi)容必須通過多線程或者委托的方式來實(shí)現(xiàn),本文通過EventHandler委托來觸發(fā)Action委托修改控件內(nèi)容,方法如下:

/* 點(diǎn)擊右鍵菜單項(xiàng)時(shí)觸發(fā) */
private void contextMenuStrip_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
    contextMenuStrip.Items[0].Click += new EventHandler(clearTxtTalk);     //定義EvendHandler委托
}
/* 聊天區(qū)記錄清空函數(shù) */
private void clearTxtTalk(object sender, EventArgs e)
{
	/* Action委托修改txtTalk控件內(nèi)容 */
    this.Invoke(new Action(() =>
    {
        txtTalk.Text = "";
    }));
}

step9-保存聊天記錄

/* 關(guān)閉窗體時(shí)觸發(fā) */
private void FormTalk_FormClosing(object sender, FormClosingEventArgs e)
{
    DialogResult res = MessageBox.Show("是否保存聊天記錄?", "提示", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);  //保存結(jié)果信息
    /// 參數(shù)1:顯示文本,參數(shù)2:標(biāo)題,參數(shù)3:按鍵類型,參數(shù)4:顯示圖標(biāo)
    if (res == DialogResult.Yes)
    {
        if (!System.IO.Directory.Exists(_path))
            System.IO.Directory.CreateDirectory(_path);
        /* 保存聊天記錄 */
        string log_path = _path + strAccount + "_log.txt";
        using (FileStream logFileStream = new FileStream(log_path, FileMode.Create, FileAccess.Write))  //創(chuàng)建寫入文件
        {
            using (StreamWriter logWriter = new StreamWriter(logFileStream))
            {
                logWriter.Write(txtTalk.Text);
                logWriter.Close();
            }
            logFileStream.Close();
        }
        e.Cancel = false;  //確認(rèn)關(guān)閉窗體
    }
    else if (res == DialogResult.No)  //不保存聊天記錄
    {
        e.Cancel = false;  //確認(rèn)關(guān)閉窗體
    }
    else  //取消
    {
        e.Cancel = true;  //取消關(guān)閉窗體
    }
}
/* 關(guān)閉窗體后觸發(fā) */
private void FormTalk_FormClosed(object sender, FormClosedEventArgs e)
{
    try
    {
        string sqlUpdate = string.Format("update [User] set IsOnline={0} where Account='{1}'", 0, strAccount);  //SQL語句,更新isOnline為上線狀態(tài)
        using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
        {
            using (SqlCommand cmdUpdate = new SqlCommand(sqlUpdate, conn))  //創(chuàng)建數(shù)據(jù)庫命令類
            {
                conn.Open();  //打開數(shù)據(jù)庫連接
                //執(zhí)行非查詢命令時(shí)使用ExecuteNonQuery,會(huì)返回影響的行數(shù)
                cmdUpdate.ExecuteNonQuery();  //執(zhí)行SQL語句
                conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                System.Environment.Exit(0);  //殺死進(jìn)程
            }
        }
    }
    catch { }  //異常捕獲
}

源碼

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.IO;
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 FormTalk : Form
    {
        #region 變(常)量定義
        /*  _ip:服務(wù)器IP
         *  _path:存放聊天記錄的路徑
         *  buffer:存放接受消息緩存的數(shù)組
         *  connStr:連接數(shù)據(jù)庫標(biāo)示
         *  lastForm:存放上級(jí)窗體
         *  PORT:服務(wù)器的網(wǎng)絡(luò)終結(jié)點(diǎn),即IP:端口號(hào)
         *  socketClient:與服務(wù)器通信的客戶端套接字
         *  strAccount:用戶名字符串
         *  thUpdateUser:用于更新在線用戶的線程
         */
        IPAddress _ip = IPAddress.Parse("10.102.101.212");  //IPAddress包含了一個(gè)IP地址,IPEndPoin包含了一對(duì)IP地址和端口
        //IPAddress _ip = IPAddress.Parse("192.168.250.66");  //IPAddress包含了一個(gè)IP地址,IPEndPoin包含了一對(duì)IP地址和端口
        string _path = System.AppDomain.CurrentDomain.BaseDirectory + @"聊天記錄\";  //設(shè)置聊天記錄保存路徑
        byte[] buffer = new byte[5 * 1024 * 1024];  //創(chuàng)建接受消息緩存數(shù)組并約定緩存長度解決粘包問題
        const string connStr = @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=D:\Code\WeTalk\WeTalk\WeTalkDataBase.mdf;Integrated Security=True";  //連接數(shù)據(jù)庫標(biāo)識(shí)
        Form lastForm;  //上級(jí)窗體
        const int PORT = 8888;  //設(shè)定端口號(hào)
        Socket socketClient = null;  //創(chuàng)建全局變量客戶端套接字
        public string strAccount;  //創(chuàng)建全局變量用戶名
        Thread thUpdateUser;  //創(chuàng)建線程用于更新在線用戶
        #endregion
        public FormTalk(string str,Form form)
        {
            this.strAccount = str;  //窗體間傳值
            this.lastForm = form;
            InitializeComponent();
        }
        /* 更新在線用戶線程函數(shù) */
        void UpdateUser()
        {
            while (true)  //死循環(huán)
            {
                /* 更新在線對(duì)象 */
                string sqlSelect = string.Format("select Account,IsOnline from [User]");  //SQL語句,選擇表User中Account和IsOnline
                /// 創(chuàng)建對(duì)象時(shí)使用using可以在使用完該對(duì)象后,自動(dòng)釋放資源
                using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
                {
                    using (SqlCommand cmdSelect = new SqlCommand(sqlSelect, conn))  //創(chuàng)建數(shù)據(jù)庫命令類
                    {
                        conn.Open();  //打開數(shù)據(jù)庫連接
                        SqlDataReader sqlRead = cmdSelect.ExecuteReader(); //遍歷數(shù)據(jù)庫
                        while (sqlRead.Read())
                        {
                            int index = lstAccount.FindString(sqlRead["Account"].ToString()); //列表中某值的下標(biāo)
                            // 列表里沒有 但 在線的
                            if (index == -1 && (int)sqlRead["IsOnline"] == 1)
                            {
                                /* Action委托修改lstAccount控件內(nèi)容 */
                                this.Invoke(new Action(() =>
                                {
                                    lstAccount.Items.Add(sqlRead["Account"].ToString());
                                }));
                            }
                            // 列表里有 但 不在線的
                            else if (index != -1 && (int)sqlRead["IsOnline"] == 0)
                            {
                                /* Action委托修改lstAccount控件內(nèi)容 */
                                this.Invoke(new Action(() =>
                                {
                                    lstAccount.Items.RemoveAt(index);
                                }));
                            }
                        } 
                        conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                    }
                }
            }
        }
        /* 窗體打開時(shí)觸發(fā) */
        private void FormTalk_Load(object sender, EventArgs e)
        {
            btnClean.Enabled = false;
            /* Action委托修改控件內(nèi)容 */
            this.Invoke(new Action(() =>
            {
                lblAccount.Text = "用戶名:" + this.strAccount;  //顯示用戶名
                /* 權(quán)限設(shè)置 */
                if (strAccount == "admin")
                    btnClean.Enabled = true;
                else
                    btnClean.Enabled = false;
            }));
            /* 線程更新在線用戶 */
            thUpdateUser = new Thread(UpdateUser);  //實(shí)例化線程
            thUpdateUser.IsBackground = true;
            thUpdateUser.Start();  //啟動(dòng)線程
            try
            {
                socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //實(shí)例化TCP通信協(xié)議Socket
                IPEndPoint _port = new IPEndPoint(_ip, PORT); //創(chuàng)建連接對(duì)象
                socketClient.SendTimeout = 1000;  //設(shè)置連接時(shí)限
                socketClient.BeginConnect(_port, new AsyncCallback(ConnectCallback), socketClient);  //異步請(qǐng)求連接
                /// 參數(shù)1:服務(wù)器終結(jié)點(diǎn),參數(shù)2:回調(diào)函數(shù),參數(shù)3:傳入回調(diào)函數(shù)的參數(shù)
                /* 寫入歷史聊天記錄 */
                string log_path = _path + strAccount + "_log.txt";
                if (File.Exists(log_path))
                {
                    string[] lines = File.ReadAllLines(log_path);
                    foreach (string line in lines)
                    {
                        this.Invoke(new Action(() =>
                        {
                            txtTalk.AppendText(line + "\r\n");
                        }));
                    }
                }
            }
            catch { }  //異常捕獲
        }
        /* 發(fā)送連接異步回調(diào)處理 */
        private void ConnectCallback(IAsyncResult ia)
        {
            try
            {
                ((Socket)ia.AsyncState).EndConnect(ia);  //結(jié)束連接請(qǐng)求
                socketClient = ia.AsyncState as Socket;  //將傳入回調(diào)函數(shù)的參數(shù)實(shí)例化為Socket類
                byte[] sendBuffer = Encoding.Default.GetBytes(strAccount + ":上線了");  //緩存解碼為字符串
                socketClient.Send(sendBuffer);  //發(fā)送緩存至服務(wù)器
                socketClient.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socketClient);  //異步接受消息
                /// 參數(shù)1:接受消息緩存,參數(shù)2:接受消息緩存偏倚,參數(shù)3:接受消息緩存長度,參數(shù)4:套接字行為,參數(shù)5:回調(diào)函數(shù),參數(shù)6:傳入回調(diào)函數(shù)的參數(shù)
            }
            catch (Exception ex)  //異常捕獲
            {
                ShowMsg(ex.ToString());
            }
        } 
        /* 顯示消息函數(shù) */
        private void ShowMsg(string str)
        {
            string time = DateTime.Now.ToString("G");  //獲取當(dāng)前時(shí)間(長日期/時(shí)間)
            /* Action委托修改txtTalk控件內(nèi)容 */
            this.Invoke(new Action(() =>
            {
                txtTalk.AppendText(time + "\r\n" + str + "\r\n");
            }));
        }
        /* 接受消息異步回調(diào)處理 */
        private void ReceiveCallback(IAsyncResult ia)
        {
            socketClient = ia.AsyncState as Socket;  //將傳入回調(diào)函數(shù)的參數(shù)實(shí)例化為Socket類
            if (socketClient == null)
            {
                return;
            }
            try
            {
                int bytesRead = socketClient.EndReceive(ia);  //接受消息成功并返回消息長度 
                if (bytesRead > 0)  //接受到非空消息
                {
                    string context = Encoding.Default.GetString(buffer, 0, bytesRead);  //緩存解碼為字符串
                    socketClient.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socketClient);  //異步接受消息
                    /// 利用BeginReceive異步接受消息時(shí)會(huì)自動(dòng)創(chuàng)建線程并自動(dòng)結(jié)束線程
                    if (!String.IsNullOrEmpty(context))
                    {
                        /* 線程鎖——完成代碼塊后執(zhí)行其他線程 */
                        lock (socketClient)
                        {
                            string[] strContext = context.Split(':');  //解碼字符串
                            if (strContext.Length > 0 && strContext[0] != strAccount)
                                ShowMsg(context);
                            /// 服務(wù)器轉(zhuǎn)接的消息不是用戶自生發(fā)出的數(shù)據(jù)時(shí)才顯示
                        }
                    }
                }
            }
            catch(Exception ex)  //異常捕獲
            {
                ShowMsg(ex.ToString());
            }
        }
        /* 點(diǎn)擊發(fā)送按鍵時(shí)觸發(fā) */
        private void btnSend_Click(object sender, EventArgs e)
        {
            try
            {
                /* 發(fā)送消息至服務(wù)器 */
                byte[] buffer = Encoding.Default.GetBytes(strAccount + ":" + txtType.Text);  //將泛型轉(zhuǎn)換為數(shù)組
                socketClient.Send(buffer, 0, buffer.Length, SocketFlags.None);  //客戶端發(fā)送消息至服務(wù)器
                /// 參數(shù)1:接受消息緩存,參數(shù)2:接受消息緩存偏倚,參數(shù)3:接受消息緩存長度,參數(shù)4:套接字行為
                /* Action委托修改控件內(nèi)容 */
                this.Invoke(new Action(() =>
                {
                    ShowMsg(strAccount + ":" + txtType.Text);
                    txtType.Text = "";
                }));
            }
            catch(Exception ex)
            {
                ShowMsg(ex.ToString());
            }
        }
        /* 點(diǎn)擊重置按鍵時(shí)觸發(fā) */
        private void btnClear_Click(object sender, EventArgs e)
        {
            /* 清空數(shù)據(jù)庫 */
            string sqlDelete = "delete from [User]";  //SQL語句,清空User內(nèi)容
            using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
            {
                using (SqlCommand cmdDelete = new SqlCommand(sqlDelete, conn)) //創(chuàng)建數(shù)據(jù)庫命令類
                {
                    conn.Open();  //打開數(shù)據(jù)庫連接
                    //執(zhí)行非查詢命令時(shí)使用ExecuteNonQuery,會(huì)返回影響的行數(shù)
                    cmdDelete.ExecuteNonQuery();  //執(zhí)行SQL語句
                    conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                    ShowMsg("管理員重置了所有賬號(hào)");
                }
            }
        }
        /* 關(guān)閉窗體后觸發(fā) */
        private void FormTalk_FormClosed(object sender, FormClosedEventArgs e)
        {
            try
            {
                string sqlUpdate = string.Format("update [User] set IsOnline={0} where Account='{1}'", 0, strAccount);  //SQL語句,更新isOnline為上線狀態(tài)
                using (SqlConnection conn = new SqlConnection(connStr))  //創(chuàng)建數(shù)據(jù)庫連接類
                {
                    using (SqlCommand cmdUpdate = new SqlCommand(sqlUpdate, conn))  //創(chuàng)建數(shù)據(jù)庫命令類
                    {
                        conn.Open();  //打開數(shù)據(jù)庫連接
                        //執(zhí)行非查詢命令時(shí)使用ExecuteNonQuery,會(huì)返回影響的行數(shù)
                        cmdUpdate.ExecuteNonQuery();  //執(zhí)行SQL語句
                        conn.Close();  //關(guān)閉數(shù)據(jù)庫連接
                        System.Environment.Exit(0);  //殺死進(jìn)程
                    }
                }
            }
            catch { }  //異常捕獲
        }
        /* 關(guān)閉窗體時(shí)觸發(fā) */
        private void FormTalk_FormClosing(object sender, FormClosingEventArgs e)
        {
            DialogResult res = MessageBox.Show("是否保存聊天記錄?", "提示", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);  //保存結(jié)果信息
            /// 參數(shù)1:顯示文本,參數(shù)2:標(biāo)題,參數(shù)3:按鍵類型,參數(shù)4:顯示圖標(biāo)
            if (res == DialogResult.Yes)
            {
                if (!System.IO.Directory.Exists(_path))
                    System.IO.Directory.CreateDirectory(_path);
                /* 保存聊天記錄 */
                string log_path = _path + strAccount + "_log.txt";
                using (FileStream logFileStream = new FileStream(log_path, FileMode.Create, FileAccess.Write))  //創(chuàng)建寫入文件
                {
                    using (StreamWriter logWriter = new StreamWriter(logFileStream))
                    {
                        logWriter.Write(txtTalk.Text);
                        logWriter.Close();
                    }
                    logFileStream.Close();
                }
                e.Cancel = false;  //確認(rèn)關(guān)閉窗體
            }
            else if (res == DialogResult.No)  //不保存聊天記錄
            {
                e.Cancel = false;  //確認(rèn)關(guān)閉窗體
            }
            else  //取消
            {
                e.Cancel = true;  //取消關(guān)閉窗體
            }
        }
        /* 點(diǎn)擊右鍵菜單項(xiàng)時(shí)觸發(fā) */
        private void contextMenuStrip_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            contextMenuStrip.Items[0].Click += new EventHandler(clearTxtTalk);     //定義EvendHandler委托
        }
        /* 聊天區(qū)記錄清空函數(shù) */
        private void clearTxtTalk(object sender, EventArgs e)
        {
            this.Invoke(new Action(() =>
            {
                txtTalk.Text = "";
            }));
        }
    }
}

結(jié)尾與展示

.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)
WeTalk聊天室后續(xù)可以升級(jí)成可以一對(duì)一私聊的聊天室,此時(shí)需要共同約定一個(gè)消息的格式也被稱為協(xié)議,在協(xié)議中隱含了特定的用來交互的信息,例如本文的聊天室所設(shè)計(jì)的消息格式為用戶名:消息內(nèi)容,利用:將用戶名與消息內(nèi)容分割開,升級(jí)成可一對(duì)一私聊的聊天室后可以把消息格式設(shè)計(jì)為接受者:發(fā)送者:消息,在服務(wù)器中把用戶名與套接字用字典進(jìn)行存儲(chǔ)并查詢即可實(shí)現(xiàn)能夠一對(duì)一私聊的聊天室,讀者可自行嘗試升級(jí)。文章來源地址http://www.zghlxwxcb.cn/news/detail-420260.html

到了這里,關(guān)于.NET編程——利用C#實(shí)現(xiàn)基于Socket類的聊天室(WinForm)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • C語言Socket編程TCP簡單聊天室

    C語言Socket編程TCP簡單聊天室

    這是一個(gè)使用C語言進(jìn)行套接字編程實(shí)現(xiàn)的簡單聊天室, 使用Pthread庫進(jìn)行多線程執(zhí)行 服務(wù)端: svr.c 客戶端: cli.c Makefile: Makefile 執(zhí)行編譯 啟動(dòng)服務(wù)器 啟動(dòng)客戶端 聊天 退出

    2024年02月03日
    瀏覽(27)
  • 【Linux網(wǎng)絡(luò)編程】基于UDP實(shí)現(xiàn)多人聊天室

    【Linux網(wǎng)絡(luò)編程】基于UDP實(shí)現(xiàn)多人聊天室

    UDP(User Datagram Protocol)用戶數(shù)據(jù)報(bào)協(xié)議,是不可靠的無連接的協(xié)議。在數(shù)據(jù)發(fā)送前,因?yàn)椴恍枰M(jìn)行連接,所以可以進(jìn)行高效率的數(shù)據(jù)傳輸。 數(shù)據(jù)報(bào)格式套接字 SOCK_DGRAM 采用UDP只管發(fā)送數(shù)據(jù)而不去驗(yàn)證發(fā)送數(shù)據(jù)的正確性,不論傳輸是否被接收,數(shù)據(jù)流是否有丟失,都不再重新發(fā)

    2024年02月08日
    瀏覽(27)
  • Python多人聊天室-基于socket UDP協(xié)議

    Python多人聊天室-基于socket UDP協(xié)議

    使用Python編寫的基于socket UDP通信的多功能即時(shí)聊天室,包含Tkinter編寫的圖形化聊天界面,功能包括有賬號(hào)注冊(cè)和登錄,登錄成功后可以查看在線用戶,并和聊天室內(nèi)的其他在線用戶聊天,包含私聊和群發(fā),能發(fā)送文字、表情包,以及文件等。 登錄和注冊(cè) 顯示在線用戶 群聊

    2024年02月11日
    瀏覽(20)
  • 基于Java Socket寫一個(gè)多線程的聊天室(附源碼)

    基于Java Socket寫一個(gè)多線程的聊天室(附源碼)

    Socket編程是在TCP/IP上的網(wǎng)絡(luò)編程,但是Socket在上述模型的什么位置呢。這個(gè)位置被一個(gè)天才的理論家或者是抽象的計(jì)算機(jī)大神提出并且安排出來 ? 我們可以發(fā)現(xiàn)Socket就在應(yīng)用程序的傳輸層和應(yīng)用層之間,設(shè)計(jì)了一個(gè)Socket抽象層,傳輸層的底一層的服務(wù)提供給Socket抽象層,S

    2024年02月10日
    瀏覽(30)
  • 計(jì)算機(jī)網(wǎng)絡(luò)技術(shù)與JAVA網(wǎng)絡(luò)編程手寫Socket聊天室-----JAVA入門基礎(chǔ)教程-----計(jì)算機(jī)網(wǎng)絡(luò)經(jīng)典

    import java.io.*; import java.net.Socket; import java.util.Scanner; public class ChatClient { public static void main(String[] args) { try { Socket socket = new Socket(\\\"127.0.0.1\\\",9090); new Thread(new Runnable() { @Override public void run() { InputStream inputStream = null; while(true) { try { inputStream = socket.getInputStream(); } catch (IOException e)

    2024年02月15日
    瀏覽(31)
  • Linux-Socket實(shí)現(xiàn)模擬群聊(多人聊天室)

    Linux-Socket實(shí)現(xiàn)模擬群聊(多人聊天室)

    簡單版本 服務(wù)端源碼 客戶端源碼 服務(wù)器可以在特定的端口監(jiān)聽客戶端的連接請(qǐng)求,若連接成功,服務(wù)器采用廣播的形式向當(dāng)前所有連接客戶端發(fā)送該客戶端登錄成功消息多個(gè)客戶端可以同時(shí)登錄,在源碼文件中可以配置最多群聊同時(shí)在線人數(shù)。服務(wù)端接收到客戶端發(fā)送的群

    2024年02月10日
    瀏覽(21)
  • Python web實(shí)戰(zhàn) | 使用 Flask 實(shí)現(xiàn) Web Socket 聊天室

    Python web實(shí)戰(zhàn) | 使用 Flask 實(shí)現(xiàn) Web Socket 聊天室

    ? ? 今天我們學(xué)習(xí)如何使用 Python 實(shí)現(xiàn) Web Socket,并實(shí)現(xiàn)一個(gè)實(shí)時(shí)聊天室的功能。本文的技術(shù)棧包括 Python、Flask、Socket.IO 和 HTML/CSS/JavaScript。 ? Web Socket 是一種在單個(gè) TCP 連接上進(jìn)行全雙工通信的協(xié)議。它是 HTML5 中的一部分,并且可以在瀏覽器和服務(wù)器之間創(chuàng)建實(shí)時(shí)的交互式

    2024年02月14日
    瀏覽(25)
  • VB.net:VB.net編程語言學(xué)習(xí)之ADO.net基本名稱空間與類的簡介、案例應(yīng)用(實(shí)現(xiàn)與SQL數(shù)據(jù)庫編程案例)之詳細(xì)攻略

    VB.net:VB.net編程語言學(xué)習(xí)之ADO.net基本名稱空間與類的簡介、案例應(yīng)用(實(shí)現(xiàn)與SQL數(shù)據(jù)庫編程案例)之詳細(xì)攻略 目錄 ADO.net基本名稱空間與類 1、ADO.net六個(gè)基礎(chǔ)名稱空間 2、ADO.net類

    2024年02月06日
    瀏覽(21)
  • 【你的第一個(gè)socket應(yīng)用】Vue3+Node實(shí)現(xiàn)一個(gè)WebSocket即時(shí)通訊聊天室

    【你的第一個(gè)socket應(yīng)用】Vue3+Node實(shí)現(xiàn)一個(gè)WebSocket即時(shí)通訊聊天室

    這篇文章主要是用WebSocket技術(shù)實(shí)現(xiàn)一個(gè) 即時(shí)通訊聊天室 ,首先先要了解為什么使用WebSocket而不是普通的HTTP協(xié)議,如果使用HTTP協(xié)議它是下面這種情況: 我發(fā)送一條消息,發(fā)送一個(gè)發(fā)送消息的請(qǐng)求;* 一直輪詢接收別人發(fā)送的消息,不管有沒有發(fā)送都要定時(shí)去調(diào)用接口。這里明

    2023年04月20日
    瀏覽(31)
  • 基于C# Socket實(shí)現(xiàn)的簡單的Redis客戶端

    基于C# Socket實(shí)現(xiàn)的簡單的Redis客戶端

    ???? Redis 是一款強(qiáng)大的高性能鍵值存儲(chǔ)數(shù)據(jù)庫,也是目前 NOSQL 中 最流行 比較流行的一款數(shù)據(jù)庫,它在廣泛的應(yīng)用場景中扮演著至關(guān)重要的角色,包括但不限于緩存、消息隊(duì)列、會(huì)話存儲(chǔ)等。在本文中,我們將介紹如何基于 C# Socket 來實(shí)現(xiàn)一個(gè)簡單的Redis客戶端類 RedisClien

    2024年02月05日
    瀏覽(19)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包