首先创建一个解决方案,选择WPF应用程序,在WPF这个项目里面点击添加,找到新建项,选择WCF服务,切记不要在解决方案点添加。
服务名可以自己起,但是要记得在配置和其他引用的地方修改。我的服务名是ChatService。
所以我在创建服务时有两个文件,IChatService.cs,ChatService.cs。
我们要先在IChatService.cs定义接口。
[ServiceContract (Namespace = "TcpService",
//SessionMode = SessionMode.Required,
CallbackContract = typeof(IChatServiceCallback))]
public interface IChatService
{
/// <summary>
/// 登录
/// </summary>
/// <param name="userName">用户名</param>
[OperationContract (IsOneWay =true)] void Login(string userName);
/// <summary>
/// 退出登录
/// </summary>
/// <param name="userName"></param>
[OperationContract(IsOneWay = true)] void Logout(string userName);
/// <summary>
/// 发送消息
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="message">消息内容</param>
[OperationContract(IsOneWay = true)] void Talk(string userName, string message);
}
/// <summary>
/// 客户端要实现的回调接口
/// </summary>
public interface IChatServiceCallback
{
/// <summary>
/// 把其他用户的用户名发送客户端
/// </summary>
/// <param name="userNames">用户名数组</param>
[OperationContract(IsOneWay = true)] void ShowUsersInfo(string[] userNames);
/// <summary>
/// 显示其他用户退出
/// </summary>
/// <param name="userName">用户名</param>
[OperationContract(IsOneWay = true)] void ShowLogon(string userName);
/// <summary>
/// 显示其他人发送的消息
/// </summary>
/// <param name="userName"></param>
/// <param name="message"></param>
[OperationContract(IsOneWay = true)] void ShowTalk(string userName, string message);
}
我在这里使用的是单工通信,如果你对TCP的其他通信方式了解的话,也可以使用别的通信方式,但不要忘记修改配置。
然后我们在ChatService.cs实现客户端的接口。
在此之前我们还要先写好几个要使用到的类。
User类,用来储存用户的用户名和实现IChatService.cs接口的用户实例。
class User
{
/// <summary>
/// 用户名
/// </summary>
public string name { get; set; }
/// <summary>
/// 客户端实例
/// </summary>
public IChatServiceCallback callback { get; set; }
public User() { }
public User(string name,IChatServiceCallback callback)
{
this.name = name;
this.callback = callback;
}
}
Lobby类,用来储存以及登录到大厅中的用户。
class Lobby
{
/// <summary>
/// 大厅中在线的用户
/// </summary>
public static List<User> users { get;set; }
/// <summary>
/// 通过用户名找到用户实例
/// </summary>
/// <param name="name">用户名</param>
/// <returns></returns>
public static User getUserByName(string name)
{
User user = new User();
if (users != null)
{
foreach(User u in users)
{
if (u.name == name)
{
user = u;
return user;
}
}
}
return user;
}
}
这两个类写完我们就可以在ChatService.cs实现客户端的接口了。
这些做完之后我们就可以修改配置了,打开App.config,把里面的代码修改成下面的代码
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="TcpService.ChatService">
<endpoint address="net.tcp://localhost:51888/TcpService/ChatService" binding="netTcpBinding" contract="TcpService.IChatService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost:8733/Design_Time_Addresses/TcpService/ChatService/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
<service name="TcpService.ChatService">这一栏中的TcpService对应的是你的项目名,即命名空间名,ChatService是你的服务名。同理下面一行endpoint标签中的contract也是这样。
<add baseAddress="http://localhost:8733/Design_Time_Addresses/TcpService/ChatService/" />这一行是系统自动配置的,一般情况下不要修改,尤其是Design_Time_Addresses关键字,它的用途是确保你以非管理员身份运行和调试。
为了使这个服务可以被引用,我们必须要打开ServiceHost。
首先我们修改一下MainWindow.xaml界面
<Window x:Class="TcpService.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TcpService"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button Content="启动服务" HorizontalAlignment="Left" Height="45" Margin="100,35,0,0" VerticalAlignment="Top" Width="176" RenderTransformOrigin="0.452,0.252" FontSize="18" Click="Button_Start"/>
<Button Content="关闭服务" HorizontalAlignment="Left" Height="45" Margin="480,35,0,0" VerticalAlignment="Top" Width="183" RenderTransformOrigin="-0.148,-0.625" FontSize="18" Click="Button_Stop"/>
<TextBlock Name="tbk" HorizontalAlignment="Left" Height="256" Margin="25,147,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="744" FontSize="18"/>
<TextBlock HorizontalAlignment="Left" Height="24" Margin="340,103,0,0" TextWrapping="Wrap" Text="运行信息" VerticalAlignment="Top" Width="126" FontSize="18"/>
</Grid>
</Window>
记得把类名修改成你自己命名空间下的类。
然后我们再来修改MainWindow.xaml.cs中的代码
public partial class MainWindow : Window
{
/// <summary>
/// 提供服务的主机
/// </summary>
private ServiceHost host;
public MainWindow()
{
InitializeComponent();
//添加关闭窗体的事件
this.Closing += MainWindow_Closing;
}
/// <summary>
/// 关闭窗体时触发
/// </summary>
private void MainWindow_Closing(object sender, CancelEventArgs e)
{
if (host != null)
{
if(host.State == CommunicationState.Opened)
{
host.Close();
}
}
}
/// <summary>
/// 点击启动服务
/// </summary>
private void Button_Start(object sender, RoutedEventArgs e)
{
//新建ChatService服务的服务主机并打开
host = new ServiceHost(typeof(ChatService));
host.Open();
tbk.Text += "本机服务已启动,监听服务的Uri为:\n";
foreach(var v in host.Description.Endpoints)
{
tbk.Text += v.ListenUri.ToString() + "\n";
}
}
/// <summary>
/// 点击关闭服务
/// </summary>
private void Button_Stop(object sender, RoutedEventArgs e)
{
host.Close();
tbk.Text += "本机服务已关闭!"+"\n";
}
}
这样我们服务端的配置就完成了。
接下来我们要在该解决方案下创建客户端,点击该解决方案,仍然选择添加WPF应用程序。
系统加载完后我们就可以添加服务引用了,不要关闭原来的程序,在文件夹中找到你的程序打开刚建好的项目,我新建的项目是TcpClient,我们从这个文件夹下找到TcpClient.csproj打开,就可以再打开一个程序了。
我们回到原程序界面,把原项目设为启动项目(没改过一般不用自己调默认就是)
点击启动进行调试,点击启动服务按钮
出现了运行信息就说明启动成功了,去到新项目的程序界面,右键点击引用,添加服务引用
在地址栏中将配置App.config时,<add baseAddress="http://localhost:8733/Design_Time_Addresses/TcpService/ChatService/" />标签中的地址过去,点击转到,服务栏中出现了服务,说明服务下载成功了,修改最下面的命名空间
也可以使用默认的命名空间,我使用的是ChatServiceReference
保存后我们就可以关闭这个程序界面了,回到原程序界面,重新从磁盘中加载一下。
然后我们右键客户端项目,添加,窗口,我的窗体名是ChatWindow,修改ChatWindow.xaml为
<Window x:Class="TcpClient.ChatWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TcpClient"
mc:Ignorable="d"
Title="群聊客户端" Height="450" Width="800">
<Grid>
<TextBlock HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" Text="用户名:" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.302,0.348" FontSize="18" />
<TextBox Name="Name" HorizontalAlignment="Left" Height="23" Margin="96,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="100" FontSize="18"/>
<TextBlock HorizontalAlignment="Left" Height="23" Margin="13,49,0,0" TextWrapping="Wrap" Text="在线用户" VerticalAlignment="Top" Width="150" TextAlignment="Center" FontSize="18"/>
<ListBox Name="userList" HorizontalAlignment="Left" Height="323" Margin="14,88,0,0" VerticalAlignment="Top" Width="150" FontSize="18"/>
<TextBlock HorizontalAlignment="Left" Height="42" Margin="350,24,0,0" TextWrapping="Wrap" Text="对话信息" VerticalAlignment="Top" Width="220" RenderTransformOrigin="0.288,-0.171" FontSize="30" TextAlignment="Center"/>
<ListBox Name="mesList" HorizontalAlignment="Left" Height="271" Margin="190,88,0,0" VerticalAlignment="Top" Width="575" FontSize="18"/>
<TextBlock HorizontalAlignment="Left" Height="35" Margin="190,375,0,0" TextWrapping="Wrap" Text="发言:" VerticalAlignment="Top" Width="73" FontSize="18"/>
<TextBox Name="Message" HorizontalAlignment="Left" Height="35" Margin="283,375,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="357" FontSize="18" />
<Button Content="发送" HorizontalAlignment="Left" Height="35" Margin="679,375,0,0" VerticalAlignment="Top" Width="71" RenderTransformOrigin="0.207,0.361" FontSize="18" Click="Button_Send"/>
<Button Name="btn_Login" Content="登录" HorizontalAlignment="Left" Height="30" Margin="665,10,0,0" VerticalAlignment="Top" Width="100" FontSize="18" Click="Button_Login"/>
</Grid>
</Window>
同样,类和项目名与我不一样的记得修改。
然后在ChatWindow.xaml.cs实现聊天功能
public partial class ChatWindow : Window,IChatServiceCallback
{
/// <summary>
/// 服务端实例
/// </summary>
private ChatServiceClient client;
/// <summary>
/// 用户名
/// </summary>
private string name;
/// <summary>
/// 在线用户信息显示控件的列表
/// </summary>
private List<TextBlock> texts = new List<TextBlock>();
/// <summary>
/// 通过用户名找到相应控件
/// </summary>
/// <param name="text">用户名信息</param>
/// <returns></returns>
private TextBlock GetTextBlockByText(string text)
{
TextBlock tb = new TextBlock();
foreach(TextBlock t in texts)
{
if (t.Text == text)
{
tb = t;
return tb;
}
}
return tb;
}
public ChatWindow(string name)
{
InitializeComponent();
//获取服务端实例
InstanceContext context = new InstanceContext(this);
client = new ChatServiceReference.ChatServiceClient(context);
//把传入的用户名显示出来
Name.Text = name;
this.name = name;
this.Closing += MainWindow_Closing;
}
#region 点击事件
/// <summary>
/// 关闭窗体时执行的语句
/// </summary>
private void MainWindow_Closing(object sender, CancelEventArgs e)
{
client.Logout(name);
}
/// <summary>
/// 点击登录按钮
/// </summary>
private void Button_Login(object sender, RoutedEventArgs e)
{
name = Name.Text.Trim();
client.Login(name);
//仅可登录一次
btn_Login.IsEnabled = false;
}
/// <summary>
/// 点击发送按钮
/// </summary>
private void Button_Send(object sender, RoutedEventArgs e)
{
client.Talk(name, Message.Text.Trim());
TextBlock tb = new TextBlock()
{
Text = name + ":" + Message.Text.Trim(),
FontSize = 18,
Height = 23
};
mesList.Items.Add(tb);
}
#endregion
#region 实现的接口
/// <summary>
/// 在用户信息列表中显示信息
/// </summary>
/// <param name="userNames">用户列表数组</param>
public void ShowUsersInfo(string[] userNames)
{
if (userList.Items.Count==0)
{
foreach (string name in userNames)
{
TextBlock tb = new TextBlock()
{
Text = name,
FontSize = 18,
Height = 23
};
userList.Items.Add(tb);
texts.Add(tb);
}
}
else foreach(string name in userNames)
{
if (GetTextBlockByText(name).Text=="")
{
TextBlock tb = new TextBlock()
{
Text = name,
FontSize = 18,
Height = 23
};
userList.Items.Add(tb);
texts.Add(tb);
}
}
}
/// <summary>
/// 显示其他用户发送的消息
/// </summary>
/// <param name="userName"></param>
/// <param name="message"></param>
public void ShowTalk(string userName, string message)
{
TextBlock tb = new TextBlock()
{
Text = userName+":"+message,
FontSize = 18,
Height = 23
};
mesList.Items.Add(tb);
}
public void ShowLogon(string userName)
{
TextBlock tb = GetTextBlockByText(userName);
userList.Items.Remove(tb);
}
#endregion
}
不要忘记导入服务引用using TcpClient.ChatServiceReference;
ShowUsersInfo接口中更新信息代码效率比较低,这里只是简易实现,把两种情况分开实现接口可以提高效率。
然后我们在MianWindow中分测试和实际情况打开聊天窗口。修改MainWindow.xaml为
<Window x:Class="TcpClient.ChatWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TcpClient"
mc:Ignorable="d"
Title="群聊客户端" Height="450" Width="800">
<Grid>
<TextBlock HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" Text="用户名:" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.302,0.348" FontSize="18" />
<TextBox Name="Name" HorizontalAlignment="Left" Height="23" Margin="96,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="100" FontSize="18"/>
<TextBlock HorizontalAlignment="Left" Height="23" Margin="13,49,0,0" TextWrapping="Wrap" Text="在线用户" VerticalAlignment="Top" Width="150" TextAlignment="Center" FontSize="18"/>
<ListBox Name="userList" HorizontalAlignment="Left" Height="323" Margin="14,88,0,0" VerticalAlignment="Top" Width="150" FontSize="18"/>
<TextBlock HorizontalAlignment="Left" Height="42" Margin="350,24,0,0" TextWrapping="Wrap" Text="对话信息" VerticalAlignment="Top" Width="220" RenderTransformOrigin="0.288,-0.171" FontSize="30" TextAlignment="Center"/>
<ListBox Name="mesList" HorizontalAlignment="Left" Height="271" Margin="190,88,0,0" VerticalAlignment="Top" Width="575" FontSize="18"/>
<TextBlock HorizontalAlignment="Left" Height="35" Margin="190,375,0,0" TextWrapping="Wrap" Text="发言:" VerticalAlignment="Top" Width="73" FontSize="18"/>
<TextBox Name="Message" HorizontalAlignment="Left" Height="35" Margin="283,375,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="357" FontSize="18" />
<Button Content="发送" HorizontalAlignment="Left" Height="35" Margin="679,375,0,0" VerticalAlignment="Top" Width="71" RenderTransformOrigin="0.207,0.361" FontSize="18" Click="Button_Send"/>
<Button Name="btn_Login" Content="登录" HorizontalAlignment="Left" Height="30" Margin="665,10,0,0" VerticalAlignment="Top" Width="100" FontSize="18" Click="Button_Login"/>
</Grid>
</Window>
修改MainWindow.xaml.cs为
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// 打开两个窗体
/// </summary>
private void Button_Click1(object sender, RoutedEventArgs e)
{
ChatWindow chat1 = new ChatWindow("西西皮");
chat1.Show();
ChatWindow chat2 = new ChatWindow("瓜瓜糖");
chat2.Show();
}
/// <summary>
/// 打开一个窗体
/// </summary>
private void Button_Click2(object sender, RoutedEventArgs e)
{
ChatWindow chat1 = new ChatWindow("西西皮");
chat1.Show();
}
}