文章目录
实验环境
WindowsServer2012+vs2010
实现效果
单机MainWindow里面的“网络会议室”按钮,然后弹出三个客户端,三个客户端端口和姓名已分配
每个客户端可以点击登陆按钮,进入会议室,只要登入的客户端,每个客户端左边的会议室成员列表都会实时更新
登陆进去的客户端,可以参与讨论,消息共享。
单机退出按钮,关闭客户端,并且其余未退出的客户端会更新在线人员列表。
实现原理
主要是利用套接字实现发送和接受消息,然后利用多播和单播结合来实现会议讨论功能。
具体功能设计思路解析如图:
一、
二、
三、
四、
五、
发消息聊天比较简单,直接RoomService发广播就好了。
一些需要注意的点
- 三个客户端自始至终都只监听8000,只跟RoomService通信
- 用户登录,先告知他当前在线成员,再将他加入用户列表,注意顺序,否则会出现一个名字加两遍
- 当收到用户登录信息,
RoomService
在实例化User时,要注意,User的UserEndPoint
是该用户端口信息,后边ShowCurrentUsers
要用到
ps:这个程序目前的缺点是,客户端退出之后,窗口就关闭了,没有办法再次加入会议室,我本来想着通过登录标志isLogin
和关闭标志isExit
,来实现单机退出按钮时,不关闭窗口,只退出多播组。这样虽然成功了,但是再次加入会议室的时候,报错,提示套接字只能使用一次。。。。。。算咯,以后会了再改吧
源码及详细注释
MainWindow后台代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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;
using System.Net;
using System.Net.Sockets;
namespace ch11
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
private IPAddress ip;
public MainWindow()
{
InitializeComponent();
//获取本机ip地址
IPAddress[] ipes = Dns.GetHostAddresses(Dns.GetHostName());
foreach (IPAddress v in ipes)
{
if (v.AddressFamily == AddressFamily.InterNetwork)
{ ip = v; break; }
}
}
//启动会议室程序
private void button1_Click(object sender, RoutedEventArgs e)
{
int servicePort = 8000;//RoomService端口
RoomService service = new RoomService(ip, servicePort);//先启动RoomService
ShowClient("客户端1", "张三", -360, -50, 8001, servicePort);
ShowClient("客户端2", "李四", 210, -50, 8002, servicePort);
ShowClient("客户端3", "王五", -100, 140, 8003, servicePort);
}
private void ShowClient(string title, string userName,double dx, double dy, int port, int servicePort)
{
NetMeetingClient w = new NetMeetingClient()
{
Title = title,
Owner = this,
Left = this.Left + dx,
Top = this.Top + dy,
localEndPoint = new IPEndPoint(ip, port),//这里是他们自己的端口
remoteEndPoint = new IPEndPoint(ip, servicePort)//这里是8000
};
w.UserName = userName;
w.Show();
}
}
}
NetMeetingClient前台设计代码
<Window x:Class="ch11.NetMeetingClient"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="网络会议室" Height="354" Width="480">
<Grid>
<GroupBox Header="会议室成员" Height="185" HorizontalAlignment="Left" Margin="21,87,0,0" Name="groupBox1" VerticalAlignment="Top" Width="133">
<Grid>
<ListBox Height="142" HorizontalAlignment="Left" Margin="6,6,0,0" Name="listBox1" VerticalAlignment="Top" Width="97" />
</Grid>
</GroupBox>
<GroupBox Header="发言内容" Height="179" HorizontalAlignment="Left" Margin="171,87,0,0" Name="groupBox2" VerticalAlignment="Top" Width="261">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="53*" />
<RowDefinition Height="25*" />
</Grid.RowDefinitions>
<ListBox Grid.RowSpan="2" Height="142" HorizontalAlignment="Left" Margin="6,6,0,0" Name="listBox2" VerticalAlignment="Top" Width="237" />
</Grid>
</GroupBox>
<Label Content="发言:" Height="32" HorizontalAlignment="Left" Margin="22,281,0,0" Name="label1" VerticalAlignment="Top" Width="60" />
<TextBox Height="32" HorizontalAlignment="Right" Margin="0,281,110,0" Name="textBox1" VerticalAlignment="Top" Width="260" />
<Button Content="发送" Height="23" HorizontalAlignment="Left" Margin="371,285,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<Label Content="用户名:" Height="28" HorizontalAlignment="Left" Margin="27,21,0,0" Name="label2" VerticalAlignment="Top" />
<TextBox Height="24" HorizontalAlignment="Left" Margin="91,21,0,0" Name="textBox2" VerticalAlignment="Top" Width="95" />
<Button Content="登录(进入会议室)" Height="22" HorizontalAlignment="Left" Margin="224,23,0,0" Name="button2" VerticalAlignment="Top" Width="124" Click="button2_Click" />
<Button Content="退出" Height="23" HorizontalAlignment="Left" Margin="371,22,0,0" Name="button3" VerticalAlignment="Top" Width="75" Click="button3_Click" />
</Grid>
</Window>
NetMeetingClient后台代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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 System.Net;
using System.Net.Sockets;
using System.Threading;
namespace ch11
{
/// <summary>
/// NetMeetingClient.xaml 的交互逻辑
/// </summary>
public partial class NetMeetingClient : Window
{
public IPEndPoint localEndPoint { get; set; }
public IPEndPoint remoteEndPoint;
private UdpClient client;
public string UserName;
private IPAddress multicastAddress = IPAddress.Parse("224.0.1.1");
private bool isExit = false;//窗口是否关闭
private bool isLogin = false;//是否登陆
public NetMeetingClient()
{
InitializeComponent();
this.Loaded += NetMeeting_Loaded;
this.Closing += NetMeeting_Closing;//加载关闭属性
}
//新启动的窗口为用户名一栏赋值 并设置“退出”按钮
void NetMeeting_Loaded(object sender, RoutedEventArgs e)
{
textBox2.Text=this.UserName;
button3.IsEnabled = false;
}
//关闭窗口
void NetMeeting_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (isLogin)
{
SendMessage("Logout," + UserName);//告诉管理员 用户退出
}
isExit = true;//窗口关闭
}
//给remoteEndPoint发送消息
private void SendMessage(string Sendstring)
{
byte[] bytes = System.Text.Encoding.Unicode.GetBytes(Sendstring);
client.Send(bytes, bytes.Length, remoteEndPoint);
}
//用户列表添加用户
private void AddUser(string userName)
{
Action act = delegate()
{
listBox1.Items.Add(userName);
};
listBox1.Dispatcher.Invoke(act);
}
//用户列表移除用户
private void RemoveUser(string userName)
{
for (int i = 0; i < listBox1.Items.Count; i++)
{
if (listBox1.Items[i].ToString() == userName)
{
Action act = delegate()
{
listBox1.Items.Remove(userName);
};
listBox1.Dispatcher.Invoke(act);
break;
}
}
}
//聊天列表添加信息
private void AddTalk(string format,params object[] args)
{
string content = string.Format(format, args);
Action act = delegate() { listBox2.Items.Add(content); };
listBox2.Dispatcher.Invoke(act);
}
//登录按钮
private void button2_Click(object sender, RoutedEventArgs e)
{
client = new UdpClient(localEndPoint);//生成套接字
client.JoinMulticastGroup(multicastAddress);//加入多播组
SendMessage("Login," + UserName);//告诉8000端口 用户登录
Thread t1 = new Thread(ReceiveDate);//委托线程监听8000端口消息
t1.Start();
isLogin = true;//登陆标志
button2.IsEnabled = false;//登陆按钮
button3.IsEnabled = true;//退出按钮
}
//监听并接收信息
public void ReceiveDate()
{
while (isExit == false)
{
try
{
byte[] result = client.Receive(ref remoteEndPoint);//接受8000端口信息
string message = Encoding.Unicode.GetString(result);
string[] split = message.Split(',');//分割
string command = split[0];
string args = message.Remove(0, split[0].Length + 1);
switch (command)
{
case "Login":
{
string userName = args;
AddUser(userName);
break;
}
case "List": //添加已在线成员列表
{
for (int i = 1; i < split.Length; i++)
{
AddUser(split[i]);
}
break;
}
case "Logout":
{
string userName = args;
RemoveUser(userName);
break;
}
case "Say":
{
AddTalk("{0}:{1}", split[1], args.Remove(0, split[1].Length + 1));
break;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
client.DropMulticastGroup(multicastAddress);//退出多播组
client.Close();//关闭套接字
}
private void button3_Click(object sender, RoutedEventArgs e)//退出
{
this.Close();//关闭窗口 退出信息在关闭窗口的属性方法里面写着
}
private void button1_Click(object sender, RoutedEventArgs e)//发送
{
SendMessage("Say," + UserName + "," + textBox1.Text);
textBox1.Text = "";//发送完消息 输入框置为空
}
}
}
RoomService类源码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows;
namespace ch11
{
class RoomService
{
private Dictionary<string, User> users = new Dictionary<string, User>();
public IPEndPoint localEndPoint { get; set; }
private UdpClient client;
private IPAddress multicastAddress = IPAddress.Parse("224.0.1.1");//多播地址
public bool isExit = false;
public RoomService(IPAddress localAddress, int servicePort)
{
localEndPoint = new IPEndPoint(localAddress, servicePort);
client = new UdpClient(localEndPoint);
Thread t1 =new Thread(ReceiveDate);//加入多播组 监听所有端口信息
t1.Start();
}
public void CloseSerivce()
{
isExit = true;//while循环结束 退出多播组
}
public void ReceiveDate()
{
IPEndPoint remote = null;//监听所有端口信息
client.JoinMulticastGroup(multicastAddress);
while (isExit==false)
{
try
{
byte[] result = client.Receive(ref remote);//接收所有端口信息
string message = Encoding.Unicode.GetString(result);
string[] split = message.Split(',');//分割
switch (split[0])
{
case "Login":
{
string userName = split[1];
if (users.ContainsKey(userName))
{
string str = "CanNotLogin," + "已有该名称";
byte[] bytess = Encoding.Unicode.GetBytes(str);
client.Send(bytess, bytess.Length, remote);
}
else
{
User user = new User()
{
UserName=userName,
UserEndPoint = remote//注意:此时的remote是当前正在登录的用户的端口
};
ShowCurrentUsers(user);//要先为该用户发送已在线人员信息
users.Add(userName, user);//再将此用户加入字典
Multicast(message);//广播该用户上线信息 使其他客户端将其加入列表
}
break;
}
case "Logout":
{
string userName = split[1];
users.Remove(userName);//移除该用户
Multicast(message);//广播移除信息
break;
}
case "Say":
{
Multicast(message);//广播消息
break;
}
}
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
client.DropMulticastGroup(multicastAddress);//退出多播组
client.Close();
}
//用户刚刚登陆 先将会议室现有人员列表发送给该用户
private void ShowCurrentUsers(User user)
{
if (users.Count == 0) return;
string s = "List";
//遍历现有的在线人员列表
foreach (var v in users.Keys)
{
s += "," + v;
}
byte[] bytes = Encoding.Unicode.GetBytes(s);
client.Send(bytes, bytes.Length, user.UserEndPoint);//发送给该用户 UserEndPoint
}
//在会议室广播
private void Multicast(string message)
{
string hostname = multicastAddress.ToString();
byte[] bytes = Encoding.Unicode.GetBytes(message);
for (int i = 8001; i < 8004; i++)
{
client.Send(bytes, bytes.Length, hostname,i);//将登陆信息发给所有客户端
}
}
}
public class User
{
public string UserName{get;set;}
public IPEndPoint UserEndPoint{get;set;}
}
}