用C#实现基于TCP协议的网络通讯

时间:2022-01-30 10:22:58

TCP协议是一个基本的网络协议,基本上所有的网络服务都是基于TCP协议的,如HTTP,FTP等等,所以要了解网络编程就必须了解基于TCP协议的编程。然而TCP协议是一个庞杂的体系,要彻底的弄清楚它的实现不是一天两天的功夫,所幸的是在.net framework环境下,我们不必要去追究TCP协议底层的实现,一样可以很方便的编写出基于TCP协议进行网络通讯的程序。

要进行基于TCP协议的网络通讯,首先必须建立同远程主机的连接,连接地址通常包括两部分——主机名和端口,如www.yesky.com:80中,www.yesky.com就是主机名,80指主机的80端口,当然,主机名也可以用IP地址代替。当连接建立之后,就可以使用这个连接去发送和接收数据包,TCP协议的作用就是保证这些数据包能到达终点并且能按照正确的顺序组装起来。

在.net framework的类库(Class Library)中,提供了两个用于TCP网络通讯的类,分别是TcpClient和TcpListener。由其英文意义显而易见,TcpClient类是基于TCP协议的客户端类,而TcpListener是服务器端,监听(Listen)客户端传来的连接请求。TcpClient类通过TCP协议与服务器进行通讯并获取信息,它的内部封装了一个Socket类的实例,这个Socket对象被用来使用TCP协议向服务器请求和获取数据。因为与远程主机的交互是以数据流的形式出现的,所以传输的数据可以使用.net framework中流处理技术读写。在我们下边的例子中,你可以看到使用NetworkStream类操作数据流的方法。

在下面的例子中,我们将建立一个时间服务器,包括服务器端程序和客户端程序。服务器端监听客户端的连接请求,建立连接以后向客户端发送当前的系统时间。

先运行服务器端程序,下面截图显示了服务器端程序运行的状况:



然后运行客户端程序,客户端首先发送连接请求到服务器端,服务器端回应后发送当前时间到客户端

发送完成后,服务器端继续等待下一次连接:



通过这个例子我们可以了解TcpClient类的基本用法,要使用这个类,必须使用System.Net.Socket命名空间,本例用到的三个命名空间如下:

using System;
using System.Net.Sockets;
using System.Text;//从字节数组中获取字符串时使用该命名空间中的类

首先讨论一下客户端程序,开始我们必须初始化一个TcpClient类的实例:

TcpClient client = new TcpClient(hostName, portNum);

然后使用TcpClient类的GetStream()方法获取数据流,并且用它初始化一个NetworkStream类的实例:

NetworkStream ns = client.GetStream();

注意,当使用主机名和端口号初始化TcpClient类的实例时,直到跟服务器建立了连接,这个实例才算真正建立,程序才能往下执行。如果因为网络不通,服务器不存在,服务器端口未开放等等原因而不能连接,程序将抛出异常并且中断执行。

建立数据流之后,我们可以使用NetworkStream类的Read()方法从流中读取数据,使用Write()方法向流中写入数据。读取数据时,首先应该建立一个缓冲区,具体的说,就是建立一个byte型的数组用来存放从流中读取的数据。Read()方法的原型描述如下:

public override int Read(in byte[] buffer,int offset,int size)

buffer是缓冲数组,offset是数据(字节流)在缓冲数组中存放的开始位置,size是读取的字节数目,返回值是读取的字节数。在本例中,简单地使用该方法来读取服务器反馈的信息:

byte[] bytes = new byte[1024];//建立缓冲区
int bytesRead = ns.Read(bytes, 0, bytes.Length);//读取字节流

然后显示到屏幕上:

Console.WriteLine(Encoding.ASCII.GetString(bytes,0,bytesRead));

最后不要忘记关闭连接:

client.Close();


下面是本例完整的程序清单:

using System;
using System.Net.Sockets;
using System.Text;

namespace TcpClientExample
{
public class TcpTimeClient
{
private const int portNum = 13;//服务器端口,可以随意修改
private const string hostName = "127.0.0.1";//服务器地址,127.0.0.1指本机

[STAThread]
static void Main(string[] args)
{
try
{
Console.Write("Try to connect to "+hostName+":"+portNum.ToString()+"/r/n");
TcpClient client = new TcpClient(hostName, portNum);
NetworkStream ns = client.GetStream();
byte[] bytes = new byte[1024];
int bytesRead = ns.Read(bytes, 0, bytes.Length);

Console.WriteLine(Encoding.ASCII.GetString(bytes,0,bytesRead));

client.Close();
Console.ReadLine();//由于是控制台程序,故为了清楚的看到结果,可以加上这句

}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}


上面这个例子清晰地演示了客户端程序的编写要点,下面我们讨论一下如何建立服务器程序。这个例子将使用TcpListener类,在13号端口监听,一旦有客户端连接,将立即向客户端发送当前服务器的时间信息。

TcpListener的关键在于AcceptTcpClient()方法,该方法将检测端口是否有未处理的连接请求,如果有未处理的连接请求,该方法将使服务器同客户端建立连接,并且返回一个TcpClient对象,通过这个对象的GetStream方法建立同客户端通讯的数据流。事实上,TcpListener类还提供一个更为灵活的方法AcceptSocket(),当然灵活的代价是复杂,对于比较简单的程序,AcceptTcpClient()已经足够用了。此外,TcpListener类提供Start()方法开始监听,提供Stop()方法停止监听。

首先我们使用端口初始化一个TcpListener实例,并且开始在13端口监听:

private const int portNum = 13;
TcpListener listener = new TcpListener(portNum);
listener.Start();//开始监听

如果有未处理的连接请求,使用AcceptTcpClient方法进行处理,并且获取数据流:

TcpClient client = listener.AcceptTcpClient();
NetworkStream ns = client.GetStream();

然后,获取本机时间,并保存在字节数组中,使用NetworkStream.Write()方法写入数据流,然后客户端就可以通过Read()方法从数据流中获取这段信息:

 byte[] byteTime = Encoding.ASCII.GetBytes(DateTime.Now.ToString());
ns.Write(byteTime, 0, byteTime.Length);
ns.Close();//不要忘记关闭数据流和连接
client.Close();

服务器端程序完整的程序清单如下:

using System;
using System.Net.Sockets;
using System.Text;

namespace TimeServer
{
class TimeServer
{
private const int portNum = 13;

[STAThread]
static void Main(string[] args)
{
bool done = false;
TcpListener listener = new TcpListener(portNum);
listener.Start();
while (!done)
{
Console.Write("Waiting for connection...");
TcpClient client = listener.AcceptTcpClient();

Console.WriteLine("Connection accepted.");
NetworkStream ns = client.GetStream();

byte[] byteTime = Encoding.ASCII.GetBytes(DateTime.Now.ToString());

try
{
ns.Write(byteTime, 0, byteTime.Length);
ns.Close();
client.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}

listener.Stop();
}
}
}

 

一个P2P实例:

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.Text ;
using System.Threading ;


namespace P2P
{
 /// <summary>
 /// Form1 的摘要说明。
 /// </summary>
 public class Form1 : System.Windows.Forms.Form
 {
  private System.Windows.Forms.Button button1;
  private System.Windows.Forms.Button button2;
  private System.Windows.Forms.Button button3;
  private System.Windows.Forms.Button button4;
  private System.Windows.Forms.TextBox textBox1;
  private System.Windows.Forms.TextBox textBox2;
  private System.Windows.Forms.TextBox textBox3;
  private System.Windows.Forms.TextBox textBox4;
  private System.Windows.Forms.Label label1;
  private System.Windows.Forms.Label label2;
  private System.Windows.Forms.Label label3;
  private System.Windows.Forms.Label label4;
  private System.Windows.Forms.Label label5;
  private System.Windows.Forms.StatusBar statusBar1;
  private System.Windows.Forms.ListBox listBox1;
  private System.Windows.Forms.ListBox listBox2;
  private System.Windows.Forms.Label label6;
  private System.Windows.Forms.StatusBarPanel statusBarPanel1;
  private System.Windows.Forms.StatusBarPanel statusBarPanel2;

 

  private Thread th ;
  //创建线程,用以侦听端口号,接收信息
  private TcpListener tlListen1 ;
  //用以侦听端口号
  private bool listenerRun = false ;
  //设定标示位,判断侦听状态
  private string HostName = string.Empty;
  private EndPoint tempRemoteEP ;

  private NetworkStream tcpStream ;
  //创建传送/接收的基本数据流实例
  private StreamWriter reqStreamW ;
  //用以实现向远程主机传送信息
  private TcpClient tcpc ;
  //用以创建对远程主机的连接
  private Socket skSocket ;
  private System.Windows.Forms.Button button5;
  private System.ComponentModel.IContainer components;

  public Form1()
  {
   //
   // Windows 窗体设计器支持所必需的
   //
   InitializeComponent();

   //
   // TODO: 在 InitializeComponent 调用后添加任何构造函数代码
   //
  }

  /// <summary>
  /// 清理所有正在使用的资源。
  /// </summary>
  protected override void Dispose( bool disposing )
  {
   //系统关闭时清除所有未释放资源
   try
   {
    listenerRun = false ;
    th.Abort ( ) ;
    th = null ;
    tlListen1.Stop ( ) ;
    skSocket.Close ( ) ;
    tcpc.Close ( ) ;
   }
   catch { }
   if ( disposing )
   {
    if ( components != null )
    {
     components.Dispose ( ) ;
    }
   }
   base.Dispose ( disposing ) ;

  }

  #region Windows 窗体设计器生成的代码
  /// <summary>
  /// 设计器支持所需的方法 - 不要使用代码编辑器修改
  /// 此方法的内容。
  /// </summary>
  private void InitializeComponent()
  {
   this.listBox1 = new System.Windows.Forms.ListBox();
   this.textBox1 = new System.Windows.Forms.TextBox();
   this.label3 = new System.Windows.Forms.Label();
   this.label2 = new System.Windows.Forms.Label();
   this.textBox3 = new System.Windows.Forms.TextBox();
   this.button1 = new System.Windows.Forms.Button();
   this.textBox2 = new System.Windows.Forms.TextBox();
   this.label1 = new System.Windows.Forms.Label();
   this.label4 = new System.Windows.Forms.Label();
   this.label5 = new System.Windows.Forms.Label();
   this.button2 = new System.Windows.Forms.Button();
   this.button3 = new System.Windows.Forms.Button();
   this.button4 = new System.Windows.Forms.Button();
   this.textBox4 = new System.Windows.Forms.TextBox();
   this.statusBar1 = new System.Windows.Forms.StatusBar();
   this.statusBarPanel1 = new System.Windows.Forms.StatusBarPanel();
   this.statusBarPanel2 = new System.Windows.Forms.StatusBarPanel();
   this.label6 = new System.Windows.Forms.Label();
   this.listBox2 = new System.Windows.Forms.ListBox();
   this.button5 = new System.Windows.Forms.Button();
   ((System.ComponentModel.ISupportInitialize)(this.statusBarPanel1)).BeginInit();
   ((System.ComponentModel.ISupportInitialize)(this.statusBarPanel2)).BeginInit();
   this.SuspendLayout();
   //
   // listBox1
   //
   this.listBox1.ItemHeight = 12;
   this.listBox1.Location = new System.Drawing.Point(122, 110);
   this.listBox1.Name = "listBox1";
   this.listBox1.Size = new System.Drawing.Size(212, 88);
   this.listBox1.TabIndex = 4;
   //
   // textBox1
   //
   this.textBox1.Location = new System.Drawing.Point(122, 18);
   this.textBox1.Name = "textBox1";
   this.textBox1.Size = new System.Drawing.Size(210, 21);
   this.textBox1.TabIndex = 1;
   this.textBox1.Text = "";
   //
   // label3
   //
   this.label3.Location = new System.Drawing.Point(220, 52);
   this.label3.Name = "label3";
   this.label3.Size = new System.Drawing.Size(66, 23);
   this.label3.TabIndex = 7;
   this.label3.Text = "本地端口:";
   //
   // label2
   //
   this.label2.Location = new System.Drawing.Point(38, 54);
   this.label2.Name = "label2";
   this.label2.Size = new System.Drawing.Size(80, 23);
   this.label2.TabIndex = 20;
   this.label2.Text = "远程端口号:";
   //
   // textBox3
   //
   this.textBox3.Location = new System.Drawing.Point(294, 50);
   this.textBox3.Name = "textBox3";
   this.textBox3.Size = new System.Drawing.Size(38, 21);
   this.textBox3.TabIndex = 3;
   this.textBox3.Text = "8889";
   //
   // button1
   //
   this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
   this.button1.Location = new System.Drawing.Point(348, 16);
   this.button1.Name = "button1";
   this.button1.Size = new System.Drawing.Size(92, 40);
   this.button1.TabIndex = 6;
   this.button1.Text = "连接远程机";
   this.button1.Click += new System.EventHandler(this.button1_Click);
   //
   // textBox2
   //
   this.textBox2.Location = new System.Drawing.Point(122, 50);
   this.textBox2.Name = "textBox2";
   this.textBox2.Size = new System.Drawing.Size(38, 21);
   this.textBox2.TabIndex = 2;
   this.textBox2.Text = "8888";
   //
   // label1
   //
   this.label1.Location = new System.Drawing.Point(38, 22);
   this.label1.Name = "label1";
   this.label1.Size = new System.Drawing.Size(80, 23);
   this.label1.TabIndex = 16;
   this.label1.Text = "远程IP地址:";
   //
   // label4
   //
   this.label4.Location = new System.Drawing.Point(50, 84);
   this.label4.Name = "label4";
   this.label4.Size = new System.Drawing.Size(66, 23);
   this.label4.TabIndex = 23;
   this.label4.Text = "发送信息:";
   //
   // label5
   //
   this.label5.Location = new System.Drawing.Point(36, 112);
   this.label5.Name = "label5";
   this.label5.Size = new System.Drawing.Size(80, 23);
   this.label5.TabIndex = 24;
   this.label5.Text = "发送的信息:";
   //
   // button2
   //
   this.button2.Enabled = false;
   this.button2.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
   this.button2.Location = new System.Drawing.Point(352, 192);
   this.button2.Name = "button2";
   this.button2.Size = new System.Drawing.Size(92, 40);
   this.button2.TabIndex = 7;
   this.button2.Text = "断开连接";
   this.button2.Click += new System.EventHandler(this.button2_Click);
   //
   // button3
   //
   this.button3.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
   this.button3.Location = new System.Drawing.Point(348, 74);
   this.button3.Name = "button3";
   this.button3.Size = new System.Drawing.Size(92, 40);
   this.button3.TabIndex = 8;
   this.button3.Text = "侦听端口";
   this.button3.Click += new System.EventHandler(this.button3_Click);
   //
   // button4
   //
   this.button4.Enabled = false;
   this.button4.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
   this.button4.Location = new System.Drawing.Point(350, 132);
   this.button4.Name = "button4";
   this.button4.Size = new System.Drawing.Size(92, 40);
   this.button4.TabIndex = 9;
   this.button4.Text = "发送信息";
   this.button4.Click += new System.EventHandler(this.button4_Click);
   //
   // textBox4
   //
   this.textBox4.Location = new System.Drawing.Point(122, 82);
   this.textBox4.Name = "textBox4";
   this.textBox4.Size = new System.Drawing.Size(212, 21);
   this.textBox4.TabIndex = 25;
   this.textBox4.Text = "";
   //
   // statusBar1
   //
   this.statusBar1.Location = new System.Drawing.Point(0, 301);
   this.statusBar1.Name = "statusBar1";
   this.statusBar1.Panels.AddRange(new System.Windows.Forms.StatusBarPanel[] {
                        this.statusBarPanel1,
                        this.statusBarPanel2});
   this.statusBar1.ShowPanels = true;
   this.statusBar1.Size = new System.Drawing.Size(456, 22);
   this.statusBar1.TabIndex = 26;
   //
   // statusBarPanel1
   //
   this.statusBarPanel1.Width = 200;
   //
   // statusBarPanel2
   //
   this.statusBarPanel2.Width = 230;
   //
   // label6
   //
   this.label6.Location = new System.Drawing.Point(48, 210);
   this.label6.Name = "label6";
   this.label6.Size = new System.Drawing.Size(66, 23);
   this.label6.TabIndex = 28;
   this.label6.Text = "接收信息:";
   //
   // listBox2
   //
   this.listBox2.ItemHeight = 12;
   this.listBox2.Location = new System.Drawing.Point(122, 206);
   this.listBox2.Name = "listBox2";
   this.listBox2.Size = new System.Drawing.Size(214, 88);
   this.listBox2.TabIndex = 27;
   //
   // button5
   //
   this.button5.Enabled = false;
   this.button5.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
   this.button5.Location = new System.Drawing.Point(352, 248);
   this.button5.Name = "button5";
   this.button5.Size = new System.Drawing.Size(92, 40);
   this.button5.TabIndex = 29;
   this.button5.Text = "停止侦听";
   this.button5.Click += new System.EventHandler(this.button5_Click);
   //
   // Form1
   //
   this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
   this.ClientSize = new System.Drawing.Size(456, 323);
   this.Controls.Add(this.button5);
   this.Controls.Add(this.label6);
   this.Controls.Add(this.listBox2);
   this.Controls.Add(this.statusBar1);
   this.Controls.Add(this.textBox4);
   this.Controls.Add(this.button4);
   this.Controls.Add(this.button3);
   this.Controls.Add(this.button2);
   this.Controls.Add(this.label5);
   this.Controls.Add(this.label4);
   this.Controls.Add(this.label2);
   this.Controls.Add(this.textBox3);
   this.Controls.Add(this.button1);
   this.Controls.Add(this.textBox2);
   this.Controls.Add(this.label1);
   this.Controls.Add(this.label3);
   this.Controls.Add(this.textBox1);
   this.Controls.Add(this.listBox1);
   this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
   this.MaximizeBox = false;
   this.Name = "Form1";
   this.Text = "Visual C#实现网络点对点通讯程序";
   this.Load += new System.EventHandler(this.Form1_Load);
   ((System.ComponentModel.ISupportInitialize)(this.statusBarPanel1)).EndInit();
   ((System.ComponentModel.ISupportInitialize)(this.statusBarPanel2)).EndInit();
   this.ResumeLayout(false);

  }

  #endregion

  /// <summary>
  /// 应用程序的主入口点。
  /// </summary>
  [STAThread]
  static void Main()
  {
   Application.Run(new Form1());
  }

  private void Listen ( )
  {
   tlListen1 = new TcpListener ( Int32.Parse(textBox3.Text));
   tlListen1.Start ( ) ;
   //侦听指定端口号
   statusBar1.Panels[1].Text = "正在监听..." ;
   //接受远程计算机的连接请求,并获得用以接收数据的Socket实例
   skSocket = tlListen1.AcceptSocket ( ) ;

   //获得远程计算机对应的网络远程终结点
   tempRemoteEP = skSocket.RemoteEndPoint ;
   IPEndPoint tempRemoteIP = ( IPEndPoint )tempRemoteEP ;
   IPHostEntry host = Dns.GetHostByAddress
    ( tempRemoteIP.Address ) ;
   HostName = host.HostName ;
   //根据获得的远程计算机对应的网络远程终结点获得远程计算机的名称
   bool listenerRun = true;
   while(listenerRun)
   {
    try
    {
     statusBar1.Panels[1].Text = "''" + HostName +"'' " +
      "远程计算机正确连接!" ;
     Byte[] stream = new Byte[80] ;
     //定义从远程计算机接收到数据存放的数据缓冲区
     string time = DateTime.Now.ToString ( ) ;
     //获得当前的时间
     int i = skSocket.ReceiveFrom ( stream,
      ref tempRemoteEP ) ;
     //接收数据,并存放到定义的缓冲区中
     string sMessage = System.Text.Encoding.UTF8.
      GetString ( stream ) ;
     //以指定的编码,从缓冲区中解析出内容
     listBox2.Items.Add(time+""+HostName+":");
     listBox2.Items.Add ( sMessage ) ;
     //显示接收到的数据
     tlListen1.Stop(); 
     Thread.Sleep(100);
     tlListen1.Start();
    }
    catch ( System.Security.SecurityException )
    {
     MessageBox.Show ( "防火墙安全错误!" ,"错误" ,
      MessageBoxButtons.OK , MessageBoxIcon.Exclamation) ;
    }
   }
  }


  private void Form1_Load(object sender, System.EventArgs e)
  {
  
  }

  private void button1_Click(object sender, System.EventArgs e)
  {
   try
   {
    tcpc = new TcpClient ( textBox1.Text ,
     Int32.Parse ( textBox2.Text ) ) ;
    //向远程计算机提出连接申请
    tcpStream = tcpc.GetStream ( ) ;
    //如果连接申请建立,则获得用以传送数据的数据流
    statusBar1.Panels[0].Text="成功连接远程计算机!" ;
    button2.Enabled = true ;
    button1.Enabled = false ;
    button4.Enabled = true ;
   }
   catch ( Exception )
   {
    statusBar1.Panels[0].Text = "目标计算机拒绝连接请求!" ;
   }
  }

  private void button3_Click(object sender, System.EventArgs e)
  {   
   button5.Enabled = true;
   if(!listenerRun)
   {
    th = new Thread ( new ThreadStart ( Listen ) ) ;
    //以Listen过程来初始化线程实例
    th.Start ( ) ;
    //启动此线程
   } 
   button3.Enabled = false;
  }

  private void button4_Click(object sender, System.EventArgs e)
  {
   try
   {
    string sMsg = textBox4.Text ;
    string MyName = Dns.GetHostName ( ) ;
    //以特定的编码往向数据流中写入数据,
    //默认为UTF8Encoding 的实例
    reqStreamW = new StreamWriter ( tcpStream ) ;
    //将字符串写入数据流中
    reqStreamW.Write ( sMsg ) ;
    //清理当前编写器的所有缓冲区,并使所有缓冲数据写入基础流
    reqStreamW.Flush ( ) ;
    string time = DateTime.Now.ToString ( ) ;
    //显示传送的数据和时间
    listBox1.Items.Add ( time +" " + MyName +":" ) ;
    listBox1.Items.Add (sMsg ) ;
    textBox4.Clear ( ) ;
   }
    //异常处理
   catch ( Exception )
   {
    statusBar1.Panels[0].Text = "无法发送信息到目标计算机!";
   }
  }

  private void button2_Click(object sender, System.EventArgs e)
  {
   //listenerRun = false ;
   tcpc.Close ( ) ;
   statusBar1.Panels[0].Text = "断开连接!" ;
   button1.Enabled = true ;
   button2.Enabled = false ;
   button4.Enabled = false ;  
  }

  

  private void button5_Click(object sender, System.EventArgs e)
  {
   button3.Enabled = true;
   tlListen1.Stop();
   listenerRun = false;
   button5.Enabled = false;
  }
 }
}