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






