基于TCP异步的聊天室程序

话说这个学期我们有一门课叫“中间件”,老师叫我们做一个基于TCP的聊天程序,主要结构如图2010060218163955.jpg

1.所有Client端需要与Server端连接(感觉这句话好白痴,TCP肯定要连接了才能工作)

2.Client端的功能是可以群发和私聊(用过QQ都应该知道什么是群发和私聊吧),但都必须经过Server端中转,也就是实现了类似通讯中间件的功能。

PS:开始写之前我是对网络编程这块完全没有认识的,上网找了几个TCP的程序,都是只能实现群发功能,或者只能实现client与server之间相互发的功能,

还没有哪个是可以实现上面所说的功能的程序的(如果有的请留言给我,我去下一个下来学习一下,O(∩_∩)O谢谢)。

实现方法有好多,用Socket类可以实现,用 TcpClient类和TcpListener类也可以实现,我就选择了后者,因为比较简单。

下面就列一下我用到的技术:

多线程,异步回调,委托,设计模式的观察者模式…………

先让大家看一下客户端和服务器端的界面先吧(本人不会做界面,而且界面上有很多Label是用来检查接收的情况,请大家选择性过滤掉)

server端的2010060218590579.jpg

client端的2010060218592734.jpg

1.Server端先启动服务,新建一个线程,绑定一个套接字,之后监听

2.Client端点击连接之后,就会与Server端建立连接。

3.每当有一个Client加入Server时,Server都会通知所有的Client更新用户列表(观察者模式)

4.点私聊和用户之后,就可以私聊;点群发,就发个所有用户。

代码解说,先看看解决方案

2010060219055252.jpg 

先说一下client端

ContractedBlock.gif ExpandedBlockStart.gif 声明变量
 
   
System.Collections.ArrayList clientlist = new System.Collections.ArrayList();
private bool isExit = false ;
private delegate void SetListBoxCallBack( string str);
private SetListBoxCallBack setlistboxcallback;
private delegate void SetTextBoxReceiveCallBack( string str);
private SetTextBoxReceiveCallBack settextboxreceivecallback;
private delegate void SetComboBoxCallBack( string str);
private SetComboBoxCallBack setcomboboxcallback;
private delegate void RemoveComboBoxItemsCallBack(DataReadWrite datareadwrite);
private RemoveComboBoxItemsCallBack removecomboboxcallback;
private TcpClient client;
private NetworkStream ns;
private ManualResetEvent allDone = new ManualResetEvent( false );
ContractedBlock.gif ExpandedBlockStart.gif 构造函数
 
   
public ClientMain()
{
InitializeComponent();
setlistboxcallback
= new SetListBoxCallBack(SetListBox); // 注册listbox回调函数
settextboxreceivecallback = new SetTextBoxReceiveCallBack(SetTextBoxReceive); /// /注册textbox回调函数
setcomboboxcallback = new SetComboBoxCallBack(SetComboBox); /// /注册combobox回调函数
removecomboboxcallback = new RemoveComboBoxItemsCallBack(RemoveComboBoxItems);
}
// 注册combobox删除的回调函数
ContractedBlock.gif ExpandedBlockStart.gif 连接按钮
 
   
// 连接按钮事件
private void btn_Connect_Click( object sender, EventArgs e)
{
client
= new TcpClient(AddressFamily.InterNetwork);
IPAddress serverip
= IPAddress.Parse(txt_Server.Text);
// 采用的是异步方式
AsyncCallback receiveCallBack = new AsyncCallback(ReceiveCallBack);
allDone.Reset();

try
{
// 开始连接
client.BeginConnect(serverip, Convert.ToInt32(txt_Port.Text),receiveCallBack,client);
txt_ReceiveMsg.Invoke(settextboxreceivecallback,
string .Format( " 本机终结点:{0} " , client.Client.LocalEndPoint));
txt_ReceiveMsg.Invoke(settextboxreceivecallback,
" 开始与服务器连接 " );
allDone.WaitOne();
btn_Connect.Enabled
= false ;
}
catch
{
MessageBox.Show(
" 登录服务器失败,请确认服务器是否正常工作! " );
}
}
ContractedBlock.gif ExpandedBlockStart.gif 接收和回调
 
   
// 接收回调函数
private void ReceiveCallBack(IAsyncResult iar)
{
allDone.Set();
try
{
client
= (TcpClient)iar.AsyncState;
client.EndConnect(iar);
txt_ReceiveMsg.Invoke(settextboxreceivecallback,
string .Format( " 与服务器{0}连接成功 " , client.Client.RemoteEndPoint));
ns
= client.GetStream();
DataRead dataRead
= new DataRead(ns, client.ReceiveBufferSize);
ns.BeginRead(dataRead.msg,
0 , dataRead.msg.Length, ReadCallBack, dataRead);
}
catch
{
MessageBox.Show(
" 已经与服务器断开连接! " );
this .Close();
}

}

// 读取回调函数
private void ReadCallBack(IAsyncResult iar)
{
try
{
DataRead dataread
= (DataRead)iar.AsyncState;
int recv = dataread.ns.EndRead(iar);
lbluserlist.Text
= Encoding.Unicode.GetString(dataread.msg, 0 , recv);
string userlist1 = lbluserlist.Text;
if (userlist1.StartsWith( " # " ))
{
lb_Users.Items.Clear();
string [] abc = userlist1.Split( new char [] { ' # ' });
for ( int i = 1 ; i < abc.Length ; i ++ )
lb_Users.Items.Add(abc[i]);
}
else
txt_ReceiveMsg.Invoke(settextboxreceivecallback, Encoding.Unicode.GetString(dataread.msg,
0 , recv));

if (isExit == false )
{

dataread
= new DataRead(ns, client.ReceiveBufferSize);
ns.BeginRead(dataread.msg,
0 , dataread.msg.Length, ReadCallBack, dataread);
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
finally
{ }
}
ContractedBlock.gif ExpandedBlockStart.gif 发送功能
 
   
// 发送按钮事件
private void btn_Send_Click( object sender, EventArgs e)
{
// 如果群发按钮被选中时
if (rbteam.Checked == true )
{
// lb_Users.SelectedItems.Clear();
SendString(txt_SendMsg.Text);
}
// 如果私聊按钮被选中时
else if (rbself.Checked == true )
{
if (lb_Users.SelectedItem != null )
SendString(
" # " + lb_Users.SelectedItem.ToString().Trim() + " # " + txt_SendMsg.Text);
// label10.Text = "#" + lb_Users.SelectedItem.ToString().Trim() + "#" + txt_SendMsg.Text;
else
MessageBox.Show(
" 你还没有选取要私聊的对象 " );
}

// label9.Text = "对大家说:" + txt_SendMsg.Text;

txt_SendMsg.Clear();
}


// 发送函数
private void SendString( string str)
{
try
{
byte [] bytesdata = Encoding.Unicode.GetBytes(str + " \r\n " );
ns.BeginWrite(bytesdata,
0 , bytesdata.Length, new AsyncCallback(SendCallBack), ns);
ns.Flush();
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
finally
{ }
}

// 发送回调函数
private void SendCallBack(IAsyncResult iar)
{
try
{
ns.EndWrite(iar);
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
finally
{ }
}
ContractedBlock.gif ExpandedBlockStart.gif 回调函数的实现
 
   
// listbox回调函数
private void SetListBox( string str)
{
lb_Users.Items.Add(str);
}

// txtbox回调函数
private void SetTextBoxReceive( string str)
{
txt_ReceiveMsg.AppendText(str
+ " \r\n " );
}

在看看Server端

ContractedBlock.gif ExpandedBlockStart.gif 开始阶段
 
   
public ServerMain()
{
InitializeComponent();
setlistboxcallback
= new SetListBoxCallBack(SetLbListBox);
setlistboxcallback2
= new SetListBoxCallBack(SetListBox);
removelistboxcallback
= new RemoveListBoxCallBack(RemoveListBoxItems);
setcomboboxcallback
= new SetComboBoxCallBack(SetComboBox);
removecomboboxcallback
= new RemoveComboBoxItemsCallBack(RemoveComboBoxItems);
}



private bool isExit = false ;
System.Collections.ArrayList clientlist
= new System.Collections.ArrayList();
TcpListener listener;
private delegate void SetListBoxCallBack( string str);
private SetListBoxCallBack setlistboxcallback;
private SetListBoxCallBack setlistboxcallback2;
private delegate void RemoveListBoxCallBack(DataReadWrite datareadwrite);
private RemoveListBoxCallBack removelistboxcallback;
private ManualResetEvent allDone = new ManualResetEvent( false );
private delegate void SetComboBoxCallBack( string str);
private SetComboBoxCallBack setcomboboxcallback;
private delegate void RemoveComboBoxItemsCallBack(DataReadWrite datareadwrite);
private RemoveComboBoxItemsCallBack removecomboboxcallback;

// 开始服务按钮
private void btn_Start_Click( object sender, EventArgs e)
{
// 新建线程接受connect
Thread myThread = new Thread( new ThreadStart(AcceptConnection));
myThread.Start();
btn_Start.Enabled
= false ;
btn_End.Enabled
= true ;
}

// 线程AcceptConnection
private void AcceptConnection()
{
IPAddress[] ip
= Dns.GetHostAddresses(Dns.GetHostName());
// IPAddress ipp = IPAddress.Parse("192.168.76.103");
listener = new TcpListener(ip[ 0 ], Convert.ToInt32(txt_ServerPort.Text));
listener.Start();
while (isExit == false )
{
try
{
allDone.Reset();
AsyncCallback callback
= new AsyncCallback(AcceptTcpClientCallBack);
lst_ServerList.Invoke(setlistboxcallback,
" 开始等待连接 " );
listener.BeginAcceptTcpClient(callback, listener);
allDone.WaitOne();
}
catch (Exception e)
{
MessageBox.Show(e.Message);
break ;
}
finally
{ }
}
}

private void AcceptTcpClientCallBack(IAsyncResult iar)
{
try
{
allDone.Set();
TcpListener mylistener
= (TcpListener)iar.AsyncState;
TcpClient client
= mylistener.EndAcceptTcpClient(iar);
lst_ServerList.Invoke(setlistboxcallback,
" 已接收客户连接 " + client.Client.RemoteEndPoint);
listBox1.Invoke(setlistboxcallback2, client.Client.RemoteEndPoint.ToString());
comboBox1.Invoke(setcomboboxcallback, client.Client.RemoteEndPoint.ToString());
DataReadWrite datareadwrite
= new DataReadWrite(client);
clientlist.Add(datareadwrite);

// 发送成员列表,这里用了观察者模式
SendList();

// 与服务器连接后的不同的客户端的datareadwrite开始异步接收数据
datareadwrite.ns.BeginRead(datareadwrite.read, 0 , datareadwrite.read.Length, ReadCallBack, datareadwrite);
}
catch (Exception e)
{
lst_ServerList.Invoke(setlistboxcallback, e.Message);
// MessageBox.Show(e.Message);
return ;
}
finally
{ }
}

private void SendList()
{
string userlist = "" ;
for ( int i = 0 ; i < clientlist.Count; i ++ )
{
userlist
= userlist + " # " + ((DataReadWrite)clientlist[i]).client.Client.RemoteEndPoint.ToString();
label14.Text
= userlist;
}
foreach (DataReadWrite drw in clientlist)
SendString(drw, userlist);
}
ContractedBlock.gif ExpandedBlockStart.gif 运行阶段
 
   
// 读取消息回调函数
private void ReadCallBack(IAsyncResult iar)
{
DataReadWrite datareadwrite
= (DataReadWrite)iar.AsyncState;
try
{

int recv = datareadwrite.ns.EndRead(iar);
string aa = Encoding.Unicode.GetString(datareadwrite.read, 0 , recv);
// string bb = "";
stringaa.Text = aa;
if (isExit == false )
{
if (aa.Substring( 0 , 1 ) != " # " ) // 群发为#,没有则为单发
{
aa
= aa.Insert( 0 , datareadwrite.client.Client.RemoteEndPoint.ToString() + " 对大家说: " );
// stringbb.Text = aa.Substring(1, aa.Length-1);
foreach (DataReadWrite drw in clientlist)
SendString(drw, aa);
}
else // 私聊时截取ip地址,收到的信息为“#192.168.76.102:3315#消息”
{
int c = aa.LastIndexOf( " # " ); // 截取ip地址的位置index
string ipaddress = aa.Substring( 1 , c - 1 ); // 截取ip地址
for ( int i = 0 ; i <= listBox1.Items.Count - 1 ; i ++ )
{
if (ipaddress == listBox1.Items[i].ToString())
{
// 找到需要发送的对象
DataReadWrite obj = (DataReadWrite)clientlist[i];
// 增加发送源的IP地址和端口号
aa = aa.Insert(c + 1 , datareadwrite.client.Client.RemoteEndPoint.ToString() + " 跟你说: " );
SendString(obj, aa.Substring(c
+ 1 ));
break ;
// MessageBox.Show(aa.Substring(c, aa.Length - 2));
}
}
}
datareadwrite.ns.BeginRead(datareadwrite.read,
0 , datareadwrite.read.Length, ReadCallBack, datareadwrite);
}
}
catch (Exception e)
{
lst_ServerList.Invoke(setlistboxcallback, e.Message);
listBox1.Invoke(removelistboxcallback, datareadwrite);
comboBox1.Invoke(removecomboboxcallback, datareadwrite);
// SendList();
}
finally
{ }
}

// 发送消息
private void SendString(DataReadWrite datareadwrite, string str)
{
try
{
datareadwrite.write
= Encoding.Unicode.GetBytes(str + " \r\n " );
datareadwrite.ns.BeginWrite(datareadwrite.write,
0 , datareadwrite.write.Length, new AsyncCallback(SendCallBack), datareadwrite);
datareadwrite.ns.Flush();
lst_ServerList.Invoke(setlistboxcallback,
string .Format( " 向{0}发送:{1} " , datareadwrite.client.Client.RemoteEndPoint, str));
}
catch (Exception e)
{
lst_ServerList.Items.Add(e.Message);
// 发送失败时,清除发送不成功的IP地址
listBox1.Invoke(removelistboxcallback, datareadwrite);
comboBox1.Invoke(removecomboboxcallback, datareadwrite);
// SendList();
}
finally
{
}
}

// 发送回调
private void SendCallBack(IAsyncResult iar)
{
DataReadWrite datareadwrite
= (DataReadWrite)iar.AsyncState;
try
{
datareadwrite.ns.EndRead(iar);
}
catch (Exception e)
{
lst_ServerList.Invoke(setlistboxcallback, e.Message);
listBox1.Invoke(removelistboxcallback, datareadwrite);
comboBox1.Invoke(removecomboboxcallback, datareadwrite);
}
finally
{ }
}


// 停止服务按钮
private void btn_End_Click( object sender, EventArgs e)
{
isExit
= true ;
allDone.Set();

btn_Start.Enabled
= true ;
btn_End.Enabled
= false ;

lst_ServerList.Items.Add(
" 服务器于 " + DateTime.Now.ToString() + " 停止运行. " );
}
ContractedBlock.gif ExpandedBlockStart.gif 回调函数实现
 
   
private void RemoveListBoxItems(DataReadWrite datareadwrite)
{
int index = clientlist.IndexOf(datareadwrite);
listBox1.Items.RemoveAt(index);
}

private void SetListBox( string str)
{
listBox1.Items.Add(str);
}

private void SetLbListBox( string str)
{
lst_ServerList.Items.Add(str);
lst_ServerList.SelectedIndex
= listBox1.Items.Count - 1 ;
lst_ServerList.ClearSelected();

}

private void RemoveComboBoxItems(DataReadWrite datareadwrite)
{
int index = clientlist.IndexOf(datareadwrite);
comboBox1.Items.RemoveAt(index);
}

private void SetComboBox( object obj)
{
comboBox1.Items.Add(obj);
}

再介绍一下DataReadWrite类,这个类是实现将一个client的属性和功能集成到一个类中,主要是负责处理数据的,包括一个tcpclient对象,

一个网络流对象,2个负责读写的字节数组

ContractedBlock.gif ExpandedBlockStart.gif DataReadWrite
 
   
public class DataReadWrite
{
public TcpClient client;
public NetworkStream ns;
public byte [] read;
public byte [] write;
public DataReadWrite(TcpClient client)
{
this .client = client;
ns
= client.GetStream();
read
= new byte [client.ReceiveBufferSize];
write
= new byte [client.SendBufferSize];
}

public void InitReadArray()
{
read
= new byte [client.ReceiveBufferSize];
}

public void InitWriteArray()
{
write
= new byte [client.SendBufferSize];
}

在构造函数里得到连接上服务器的句柄,包含服务器的IP地址信息,从client对象上得到流对象,流对象就是用来在网络上进行流的读写。

以下是实现的截图

2010060219264328.jpg

尝试了群发和私聊

2010060219270236.jpg

由于是新手,所以里面用词方面可能有些不准确,而且程序这是初成品,还有很多方面的BUG。例如退出时会发生异常,当有人离开时,列表不会更新。

希望大家看了之后给点意见和建议,谢谢大家撒。有空多留言,谢谢

参考书籍:《visual c#网络编程技术与实践》和《c#网络应用高级编程》

ps:漏了源码,现在上传qqsocket.rar

转载于:https://www.cnblogs.com/cookies9/archive/2010/06/02/1750244.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值