网络编程: 顾名思义, 网络编程就是针对网络应用编写程序.
针对网路通信, ISO(International Organization for Standardization)国际标准化组织提出了标准的网路通信模型OSI七层模型, 而实际上这个七层模型被创建以来最大的作用还是在教育科研事业的理论教学中, 实际用于通信的是TCP/IP四层模型. 由于网络通信机制较复杂, 操作系统OS内置了许多种不同厂商网卡的驱动程序, 这些驱动程序配合网络适配器完成网络的绝大多数通信过程(传输层 -> 物理层), 针对我们程序员来说, 通常只要处理好应用层到传输层之间的信息流即可, 而Socket这个类被创建出来正是为了帮助程序员完成网路通信任务的. 见下图:
我本身是学网络的, 下图列举了一些常见协议的报文格式, 顺带复习一下:
Socket(套接字)用来在服务器和客户端之间传送数据, 无论是在客户端还是在服务器, 只要涉及到网络通信都需要使用Socket对象. 可以将Socket理解为封装了网络通信过程类, 我们通过Socket类的对象就能进行网络通信.
具体过程: 见下图
在自己做练习时, 有几个值得注意的地方, 具体见代码(练习代码, 质量不高, 见谅)
1. 在异步接收客户端请求时, 希望实现一接收到请求就弹出一个子窗体与客户端通信:
需要在主线程中, 调用this.Invoke(委托对象)创建子窗体的对象, 这样主线程便可以和子线程进行交互. 同时还要用Control.CheckForIllegalCrossThreadCalls = false来关闭非法的跨线程调用.
注意: a. Invoke方法的目的是在主线程中调用子线程的方法. 不难看出这里发生了方法传递, 很自然的想到这里需要一个委托对象(C#不允许直接传递方法指针). 因此, 我们要创建一个委托和一个产生子窗体的方法, 这可以通过Invoke方法的参数体现出来.
b. 通过构造函数在窗体间传值. 因为是子窗体与客户端通信, 因此子线程需要得到主线程中的Socket对象, 也就是窗体间传值, 我们可以通过子窗体的构造函数将主线程的socket对象的引用传递给子窗体, 这样子窗体便可以和客户端通信了.
c. 若要在主线程中, 操作或修改子窗体的控件, 那么需要在子窗体的资源文件中, 将相关控件的访问权限设置为public
2. 在异步的接收请求和接收消息的过程中, 当接受完请求或者接收完消息后, 必须再次调用BeginAccept或者BeginReceive方法, 以便接收下一个请求或下一条消息. 并且, 当我们需要传递的参数比较多时, 可以采用自定义一个类来传递多个参数.
3. 可以异常处理try{….} catch{} 来规避程序异常退出的错误, 只要catch{}中不作处理即可.
4. 通过try…catch也可以维护list_box等列表, 但可能遗漏某些情况. 建议的解决方案是通过Timer类的对象来维护list_box等列表.
5. 任务栏通知图标的使用
private void ChatForm_Load(object sender, EventArgs e)
{
//this.components = new System.ComponentModel.Container(); //创建容器对象
NotifyIcon icon = new NotifyIcon(); //创建任务栏图标
icon.Visible = true;
icon.Text = "任务图标示例";
icon.Icon = new Icon("c:\\wan.ico");
MenuItem showForm = new MenuItem(); //菜单项
showForm.Text = "显示";
showForm.Click +=new EventHandler(showForm_Click);
MenuItem showHello = new MenuItem();
showHello.Text = "显示Hello";
showHello.Click +=new EventHandler(showHello_Click);
MenuItem exit = new MenuItem();
exit.Text = "退出";
exit.Click +=new EventHandler(exit_Click);
icon.ContextMenu = new ContextMenu(new MenuItem[] { showForm,showHello,exit});
}
6. MD5散列算法的使用
public class Security
{
public static string Encrypt(string cleanString)
{
Byte[] clearBytes = new UnicodeEncoding().GetBytes(cleanString);
Byte[] hashedBytes = ((HashAlgorithm) CryptoConfig.CreateFromName("MD5")).ComputeHash(clearBytes);
return BitConverter.ToString(hashedBytes);
}
}
//示例代码:
//WinServer项目:
//ServerForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
namespace WinServer
{
public partial class ServerForm : Form
{
private static readonly int buffersize = 409600; //缓存的大小
Timer timer = new Timer();
List<Helper> list = new List<Helper>();
public ServerForm()
{
Control.CheckForIllegalCrossThreadCalls = false; //默认不允许垮线程传值, 别忘了写
InitializeComponent();
InitData();
}
private void InitData()
{
this.btn_stop.Enabled = false;
timer.Interval = 1000;
timer.Tick += new EventHandler(timer_Tick); //timer到期后的事件处理器
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
timer.Stop();
List<Helper> temp = new List<Helper>(); //这里必须借助temp, list在遍历自身的时候不能删除自己元素
foreach (Helper h in list)
{
if (!h.Worker.Connected)
{
temp.Add(h);
}
}
foreach(Helper h in temp)
{
h.ChatForm.Close();
this.lst_userList.Items.Remove(h.Worker.RemoteEndPoint.ToString());
h.Worker.Close();
list.Remove(h);
}
timer.Start();
}
private void ServerForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
DialogResult dr = MessageBox.Show("您确定要退出吗? ", "退出", MessageBoxButtons.YesNo);
if (dr == DialogResult.No)
{
e.Cancel = true;
}
}
}
private void btn_start_Click(object sender, EventArgs e)
{
this.btn_stop.Enabled = true;
//1. 创建Socket对象
Socket soc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2. 创建IPEndPoint对象
IPEndPoint ip = new IPEndPoint(IPAddress.Any, 5267);
//3. 绑定IPEndPoint到soc
soc.Bind(ip);
//4. 开始监听并提供服务
soc.Listen(5);
//5. 接受信息
//wait 1, accept connect request and create new socket object
soc.BeginAccept(new AsyncCallback(dealAccept), soc); //开启子线程, soc用来保持soc对象的引用
this.btn_start.Enabled = false;
this.btn_video.Enabled = false;
}
delegate ChatForm ChatFormHandler(Socket worker); //Invoke用的委托
private ChatForm createChatForm(Socket worker) //委托上绑定的方法, 主线程将通信对象Socket传到子窗体中
{
ChatForm chat = new ChatForm(worker); //可在子窗体的构造函数中添加通信对象
chat.Show(); //显示chatForm
return chat; //返回chat的引用到主线程
}
private void dealAccept(IAsyncResult ia) //子线程, ia.AsyncState是Object类型, 其实就是soc
{
Socket soc = ia.AsyncState as Socket;
Socket worker = soc.EndAccept(ia);
string identity = worker.RemoteEndPoint.ToString();
/*窗体名字的设置
byte[] namebuf = new byte[1024];
int c =worker.Receive(namebuf, SocketFlags.None);
string name = Encoding.GetEncoding("gb2312").GetString(namebuf);
string identity = name + worker.RemoteEndPoint.ToString();
*/
soc.BeginAccept(new AsyncCallback(dealAccept), soc); //有work处理后, soc又开始监听(类似公司前台) ---别忘了
this.lst_userList.Items.Add(identity);
//当客户端要求与服务器聊天时, 自动在服务器上弹出子窗体(子线程), 为使主线程能够控制子线程, 使用Invoke方法. (否则窗体紧在子线程dealAccept中执行后立即销毁)
ChatForm chat = this.Invoke(new ChatFormHandler(createChatForm), worker) as ChatForm; //invoke需要通过委托传递一个方法, parames为方法的参数
chat.Text = identity;
//chat.Text = name;
byte[] buffer = new byte[buffersize]; //异步接收, buffer要么当全局变量, 要么当参数传递. 这里, 选参数
//这里既要串worker(现在换成id), 又要传buffer, 可以做成个对象
Helper mi = new Helper(identity, buffer,worker);
mi.ChatForm = chat;
//mi.Name = name;
list.Add(mi);
//wait 2, accept message
worker.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(dealReceive), mi);
}
private void btn_stop_Click(object sender, EventArgs e)
{
foreach (Helper h in list)
{
if (h.Identity == this.lst_userList.SelectedItem.ToString())
{
this.lst_userList.Items.Remove(h.Worker.RemoteEndPoint.ToString());
h.Worker.Close();
list.Remove(h);
h.ChatForm.Close();
break;
}
}
}
private void dealReceive(IAsyncResult ia)
{
Helper help = ia.AsyncState as Helper;
Socket worker = help.Worker;
//help.ChatForm.Text = help.Name; //给窗体的名称赋值
try //通过try..catch捕获断开连接的错误, 来子窗口并清理资源; 也可以通过timer来定期维护列表
{
int c = worker.EndReceive(ia);
if (c > 0) //确定接受完数据了
{
//为了在Helper类中的ChatForm成员上获得listbox控件, 需要在ChatForm.Designer文件中将listbox设置为public的
help.ChatForm.lst_showMsg.Items.Add(worker.RemoteEndPoint.ToString() + " : " + Encoding.GetEncoding("gb2312").GetString(help.Buffer, 0, c));
}
//接受完信息后, 继续开始接收下一条消息 --别忘了
worker.BeginReceive(help.Buffer, 0, help.Buffer.Length, SocketFlags.None, new AsyncCallback(dealReceive), help);
}
catch
{
/*//也可以通过timer来定期维护列表(更常用的方法), 异常有可能会漏掉某些情况(如: 在try块里执行正常语句时, 客户突然关闭的情况)
help.ChatForm.Close();
this.lst_userList.Items.Remove(help.Worker.RemoteEndPoint.ToString());
help.Worker.Close();
*/
}
}
private void lst_userList_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
foreach (Helper h in list)
{
if (h.Identity == this.lst_userList.SelectedItem.ToString())
{
h.ChatForm.Show();
}
}
}
catch { }
}
private void btn_video_Click(object sender, EventArgs e)
{
this.btn_stop.Enabled = true;
//1. 创建Socket对象
Socket videosoc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2. 创建IPEndPoint对象
IPEndPoint ip = new IPEndPoint(IPAddress.Any, 5267);
//3. 绑定IPEndPoint到soc
videosoc.Bind(ip);
//4. 开始监听并提供服务
videosoc.Listen(5);
//5. 接受信息
//wait 1, accept connect request and create new socket object
videosoc.BeginAccept(new AsyncCallback(dealVideoAccept), videosoc); //开启子线程, soc用来保持soc对象的引用
this.btn_start.Enabled = false;
this.btn_stop.Enabled = false;
}
delegate ScreenView VideotFormHandler(Socket worker); //Invoke用的委托
private ScreenView createScreenView(Socket worker) //委托上绑定的方法, 主线程将通信对象Socket传到子窗体中
{
ScreenView video = new ScreenView(worker); //可在子窗体的构造函数中添加通信对象
video.Show(); //显示chatForm
return video; //返回chat的引用到主线程
}
private void dealVideoAccept(IAsyncResult ia) //子线程, ia.AsyncState是Object类型, 其实就是soc
{
Socket videosoc = ia.AsyncState as Socket;
Socket worker = videosoc.EndAccept(ia);
string identity = worker.RemoteEndPoint.ToString();
videosoc.BeginAccept(new AsyncCallback(dealVideoAccept), videosoc); //有work处理后, soc又开始监听(类似公司前台) ---别忘了
this.lst_userList.Items.Add(identity);
//当客户端要求与服务器聊天时, 自动在服务器上弹出子窗体(子线程), 为使主线程能够控制子线程, 使用Invoke方法. (否则窗体紧在子线程dealAccept中执行后立即销毁)
ScreenView video = this.Invoke(new VideotFormHandler(createScreenView), worker) as ScreenView; //invoke需要通过委托传递一个方法, parames为方法的参数
video.Text = identity;
//chat.Text = name;
byte[] buffer = new byte[buffersize]; //异步接收, buffer要么当全局变量, 要么当参数传递. 这里, 选参数
//这里既要串worker(现在换成id), 又要传buffer, 可以做成个对象
Helper mi = new Helper(identity, buffer, worker);
mi.ScreenView = video;
//mi.Name = name;
list.Add(mi);
//wait 2, accept message
worker.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(dealVideoReceive), mi);
}
private void dealVideoReceive(IAsyncResult ia)
{
Helper help = ia.AsyncState as Helper;
Socket worker = help.Worker;
//help.ChatForm.Text = help.Name; //给窗体的名称赋值
try //通过try..catch捕获断开连接的错误, 来子窗口并清理资源; 也可以通过timer来定期维护列表
{
int c = worker.EndReceive(ia);
if (c > 0) //确定接受完数据了
{
//通过Image的静态方法FromStream, 获得Image对象
help.ScreenView.BackgroundImage = Image.FromStream(new System.IO.MemoryStream(help.Buffer, 0, c));
}
//接受完信息后, 继续开始接收下一条消息 --别忘了
worker.BeginReceive(help.Buffer, 0, help.Buffer.Length, SocketFlags.None, new AsyncCallback(dealVideoReceive), help);
}
catch
{
}
}
}
public class Helper //通过helper类传递多个参数
{
private string _identity = null;
private byte[] _buffer = null;
private Socket _worker = null;
private ChatForm _chatform = null;
private ScreenView _screenview = null;
//private string _name = null;
public Helper(string identity, byte[] buffer, Socket worker)
{
this._identity = identity;
this._buffer = buffer;
this._worker = worker;
}
public string Identity
{
get
{
return this._identity;
}
set
{
this._identity = value;
}
}
//public string Name
//{
// get
// {
// return this._name;
// }
// set
// {
// this._name = value;
// }
//}
public byte[] Buffer
{
get
{
return this._buffer;
}
set
{
this._buffer = value;
}
}
public Socket Worker
{
get
{
return this._worker;
}
set
{
this._worker = value;
}
}
public ChatForm ChatForm
{
get
{
return this._chatform;
}
set
{
this._chatform = value;
}
}
public ScreenView ScreenView
{
get
{
return this._screenview;
}
set
{
this._screenview = value;
}
}
}
}
//ChatForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
namespace WinServer
{
public partial class ChatForm : Form
{
private Socket _worker;
public ChatForm(Socket worker)
{
InitializeComponent();
this._worker = worker;
this.Text = worker.RemoteEndPoint.ToString();
}
private void btn_sendMsg_Click(object sender, EventArgs e)
{
this._worker.Send(Encoding.GetEncoding("gb2312").GetBytes(this.txtSendMsg.Text));
this.lst_showMsg.Items.Add("你说: " + this.txtSendMsg.Text);
this.txtSendMsg.Text = "";
}
private void ChatForm_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
this.Hide();
}
}
}
//ScreenView.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
namespace WinServer
{
public partial class ScreenView : Form
{
private Socket soc;
public ScreenView(Socket worker)
{
InitializeComponent();
soc = worker;
}
}
}
//WinClient项目:
//ChatForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
namespace WinClient
{
public partial class ChatForm : Form
{
Timer timer = new Timer();
private static readonly int buffersize = 1024; //客户端只使用1个buffer即可
private static byte[] receivebuffer = new byte[buffersize];
Socket soc;
Socket videosoc;
Timer sendtimer = new Timer();
Timer intervaltimer = new Timer();
int count; //截屏次数
public ChatForm()
{
Control.CheckForIllegalCrossThreadCalls = false; //默认不允许垮线程传值, 别忘了写
InitializeComponent();
timer.Interval = 1000;
timer.Tick += new EventHandler(timer_Tick);
sendtimer.Interval = 1000 * int.Parse(System.Configuration.ConfigurationManager.AppSettings["sendTime"]);
sendtimer.Tick += new EventHandler(sendtimer_Tick);
intervaltimer.Interval = 1000 * int.Parse(System.Configuration.ConfigurationManager.AppSettings["intervaltime"]);
intervaltimer.Tick += new EventHandler(intervaltimer_Tick);
InitData();
}
void intervaltimer_Tick(object sender, EventArgs e)
{
//间隔计数完毕, 建立通信对象
intervaltimer.Stop();
//1. 创建socket对象
videosoc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2. 创建IPEndPoint
IPEndPoint ip = new IPEndPoint(IPAddress.Parse("192.168.0.67"), 5267);
//3. 连接
videosoc.Connect(ip);
count = 0;
sendtimer.Start(); //每两秒截一次屏
}
void sendtimer_Tick(object sender, EventArgs e)
{
sendtimer.Stop();
//使用Graphic对象画图(Graphic类的静态方法), Graphic对象需要借助Image类的子类对象获得
Image image = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
using(Graphics graphic = Graphics.FromImage(image))
{
//graphic将屏幕拷贝到image中
graphic.CopyFromScreen(0, 0, 0, 0, image.Size); //前两个参数是屏幕位置, 后两个参数是图片image上的位置
}
System.IO.MemoryStream ms = new System.IO.MemoryStream();
//image.Save(@"C:\1\" + DateTime.Now.Millisecond + ".jpg");
image.Save(ms,System.Drawing.Imaging.ImageFormat.Jpeg); //保存到image到字节数组中
byte[] picbuf = ms.ToArray();
try
{
videosoc.Send(picbuf);
}
catch { }
count++;
if (count > 5) //每次只截5张
{
intervaltimer.Start(); //继续间隔
}
else
{
sendtimer.Start();
}
}
void timer_Tick(object sender, EventArgs e)
{
timer.Stop();
if (!soc.Connected)
{
this.btn_sendMsg.Enabled = false;
this.lst_userList.Items.Remove(soc.RemoteEndPoint.ToString());
}
else
{
if (!this.lst_userList.Items.Contains(soc.RemoteEndPoint.ToString()))
{
this.lst_userList.Items.Add(soc.RemoteEndPoint.ToString());
};
}
timer.Start();
}
private void InitData()
{
this.btn_sendMsg.Enabled = false;
this.lbl_NN.Text = "未命名";
}
private void dealReceive(IAsyncResult ia)
{
soc = ia.AsyncState as Socket;
try
{
int count = soc.EndReceive(ia);
this.lst_showMsg.Items.Add(soc.RemoteEndPoint.ToString() + ":" + Environment.NewLine + Encoding.GetEncoding("gb2312").GetString(receivebuffer, 0, count));
this.lst_showMsg.Refresh();
//接收完消息后, 继续接收消息 --别忘了
soc.BeginReceive(receivebuffer, 0, receivebuffer.Length, SocketFlags.None, new AsyncCallback(dealReceive), soc);
}
catch { }
}
private void btn_sendMsg_Click(object sender, EventArgs e)
{
string msg = this.txtSendMsg.Text;
if (msg != null && msg.Trim().Length > 0)
{
//5. 发送信息, 不需要连接直接发送即可
soc.Send(Encoding.GetEncoding("gb2312").GetBytes(msg));
this.lst_showMsg.Items.Add("你说" + " : " + Environment.NewLine + msg);
}
this.txtSendMsg.Text = "";
}
private void ChatForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
DialogResult dr = MessageBox.Show("您确定要退出吗? ", "退出", MessageBoxButtons.YesNo);
if (dr == DialogResult.No)
{
e.Cancel = true;
}
else
{
e.Cancel = true;
this.Visible = false;
}
}
}
private void btn_chat_Click(object sender, EventArgs e)
{
this.btn_sendMsg.Enabled = true;
//1. 创建Socket对象
soc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2. 创建IPEndPoint对象(封装IP和Port)
IPEndPoint ip = new IPEndPoint(IPAddress.Parse("192.168.0.67"), 5267); //IPAddress的静态方法Parse将串转换为IP地址
try
{
//3. 连接服务器
soc.Connect(ip);
//将用户名通过socket发送到服务器
//soc.Send(Encoding.GetEncoding("gb2312").GetBytes(this.lbl_NN.Text));
//4. 启动即进入接收状态
soc.BeginReceive(receivebuffer, 0, receivebuffer.Length, SocketFlags.None, new AsyncCallback(dealReceive), soc);
}
catch { }
timer.Start();
}
private void btn_nickname_Click(object sender, EventArgs e)
{
RenameForm rename = new RenameForm(this); //将当前窗体的引用传到子窗体ReanmeForm中
rename.Show();
}
private void btn_video_Click(object sender, EventArgs e)
{
intervaltimer.Start(); //开启间隔timer
}
private void ChatForm_Load(object sender, EventArgs e)
{
//this.components = new System.ComponentModel.Container(); //创建容器对象
NotifyIcon icon = new NotifyIcon(); //创建任务栏图标
icon.Visible = true;
icon.Text = "任务图标示例";
icon.Icon = new Icon("c:\\wan.ico");
MenuItem showForm = new MenuItem(); //菜单项
showForm.Text = "显示";
showForm.Click +=new EventHandler(showForm_Click);
MenuItem showHello = new MenuItem();
showHello.Text = "显示Hello";
showHello.Click +=new EventHandler(showHello_Click);
MenuItem exit = new MenuItem();
exit.Text = "退出";
exit.Click +=new EventHandler(exit_Click);
icon.ContextMenu = new ContextMenu(new MenuItem[] { showForm,showHello,exit});
}
void exit_Click(object sender, EventArgs e)
{
Application.Exit();
}
void showForm_Click(object sender, EventArgs e)
{
this.Show();
}
public void showHello_Click(object sender, EventArgs e)
{
MessageBox.Show("Hello, World! ");
}
}
}
//renameForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WinClient
{
public partial class RenameForm : Form
{
private ChatForm _chatform;
public RenameForm(ChatForm chat)
{
InitializeComponent();
this._chatform = chat;
InitData();
}
private void InitData()
{
//需要在ChatForm的设计资源中, 将lbl_nickname置位public
this.txt_name.Text = this._chatform.lbl_NN.Text;
}
private void btn_savename_Click(object sender, EventArgs e)
{
this._chatform.lbl_NN.Text = this.txt_name.Text;
this.Close();
}
}
}
//App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="sendTime" value="2"/>
<add key="intervaltime" value="5"/>
</appSettings>
</configuration>