实验目的
一. 编写端口扫描器程序,分别采用单一进程和多线程方式,对比两者的效果。
二. 编写一个网游客户端,游戏服务器的校园内网IP地址为 10.160.52.106,端口为3900,采用TCP连接。
-
连接成功后,可以将服务器发来的消息不停地显示在 listbox 中;
-
客户端要发给服务器的数据,通过 textbox输入 或者点击button;
-
能够播放背景音乐;
-
每隔30秒,变换一次游戏背景图片。
编写端口扫描器
-
创建项目文件
-
布局
-
单线程实现端口扫描,代码如下
using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
namespace PortScan
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//自定义变量
private int port;//记录当前扫描的端口号
private string Address;//记录扫描的系统地址
private bool[] done = new bool[65536];//记录端口的开放状态
private int start;//记录扫描的起始端口
private int end;//记录扫描的结束端口
private bool OK;
private void button1_Click(object sender, EventArgs e)
{
label4.Text = textBox2.Text;
label6.Text = textBox3.Text;
progressBar1.Minimum = Int32.Parse(textBox2.Text);
progressBar1.Maximum = Int32.Parse(textBox3.Text);
listBox1.Items.Clear();
listBox1.Items.Add("端口扫描器v1.0.");
listBox1.Items.Add("");
PortScan();
}
private void PortScan()
{
start = Int32.Parse(textBox2.Text);
end = Int32.Parse(textBox3.Text);
//判断输入端口是否合法
if((start>=0&&start<=65536)&&(end>=0&&end<=65536)&&(start<=end))
{
listBox1.Items.Add("开始扫描:这个过程可能需要等待几分钟!");
Address = textBox1.Text;
for(int i = start; i <= end; i++)
{
port = i;
Scan();
progressBar1.Value = i;
label5.Text = i.ToString();
}
while (!OK)
{
OK = true;
for(int i = start; i <= end; i++)
{
if (!done[i])
{
OK = false;
break;
}
}
}
listBox1.Items.Add("扫描结束!");
}
else
{
MessageBox.Show("输入错误,端口范围为[0,65536]");
}
}
//连接端口
private void Scan()
{
int portnow = port;
done[portnow] = true;
TcpClient objTCP = null;
try
{
objTCP = new TcpClient(Address, portnow);
listBox1.Items.Add("端口"+portnow.ToString()+"开放");
}
catch
{
}
}
}
}
首先整个过程是依靠单条线程,先对输入开始端口到结束端口进行连接,连接成功,表示这个端口是开放的,并将其记录下来。然后就是将开放的端口在ListBox中显示出来。
- 多线程实现端口扫描,代码如下
using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace PortScan
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//自定义变量
private int port;//记录当前扫描的端口号
private string Address;//记录扫描的系统地址
private bool[] done = new bool[65536];//记录端口的开放状态
private int start;//记录扫描的起始端口
private int end;//记录扫描的结束端口
private bool OK;
private Thread scanThread;
//将输入的起始端口放到进度条的开始位置
private void label4_TextChanged(object sender, EventArgs e)
{
label4.Text = textBox2.Text;
}
//将输入的结束地址放到进度条的结束位置
private void label6_TextChanged(object sender, EventArgs e)
{
label6.Text = textBox3.Text;
}
private void button1_Click(object sender, EventArgs e)
{
label4_TextChanged(sender, e);
label6_TextChanged(sender, e);
//创建线程,并创建ThreadStart委托对象
Thread procss = new Thread(new ThreadStart(PortScan));
procss.Start();
//显示端口扫描范围
progressBar1.Minimum = Int32.Parse(textBox2.Text);
progressBar1.Maximum = Int32.Parse(textBox3.Text);
//显示框的初始化
listBox1.Items.Clear();
listBox1.Items.Add("端口扫描器v1.0.");
listBox1.Items.Add("");
}
private void PortScan()
{
start = Int32.Parse(textBox2.Text);
end = Int32.Parse(textBox3.Text);
//检查端口的合法性
if ((start >= 0 && start <= 65536) && (end >= 0 && end <= 65536) && (start <= end))
{
listBox1.Items.Add("开始扫描:这个过程可能需要等待几分钟!");
Address = textBox1.Text;
for (int i = start; i <= end; i++)
{
port = i;
//对该端口进行扫描的线程
scanThread = new Thread(Scan);
scanThread.Start();
//使线程睡眠
System.Threading.Thread.Sleep(100);
progressBar1.Value = i;
label5.Text = i.ToString();
}
//未完成时情况
while (!OK)
{
OK = true;
for (int i = start; i <= end; i++)
{
if (!done[i])
{
OK = false;
break;
}
}
}
listBox1.Items.Add("扫描结束!");
System.Threading.Thread.Sleep(1000);
}
else
{
MessageBox.Show("输入错误,端口范围为[0,65536]");
}
}
private void Scan()
{
int portnow = port;
//创建线程变量
Thread Threadnow = scanThread;
done[portnow] = true;
//创建TcpClient对象,TcpClient用于TCP网络服务提供客户端连接
TcpClient objTCP = null;
//扫描端口,成功就写入信息
try
{
objTCP = new TcpClient(Address, portnow);
listBox1.Items.Add("端口" + portnow.ToString() + "开放!");
}
catch
{
}
}
}
}
整个过程使用多个线程来实现扫描端口,次线程的进行并不会影响主线程的进行,线程之间不会相互影响。
编写网游客户端
- 新建项目
- 设计界面
- 键入代码到进入游戏按钮
private void button2_Click(object sender, EventArgs e)
{
/*
* 连接服务器
*/
try
{
//实例化
tcpClient = new TcpClient();
//向指定的IP地址的服务器发出连接请求
tcpClient.Connect("10.160.52.106", 4000);
//获取网络传输流
stream = tcpClient.GetStream();
//接受数据并转化为字符串
byte[] data = new byte[1024];
int receive = stream.Read(data, 0, 1024);
string msg = Encoding.Default.GetString(data, 0, receive);
//显示出来
textBox2.AppendText(msg);
}
catch
{
textBox2.AppendText("服务器未启动" + Environment.NewLine);
}
}
- 连接成功
- 播放音乐,在构造函数中键入以下代码
public Form1()
{
InitializeComponent();
//设置音量调节器的初始位置
trackBar1.Value = 50;
label1.Text = "50";
axWindowsMediaPlayer1.Hide();
}
然后在button1_Click函数键入以下代码
private void button2_Click(object sender, EventArgs e)
{
/*
* 连接服务器
*/
try
{
//实例化
tcpClient = new TcpClient();
//向指定的IP地址的服务器发出连接请求
tcpClient.Connect("10.160.52.106", 3900);
//获取网络传输流
stream = tcpClient.GetStream();
//接受数据并转化为字符串
byte[] data = new byte[1024];
int receive = stream.Read(data, 0, 1024);
string msg = Encoding.Default.GetString(data, 0, receive);
//去除字符串中的终端转义字符
msg = strDelete(msg);
//显示出来
textBox2.AppendText(msg);
}
catch
{
textBox2.AppendText("服务器未启动" + Environment.NewLine);
}
/******************
* 播放背景音乐
******************/
//设置音量键值
trackBar1.Value = 50;
label1.Text = "50";
//字符串存储音乐路径
string s = @"C:\Users\ASUS\Music\damian.mp3";
//设置为循环播放
axWindowsMediaPlayer1.settings.setMode("loop", true);
//设置初始音乐的音量大小(范围:0——100)
axWindowsMediaPlayer1.settings.volume = 50;
//设置播放歌曲的路径
axWindowsMediaPlayer1.URL = s;
}
在trackBar1控件中键入以下代码
/*****************
* 音量调节
*****************/
private void trackBar1_Scroll(object sender, EventArgs e)
{
//控制音量大小
axWindowsMediaPlayer1.settings.volume = trackBar1.Value;
//控制音量显示
label1.Text = trackBar1.Value.ToString();
}
- 变换背景图片
加入timer控件,一定要让其父类为Forms。
设置Enabled 为 True
设置 Interval 为 3000
在timer1_Tick中键入以下代码
//定义flag变量
private int flag = 0;
/*****************
* 自动换图
*****************/
private void timer1_Tick(object sender, EventArgs e)
{
flag++;
string picturePath = @"F:\121\" + flag + ".jpg";
//设置图片填充
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox1.Image = Image.FromFile(picturePath);
if (flag == 5)
{
flag = 0;
}
}
总结
通过学习发现,单线程会出现卡顿的情况,而多线程的方式不会产生卡顿。单线程是一个端口一次进行扫描,多线程则是以线程的方式每个线程进行一个端口的扫描,很明显可以看出多线程扫描要比单线程扫描用的时间更少。
还学会了如何通过 C# 的 TcpClient 类建立连接,并使用 NetworkStream 类传输数据。