在學(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)。
登錄注冊(cè)
窗體設(shè)計(jì)
可輸入賬號(hào)密碼,點(diǎn)擊按鍵即可注冊(cè)、登錄。
控件選擇
根據(jù)窗體設(shè)計(jì)選擇相應(yīng)的控件,包含按鍵、文本框以及標(biāo)簽等。
其中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)。
控件選擇
根據(jù)窗體設(shè)計(jì)選擇文本框控件。
其中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ù)。
/* 接受連接異步回調(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)與密碼。
控件選擇
根據(jù)窗體設(shè)計(jì)選擇相應(yīng)的控件,包含按鍵、文本框、標(biāo)簽以及菜單欄等。
其中,菜單欄的設(shè)置在聊天區(qū)內(nèi),故此在txtTalk控件屬性中的ContextMenuStrip需選擇設(shè)計(jì)好的菜單欄,如下圖所示:
程序設(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)容,方法如下:文章來源:http://www.zghlxwxcb.cn/news/detail-420260.html
/* 點(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é)尾與展示
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)!