通信编程2-中等难度Socket实用篇(转载)
同一个东西---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(o b j e c t 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(o b j e c t 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(o b j e c t 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(o b j e c t sender, System.ComponentModel.CancelEventArgs e)
{
System.Environment.Exit (System.Environment .ExitCode );
}
}
}
这样就能实现2个窗体的互相通信,窗体关闭的private void Client_Closing(o b j e c t 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