资源描述
面向对象网络程序设计课程设计报告
郑州轻工业学院
课 程 设 计 报 告
课程设计名称:
C#网络应用编程课程设计
指导教师:
姓名:
学号:
班级:
提交日期:
2015.7.2
1. 目的
通过开发一款网络聊天程序, 熟练掌握多线程编程技术和TCP应用编程技术。 通过本实训, 达到如下目标:
1)理解委托的概念, 掌握线程之间通过委托与控件交互的方法。
2)理解异常处理的机制,掌握异常处理语句的用法。
3)理解线程的概念, 掌握多线程编程的一般方法。
4)掌握基于TCP的应用编程技术 。
2. 题目
使用c#编程语言, 开发一款网络聊天程序, 玩家通过客户端登陆远程的服务器, 登陆成功后可以与其他在线的玩家通信。
3. 功能描述
1) 当服务器开启服务后, 允许任何一个玩家通过客户端登陆服务器。
2) 每当有玩家登录或退出时, 每一个在线的玩家都会刷新玩家列表。
3) 当玩家登陆成功后, 可以通过服务器端与任何一个在线的玩家聊天
4. 需求分析
定义数据字典如下:
用户(User):在网络聊天程序中,用户是已登陆的玩家。用户具有玩家的用户名、 客户端与服务器之间建立的 Tc连接等属性 。 在程序中, 用户负责向服务器发送和接收数据。
5. 设计说明
根据需求分析可知, 管理员可以启动停止服务器; 玩家可以登陆服务器; 登录后的玩家可以与其它在线的玩家聊天; 在线玩家关闭客户端即退出登录。
客户端发送给服务器端的命令有如下3种:
1)1ogin,用户名:玩家请求登陆服务器。玩家点击“登陆''按钮后, 就会发送1ogin命令, 其中的用户名就是请求登陆玩家的用户名 。
2)1ogout,用户名: 玩家请求退出服务器。 玩家关闭客户端的主窗体后, 就会发送1ogout命令, 其中的用户名就是请求退出玩家的用户名 。
3)talk,用户名1 ,用户名2,信息: 玩家发出聊天信息 。 玩家点击 “发送''按钮后,就会发送talk命令,其中的用户名1是发出信息的玩家, 用户名2是接收信息的玩家 。
服务器端发送给客户端的命令有如下4种:
1)1ogin,用户名:服务器接受到1ogin命令后,检査用户集合,如果没有重名的在线用户,则给所有用户发送1ogin命令,通知玩家更新用户列表。
2)1ogout,用户名: 服务器接收到1ogout命令后, 向所有在线用户发送1ogout命令, 通知其他玩家更新用户列表 。
3)talk,用户名1 ,用户名2,信息: 服务器接收到talk命令后, 先给用户2发送talk命令; 发送成功后, 再给用户1发送talk命令, 通知用户1 , 聊天信息发送成功。
6. 源代码
1、 服务器源代码:
User类:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ChatServer
{
public class User
{
public BinaryReader br { get; private set; }
public BinaryWriter bw { get; private set; }
private string userName;
private TcpClient client;
public User(TcpClient client, bool isTask)
{
this.client = client;
NetworkStream networkStream = client.GetStream();
br = new BinaryReader(networkStream);
bw = new BinaryWriter(networkStream);
if (isTask)
{
Task.Run(() => ReceiveFromClient());
}
else
{
Thread t = new Thread(ReceiveFromClient);
t.Start();
}
}
public void Close()
{
br.Close();
bw.Close();
client.Close();
}
/// <summary>处理接收的客户端数据</summary>
public void ReceiveFromClient()
{
while (true)
{
string receiveString = null;
try
{
receiveString = br.ReadString();
}
catch
{
CC.RemoveUser(this); return;
}
string[] split = receiveString.Split(',');
switch (split[0])
{
case "Login": //格式:Login,用户名
userName = split[1];
CC.SendToAllClient(split[1] + "登录成功,当前用户数:" + CC.userList.Count);
break;
case "Logout": //格式:Logout,用户名
CC.RemoveUser(this);
return;
case "Talk": //格式:Talk,对话信息
CC.SendToAllClient(userName + "说:" + receiveString.Remove(0, 5));
break;
default:
throw new Exception("无法解析:" + receiveString);
}
}
}
}
}
CC类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace ChatServer
{
public class CC
{
public static List<User> userList { get; set; } //保存连接的所有用户
public static IPAddress localIP { get; set; } //使用的本机IPv4地址
public static int port { get; set; } //监听端口
static CC()
{
userList = new List<User>();
port = 51888;
IPAddress[] ips = Dns.GetHostAddresses(Dns.GetHostName());
foreach (var v in ips)
{
if (v.AddressFamily == AddressFamily.InterNetwork)
{
localIP = v; break;
}
}
}
public static void RemoveUser(User user)
{
userList.Remove(user);
SendToAllClient("有用户退出或失去连接,当前用户数:" + userList.Count);
}
public static void SendToAllClient(string message)
{
for (int i = 0; i < userList.Count; i++)
{
try
{
userList[i].bw.Write(message);
userList[i].bw.Flush();
}
catch
{
RemoveUser(userList[i]);
}
}
}
public static void ChangeState(Button btn1, bool b1, Button btn2, bool b2)
{
btn1.IsEnabled = b1;
btn2.IsEnabled = b2;
}
}
}
MainWindow/窗口代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
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;
namespace ChatServer
{
/// <summary>
/// TaskAsyncServer.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
private TcpListener myListener;
public MainWindow()
{
InitializeComponent();
this.Closing += TaskServer_Closing;
btnStop.IsEnabled = false;
}
void TaskServer_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (myListener != null) myListener.Stop();
}
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
CC.ChangeState(btnStart, false, btnStop, true);
myListener = new TcpListener(CC.localIP, CC.port);
myListener.Start();
textBlock1.Text += string.Format("开始在{0}:{1}监听客户连接",
CC.localIP, CC.port);
while (true)
{
try
{
TcpClient newClient = await myListener.AcceptTcpClientAsync();
User user = new User(newClient, true);
CC.userList.Add(user);
}
catch { break; }
}
}
private void btnStop_Click(object sender, RoutedEventArgs e)
{
myListener.Stop();
textBlock1.Text += "\n监听已停止!";
CC.ChangeState(btnStart, true, btnStop, false);
}
}
}
2、 客户端源代码:
TaskClientWindow窗体代码:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
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;
namespace ChatClient
{
/// <summary>
/// TaskClientWindow.xaml 的交互逻辑
/// </summary>
public partial class TaskClientWindow : Window
{
private bool isExit = false;
private TcpClient client;
private BinaryReader br;
private BinaryWriter bw;
private string remoteHost;
private int remotePort = 51888;
public TaskClientWindow()
{
InitializeComponent();
this.Closing += ClientWindow_Closing;
remoteHost = Dns.GetHostName();
}
void ClientWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
//未与服务器连接前client为null
if (client != null)
{
SendMessage("Logout," + textBoxUserName.Text);//格式:Logout,用户名
isExit = true;
br.Close();
bw.Close();
client.Close();
}
}
private void AddInfo(string format, params object[] args)
{
textBlock1.Dispatcher.InvokeAsync(() =>
{
textBlock1.Text += string.Format(format, args) + "\n";
});
}
private void btnLogin_Click(object sender, RoutedEventArgs e)
{
try
{
//此处为方便演示,实际使用时要将Dns.GetHostName()改为服务器域名
client = new TcpClient(remoteHost, remotePort);
AddInfo("与服务端连接成功");
btnLogin.IsEnabled = false;
}
catch
{
AddInfo("与服务端连接失败");
return;
}
//获取网络流
NetworkStream networkStream = client.GetStream();
//将网络流作为二进制读写对象
br = new BinaryReader(networkStream);
bw = new BinaryWriter(networkStream);
SendMessage("Login," + textBoxUserName.Text);//格式:Login,用户名
Task.Run(() => ReceiveData());
}
private void btnSend_Click(object sender, RoutedEventArgs e)
{
SendMessage("Talk," + textBoxSend.Text);//格式:Talk,对话信息
textBoxSend.Clear();
}
/// <summary>处理接收的数据</summary>
private void ReceiveData()
{
string receiveString = null;
while (isExit == false)
{
try
{
//从网络流中读出字符串
//此方法会自动判断字符串长度前缀,并根据长度前缀读出字符串
receiveString = br.ReadString();
}
catch
{
if (isExit == false)
{
AddInfo("与服务端失去联系。");
}
break;
}
AddInfo(receiveString);
}
// this.Close();
}
//向服务器端发送信息
private void SendMessage(string message)
{
try
{
//将字符串写入网络流,此方法会自动附加字符串长度前缀
bw.Write(message);
bw.Flush();
}
catch
{
AddInfo("发送失败!");
}
}
}
}
7. 测试报告
1) 测试用例
1、 测试客户端是否能在服务器启动时登陆
2、 测试客户端是否能在服务器时停止时登陆。
3、 测试客户端登陆后,能否正确的显示当前在线用户
2) 测试结果
1、测试玩家发出聊天信息后,接收方是否能正确接收到数据。
8. 心得体会
通过本次实训,理解了TCP协议是怎么工作的,理解了服务器和客户端是怎么进行数据通信的,以及服务器是如何反馈给客户端信息的。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。
通过这些的学习深刻理解网络编程的价值和意义,为以后更高层次的学习打下了基础,对网络编程产生了更深的兴趣。当然本次实训,我也有很多不足的地方,希望在今后的锻炼中能够不断的改进。
13
展开阅读全文