同学们,朋友们好,今天我要讲的比上回我讲的难一点,上回我讲的是在System.net里的高层类。今天我要和幻风幻云同学讲 同一个东西---Socket。这个有点难度,为了提高程序的趣味性,我想起个过年前看到的例子----有点类似QQ的东西,不过只能2个人 互相聊天,而且有建的有局域网的朋友更是值得一试。本篇难度一般,在下虽然现职教师,自认讲东西能力差,我会尽我最大的努力 去讲解以下的例子 本例分服务器端程序和客户端程序,对于服务器端,要用到以下内容: 1 TcpListener 建立一个服务器监听 TcpListener len=new TcpListener(port); 其参数是端口号,段口号用来标识程序。这样说吧,很多人玩过MU私服或者传奇私服吗?还记得以前MU私服没有登陆器的时候要改端口号 为44405吗??44405这个号就表明了MU这个游戏,正应为有这种标识记号,当你运行MU私服客户端的时候,不会连接到传奇的服务器上去 2 开始监听 len.Start(); 3挂起连接请求 Socket s=len.AcceptSocket (); 这个AcceptSocket ()成功的话,就返回一个套接字类型的东西 4建立工作通道 NetWorkStream ns=new NetworkStream (s);在此套接字上建立ns通道,注意ns是NetWorkStream类型的 对于客户端,要用到以下的东西 1 TcpClient 用法如下 TcpClient cli=new TcpClient();也就是实例化一个叫cli的TcpClient 注意的是必须实例化 2 Connect(终点,端口号); 建立了TcpClient后,要把他连接“绑定”到其对应的端口比如如果在服务器端的IP是192.168.0.4,port是4500的话 1建立IPAddress IPAddress ip=IPAddress.Parse ("192.168.0.4"); 2把它作为参数 cil.Connect(ip,4500);这个连接,是与我们将要做的服务器程序的连接 要注意2点,第1,要在以有的实例上建立连接,这里已有的是cil第2,很多程序号已经占用了端口号,比如我这里说的4500,可能你的 计算机上有别的程序是这个号,而同一个号不能在2个程序里用,你就要换个号,你也许说:我不知道哪些号已经被占用。是这样的,第1 1024号以前的最好别用,应为很多是系统程序用的,第2,如果4500号别的程序已经用了,你在调试完了,应用程序的时候,系统要提示你 说“一般,一个端口号只能用一次”,你遇到这样的提示再把号改过就是。 3闭合通道 刚才建立了个服务器端的通道,ns,但是这个通道是不闭合的,ns的终点在那里?没有指定 NetWorkStream output=cle.GetStream(); GetStream原意思是“得到流”,进一步来说,得到什么流?再看一下cle.GetStream(),我们知道了是从cle得到的,而cle又通过cle.Connect 和服务器连接,所以我们得到的结论是cle.GetStream()得到的流(我理解成“得到的通道”) 既然在前面建立了个通道ns,现在通过客户端的“得到通道”,那么现在工作通道就闭和了,你也许要问这个闭和通道的作用,呵呵 你可以回忆一下FileStream作用是什么,也大致知道这个NetWorkStream的作用了把。
好了,最后还要说下这个东西System.IO .BinaryWriter 用于向指定通道写操作,当然也有System.IO .BinaryReader哦 还要回顾一下这个东西System.Threading 能弄出多线程的东西。应为一般来说,一个窗体在Main()里运行的是什么?是Application.Run(new Form()); 对,是窗体,但是在窗体背后的服务器也在运行啊,这是个双重运行的程序(前台窗体操作,后台服务器运行操作)其实很多稍微大型点的程序都是多线程的 比如你按下Windows任务管理器,在进程栏看到的所有进程,就是现在你机子上的多个线程。
好了,现在开始编写程序,本来想画几个图帮助理解,由于只能上传15K的内容。图只好省略了 1先制作服务器端 服务器端和客户端都由一个窗体 ,2个richtextbox,一个按牛组成2个richtextbox分别是inrichtextbox和derichtextbox,比如在inrichtextbox输入文字 “你好”在服务器端和客户端的derichtextbox都会显示Server>>“你好”,同样,在客户端的inrichtextbox输入“你也好”,在服务器端和客户端的derichtextbox 都会显示Client>>“你也好”,代码如下 using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Net.Sockets ; using System.Net ; using System.IO ; using System.Threading ;
namespace 网络通信2A { /// <summary> /// Form1 的摘要说明。 /// </summary> public class Server : System.Windows.Forms.Form { private System.Windows.Forms.RichTextBox dirichTextBox1; private System.Windows.Forms.RichTextBox inrichTextBox2; private System.Windows.Forms.Button button1; private System.Net .Sockets .NetworkStream ns; private System.Threading .Thread th; private Socket s; private System.IO .BinaryReader re; private System.IO .BinaryWriter wr; private string rong; /// <summary> /// 必需的设计器变量。 /// </summary> private System.ComponentModel.Container components = null;
public Server() { // // Windows 窗体设计器支持所必需的 // InitializeComponent(); th=new Thread (new ThreadStart (RunServer)); th.Start ();
// // TOD 在 InitializeComponent 调用后添加任何构造函数代码 // }
/// <summary> /// 清理所有正在使用的资源。 /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); }
#region Windows 窗体设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 #endregion
/// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { Application.Run(new Server()); }
private void button1_Click(object sender, System.EventArgs e) { if(s!=null) wr.Write ("Server>>"+this.inrichTextBox2 .Text ); this.dirichTextBox1 .Text +="\r\nServer>>"+this.inrichTextBox2 .Text ; if(this.inrichTextBox2 .Text =="ClE") s.Close (); this.inrichTextBox2 .Clear (); } public void RunServer() { try { TcpListener len=new TcpListener (4700);//创建监听 len.Start ();//开始监听 while(true)//用无限循环,无限通过基于套接字的ns流传信息 { this.dirichTextBox1 .Text ="等待,没连接通哦!"; s=len.AcceptSocket ();//创建一个套接字,挂起连接请求 ns=new NetworkStream (s);//在此套接字上建立ns通道 wr=new BinaryWriter (ns);//wr用来为此通道的写服务 re=new BinaryReader (ns);//re为此通道的读服务 /*值得注意的是,wr和re和能与4700断口的程序对接,wr只能向对接的程序写,re只能向对接的读,这和FlieStream中的读写不同了,FileStream建立读写要定义个本机的位置,是死的位置,而这里wr和re对应的位置完全依赖ns和什么程序接通形成完整闭合通道*/ this.dirichTextBox1 .Text +="现在接通了,呵呵:)"; do { try { rong=re.ReadString (); this.dirichTextBox1 .Text +="\r\n"+rong; } catch(Exception) { break; } }while(rong!="Clent>>>CLE"); wr.Close ();re.Close ();ns.Close ();s.Close ();
}
} catch(Exception err) { MessageBox.Show (err.Message );} }
private void Server_Closing(object sender, System.ComponentModel.CancelEventArgs e) { System.Environment.Exit (System.Environment .ExitCode ); } } }
2客户缎 using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Net ; using System.Net .Sockets ; using System.IO ; using System.Threading ;
namespace 网络通信2B { /// <summary> /// Form1 的摘要说明。 /// </summary> public class Client : System.Windows.Forms.Form { private System.Windows.Forms.RichTextBox inrichTextBox1; private System.Windows.Forms.RichTextBox dirichTextBox2; private System.Windows.Forms.Button button1; private System.Windows.Forms.Button button2; private Thread th; private System.IO .BinaryWriter wr; private System.IO .BinaryReader re; private string mess; private System.Net .Sockets .NetworkStream output; /// <summary> /// 必需的设计器变量。 /// </summary> private System.ComponentModel.Container components = null;
public Client() { // // Windows 窗体设计器支持所必需的 // InitializeComponent(); th=new Thread (new ThreadStart (RunClient)); th.Start (); // TOD 在 InitializeComponent 调用后添加任何构造函数代码 // }
/// <summary> /// 清理所有正在使用的资源。 /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); }
#region Windows 窗体设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// </summary> // button1 // this.button1.Location = new System.Drawing.Point(32, 176); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(59, 23); this.button1.TabIndex = 2; this.button1.Text = "button1"; // // button2 // this.button2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.button2.Location = new System.Drawing.Point(280, 176); this.button2.Name = "button2"; this.button2.Size = new System.Drawing.Size(59, 23); this.button2.TabIndex = 3; this.button2.Text = "发送"; this.button2.Click += new System.EventHandler(this.button2_Click); // // Client // this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.ClientSize = new System.Drawing.Size(392, 349); this.Controls.Add(this.button2); this.Controls.Add(this.button1); this.Controls.Add(this.dirichTextBox2); this.Controls.Add(this.inrichTextBox1); this.Name = "Client"; this.Text = "Client"; this.Closing += new System.ComponentModel.CancelEventHandler(this.Client_Closing); this.ResumeLayout(false);
} #endregion
/// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { Application.Run(new Client()); }
private void button2_Click(object sender, System.EventArgs e) { wr.Write ("Client>>>"+this.inrichTextBox1 .Text ); this.dirichTextBox2 .Text +="\r\nClient>>>"+this.inrichTextBox1 .Text ; this.inrichTextBox1 .Clear (); } public void RunClient() { TcpClient cle; try { this.dirichTextBox2 .Text +="客户端立即开始........";
cle=new TcpClient (); file://IPAddress ip=IPAddress.Parse ("192.168.0.33");//与服务器连接 cle.Connect ("localhost",4700); output=cle.GetStream();//与服务器的通道联通,现在通道闭合了 wr=new BinaryWriter(output);//通过此通道进行读写操作 re=new BinaryReader(output); do { try { mess=re.ReadString ();//通过此通道的“读”设备读取服务器传递的信息,显示在“输出”文本筐里. this.dirichTextBox2 .Text +="\r\n"+mess; } catch(Exception) { System.Environment .Exit(1); } } while(mess!="Server>>CLE"); this.dirichTextBox2 .Text +="\r\n关闭了哈~"; wr.Close ();re.Close ();output.Close ();cle.Close (); Application.Exit(); } catch(Exception err){MessageBox.Show (err.Message );} }
private void Client_Closing(object sender, System.ComponentModel.CancelEventArgs e) { System.Environment.Exit (System.Environment .ExitCode ); } } }
这样就能实现2个窗体的互相通信,窗体关闭的private void Client_Closing(object sender, System.ComponentModel.CancelEventArgs e)必不可少,要不染你关闭了窗体 ,但是第2个线程依然在运行,这种情况就只能用 Windows任务管理器来结束进程了。
好了,我流下个问题让大家思考。现在比如有4台机子,我怎么让4个客户端程序之间1对1的通信??又怎么让他们集体通信? 提示:点播与广播,原理如下,大家最好别马上看,先自己想想
有4台机子,就要做4个客户端(比如叫A.B.C.D)和1个服务器端,4个客户端都cli.Connect()到这个服务器端,应此有4个output=cle.GetStream(); 他们比如为ooutput1=cle1.GetStream();utput2=cle2.GetStream();output3=cle3.GetStream();output4=cle4.GetStream();就是说要建立4个通道 A和B要通信,A先和服务器通信,服务器得到A信息再同过服务器与B的通道发送给B
全部代码超过15K以上是部分关键代码