分别编写服务端和客户端程序,利用基于WCF的TCP技术,实现在线聊天功能,完成在线用户列表管理,消息发送、接收的功能。
在同一个解决方案中,分别编写服务端程序和客户端程序,利用TCP实现简单的群聊功能。
具体要求如下:
(1)服务端程序选择【WCF服务库】模板,客户端程序选择【WPF应用程序】模板。
(2)客户端与服务端连接成功后,通过服务端获取已经在线的用户,并将其显示在客户端的在线用户列表中。
(3)不论哪个用户发送聊天消息,其他所有用户都能看到该消息。
(4)当某个用户退出后,在线用户列表中自动移除该用户。
重点:
(1)掌握TCP协议下WCF服务应用程序构建方法。
(2)掌握客户端和服务端通信接口设计方法。
(3)掌握协定的设计及实现方法。
(4)掌握双工通信的设计及实现方法。
(5)熟悉WCF和TCP的相关绑定设置。
难点:
(1)掌握客户端和服务端通信接口设计方法。
(2)掌握协定的设计及实现方法。
(3)掌握双工通信的设计及实现方法。
(4)熟悉WCF和TCP的相关绑定设置。
客户端
MainWindow.xaml
<Window x:Class="ChatClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="主界面" Height="350" Width="525" WindowStartupLocation="CenterScreen">
<StackPanel VerticalAlignment="Center">
<Button Name="btn1" Margin="10" Width="170" Content="同时启动2个客户端(测试用)" Click="btn1_Click"/>
<Button Name="btn2" Margin="10" Width="170" Content="只启动1个客户端(实际情况)" Click="btn2_Click"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ChatClient
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btn1_Click(object sender, RoutedEventArgs e)
{
StartWindow("西西皮", 50, 300);
StartWindow("瓜瓜糖", 600, 300);
}
private void StartWindow(string userName, int left, int top)
{
ClientWindow w = new ClientWindow();
w.Left = left;
w.Top = top;
w.UserName = userName;
w.Owner = this;
w.Closed += (sender, e) => this.Activate();
w.Show();
}
private void btn2_Click(object sender, RoutedEventArgs e)
{
ClientWindow w = new ClientWindow();
w.Owner = this;
w.Closed += (sendObj, args) => this.Activate();
w.Show();
}
}
}
ClientWindow.xaml
<Window x:Class="ChatClient.ClientWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="群聊客户端" Height="300" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Background="Cornsilk" LastChildFill="False">
<TextBlock Text="用户名:" DockPanel.Dock="Left" VerticalAlignment="Center"/>
<TextBox Name="textBoxUserName" Text="西西" Width="100" VerticalAlignment="Center"/>
<Button Name="btnLogin" Content="登录" Click="btnLogin_Click" DockPanel.Dock="Left" Padding="10 0 10 0" Margin="5"/>
</DockPanel>
<DockPanel Name="dockPanel1" Grid.Row="1">
<Grid Width="60">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="在线用户" Grid.Row="0" Background="Blue" Foreground="White" TextAlignment="Center"/>
<Grid Grid.Row="1">
<ListBox Name="listBoxOnLine" Background="AntiqueWhite"/>
</Grid>
</Grid>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Name="textBlockMessage" Grid.Row="0"
Text="对话信息" Background="Beige" TextAlignment="Center"/>
<ListBox Name="listBoxMessage" Grid.Row="1" BorderThickness="1" Padding="0 5 0 0"/>
<DockPanel Grid.Row="2">
<TextBlock Text="发言:" Margin="10 0 0 0" DockPanel.Dock="Left" VerticalAlignment="Center"/>
<Button Name="btnSend" Click="btnSend_Click" Content="发送" Width="50" DockPanel.Dock="Right" Margin="10 0 10 0"/>
<TextBox Name="textBoxTalk" KeyDown="textBoxTalk_KeyDown"/>
</DockPanel>
</Grid>
</DockPanel>
</Grid>
</Window>
ClientWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using ChatClient.ChatServiceReference;
namespace ChatClient
{
/// <summary>
/// ClientWindow.xaml 的交互逻辑
/// </summary>
public partial class ClientWindow : Window, IChatServiceCallback
{
public ClientWindow()
{
InitializeComponent();
this.Closing += ClientWindow_Closing;
dockPanel1.Visibility = System.Windows.Visibility.Hidden;
}
public string UserName
{
get { return textBoxUserName.Text; }
set { textBoxUserName.Text = value; }
}
private ChatServiceClient client;
void ClientWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (client != null)
{
client.Logout(UserName); //退出
client.Close();
}
}
private void AddMessage(string str)
{
TextBlock t = new TextBlock();
t.Text = str;
t.Foreground = Brushes.Blue;
listBoxMessage.Items.Add(t);
}
//单击登录按钮引发的事件
private void btnLogin_Click(object sender, RoutedEventArgs e)
{
this.Cursor = Cursors.Wait;
UserName = textBoxUserName.Text;
InstanceContext context = new InstanceContext(this);
client = new ChatServiceReference.ChatServiceClient(context);
try
{
client.Login(textBoxUserName.Text);
btnLogin.IsEnabled = false;
}
catch (Exception ex)
{
MessageBox.Show("与服务端连接失败:" + ex.Message);
return;
}
this.Cursor = Cursors.Arrow;
}
//单击发送按钮引发的事件
private void btnSend_Click(object sender, RoutedEventArgs e)
{
client.Talk(UserName, textBoxTalk.Text);
textBoxTalk.Text = "";
}
//在文本框中输入对话后按回车键时引发的事件
private void textBoxTalk_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
client.Talk(UserName, textBoxTalk.Text);
textBoxTalk.Text = "";
}
}
#region 实现IRndGameServiceCallback接口
/// <summary>有用户登录</summary>
public void ShowLogin(string loginUserName)
{
if (loginUserName == UserName)
{
dockPanel1.Visibility = System.Windows.Visibility.Visible;
}
listBoxOnLine.Items.Add(loginUserName);
}
/// <summary>其他用户退出</summary>
public void ShowLogout(string userName)
{
listBoxOnLine.Items.Remove(userName);
}
public void ShowTalk(string userName, string message)
{
AddMessage(string.Format("[{0}]说:{1}", userName, message));
}
public void InitUsersInfo(string UsersInfo)
{
if (UsersInfo.Length == 0) return;
string[] users=UsersInfo.Split('、');
for (int i = 0; i < users.Length; i++)
{
listBoxOnLine.Items.Add(users[i]);
}
}
#endregion
}
}
服务端
IChatService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace ChatServer
{
//需要服务端实现的协定
[ServiceContract(Namespace = "WcfChatExample", CallbackContract = typeof(IChatServiceCallback))]
public interface IChatService
{
[OperationContract(IsOneWay = true)]
void Login(string userName);
[OperationContract(IsOneWay = true)]
void Logout(string userName);
[OperationContract(IsOneWay = true)]
void Talk(string userName, string message);
}
//需要客户端实现的接口
public interface IChatServiceCallback
{
[OperationContract(IsOneWay = true)]
void ShowLogin(string loginUserName);
[OperationContract(IsOneWay = true)]
void ShowLogout(string userName);
[OperationContract(IsOneWay = true)]
void ShowTalk(string userName, string message);
[OperationContract(IsOneWay = true)]
void InitUsersInfo(string UsersInfo);
}
}
ChatService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace ChatServer
{
public class ChatService : IChatService
{
/// <summary>
/// 用户退出。参数:登录者用户名
/// </summary>
public void Login(string userName)
{
OperationContext context = OperationContext.Current;
IChatServiceCallback callback = context.GetCallbackChannel<IChatServiceCallback>();
User newUser = new User(userName, callback);
string str = "";
for (int i = 0; i < CC.Users.Count; i++)
{
str += CC.Users[i].UserName + "、";
}
newUser.callback.InitUsersInfo(str.TrimEnd('、'));
CC.Users.Add(newUser);
foreach (var user in CC.Users)
{
user.callback.ShowLogin(userName);
}
}
/// <summary>
/// 用户退出。参数:退出者用户名
/// </summary>
public void Logout(string userName)
{
User logoutUser = CC.GetUser(userName);
CC.Users.Remove(logoutUser);
logoutUser = null; //将其设置为null后,WCF会自动关闭该客户端
foreach (var user in CC.Users)
{
user.callback.ShowLogout(userName);
}
}
/// <summary>
/// 客户端发来的对话信息。参数:发送者用户名,对话内容
/// </summary>
public void Talk(string userName, string message)
{
User user = CC.GetUser(userName);
foreach (var v in CC.Users)
{
v.callback.ShowTalk(userName, message);
}
}
}
}
User.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChatServer
{
public class User
{
public string UserName { get; set; }
public readonly IChatServiceCallback callback;
public User(string userName, IChatServiceCallback callback)
{
this.UserName = userName;
this.callback = callback;
}
}
}
CC.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChatServer
{
public class CC
{
//连接的用户,每个用户都对应一个ChatService线程
public static List<User> Users { get; set; }
static CC()
{
Users = new List<User>();
}
public static User GetUser(string userName)
{
User user = null;
foreach (var v in Users)
{
if (v.UserName == userName)
{
user = v;
break;
}
}
return user;
}
}
}