C#网络编程初探

我们知道C#和C++的差异之一,就是他本身没有类库,所使用的类库是.Net框架中的类库--.Net FrameWork SDK。在.Net FrameWork SDK中为网络编程提供了二个名称空间:"System.Net"和"System.Net.Sockets"。C#就是通过这二个名称空间中封装的类和方法实现网络通讯的。

首先我们解释一下在网络编程时候,经常遇到的几个概念:同步(synchronous)、异步(asynchronous)、阻塞(Block)和非阻塞(Unblock):

所谓同步方式,就是发送方发送数据包以后,不等接受方响应,就接着发送下一个数据包。异步方式就是当发送方发送一个数据包以后,一直等到接受方响应后,才接着发送下一个数据包。而阻塞套接字是指执行此套接字的网络调用时,直到调用成功才返回,否则此套节字就一直阻塞在网络调用上,比如调用StreamReader 类的Readline()方法读取网络缓冲区中的数据,如果调用的时候没有数据到达,那么此Readline()方法将一直挂在调用上,直到读到一些数据,此函数调用才返回;而非阻塞套接字是指在执行此套接字的网络调用时,不管是否执行成功,都立即返回。同样调用StreamReader 类的Readline()方法读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。在Windows网络通信软件开发中,最为常用的方法就是异步非阻塞套接字。平常所说的C/S(客户端/服务器)结构的软件采用的方式就是异步非阻塞模式的。

其实在用C#进行网络编程中,我们并不需要了解什么同步、异步、阻塞和非阻塞的原理和工作机制,因为在.Net FrameWrok SDK中已经已经把这些机制给封装好了。下面我们就用C#开一个具体的网络程序来说明一下问题。

一.本文中介绍的程序设计及运行环境

(1).微软视窗2000 服务器版
(2)..Net Framework SDK Beta 2以上版本

二.服务器端程序设计的关键步骤以及解决办法

在下面接受的程序中,我们采用的是异步阻塞的方式。

(1).首先要要在给定的端口上面创建一个"tcpListener"对象侦听网络上面的请求。当接收到连结请求后通过调用"tcpListener"对象的"AcceptSocket"方法产生一个用于处理接入连接请求的Socket的实例。下面是具体实现代码:

//创建一个tcpListener对象,此对象主要是对给定端口进行侦听
tcpListener = new TcpListener(1234);
//开始侦听
tcpListener.Start();
//返回可以用以处理连接的Socket实例
socketForClient = tcpListener.AcceptSocket();

(2).接受和发送客户端数据:

此时Socket实例已经产生,如果网络上有请求,在请求通过以后,Socket实例构造一个"NetworkStream"对象,"NetworkStream"对象为网络访问提供了基础数据流。我们通过名称空间"System.IO"中封装的二个类"StreamReader"和"StreamWriter"来实现对"NetworkStream"对象的访问。其中"StreamReader"类中的ReadLine ( )方法就是从"NetworkStream"对象中读取一行字符;"StreamWriter"类中的WriteLine ( )方法就是对"NetworkStream"对象中写入一行字符串。从而实现在网络上面传输字符串,下面是具体的实现代码:

try
{
    //如果返回值是"true",则产生的套节字已经接受来自远方的连接请求 
    if (socketForClient.Connected)
    {
        ListBox1.Items.Add("已经和客户端成功连接!");
        while (true)
        {
            //创建networkStream对象通过网络套节字来接受和发送数据 
            networkStream = new NetworkStream(socketForClient);
            //从当前数据流中读取一行字符,返回值是字符串 
            streamReader = new StreamReader(networkStream);
            string msg = streamReader.ReadLine();
            ListBox1.Items.Add("收到客户端信息:" + msg);
            streamWriter = new StreamWriter(networkStream);
            if (textBox1.Text != "")
            {
                ListBox1.Items.Add("往客户端反馈信息:" + textBox1.Text);
                //往当前的数据流中写入一行字符串 
                streamWriter.WriteLine(textBox1.Text);
                //刷新当前数据流中的数据 
                streamWriter.Flush();
            }
        }
    }
}
catch (Exception ey)
{
    MessageBox.Show(ey.ToString());
}

(3).最后别忘了要关闭所以流,停止侦听网络,关闭套节字,具体如下:

//关闭线程和流 
networkStream.Close();
streamReader.Close();
streamWriter.Close();
_thread1.Abort();
tcpListener.Stop();
socketForClient.Shutdown(SocketShutdown.Both);
socketForClient.Close();

三.C#网络编程服务器端程序的部分源代码(server.cs)

由于在此次程序中我们采用的结构是异步阻塞方式,所以在实际的程序中,为了不影响服务器端程序的运行速度,我们在程序中设计了一个线程,使得对网络请求侦听,接受和发送数据都在线程中处理,请在下面的代码中注意这一点,下面是server.cs的完整代码:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Net;
//导入程序中使用到的名字空间 
public class Form1 : Form
{
    private ListBox ListBox1;
    private Button button2;
    private Label label1;
    private TextBox textBox1;
    private Button button1;
    private Socket socketForClient;
    private NetworkStream networkStream;
    private TcpListener tcpListener;
    private StreamWriter streamWriter;
    private StreamReader streamReader;
    private Thread _thread1;
    private System.ComponentModel.Container components = null;
    public Form1()
    {
        InitializeComponent();
    }
    //清除程序中使用的各种资源 
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (components != null)
            {
                components.Dispose();
            }
        }
        base.Dispose(disposing);
    }
    private void InitializeComponent()
    {
        label1 = new Label();
        button2 = new Button();
        button1 = new Button();
        ListBox1 = new ListBox();
        textBox1 = new TextBox();
        SuspendLayout();
        label1.Location = new Point(8, 168);
        label1.Name = "label1";
        label1.Size = new Size(120, 23);
        label1.TabIndex = 3;
        label1.Text = "往客户端反馈信息:";
        //同样的方式设置其他控件,这里略去 

        this.Controls.Add(button1);
        this.Controls.Add(textBox1);
        this.Controls.Add(label1);
        this.Controls.Add(button2);
        this.Controls.Add(ListBox1);
        this.MaximizeBox = false;
        this.MinimizeBox = false;
        this.Name = "Form1";
        this.Text = "C#的网络编程服务器端!";
        this.Closed += new System.EventHandler(this.Form1_Closed);
        this.ResumeLayout(false);
    }
    
    private void Listen()
    {
        //创建一个tcpListener对象,此对象主要是对给定端口进行侦听 
        tcpListener = new TcpListener(1234);
        //开始侦听 
        tcpListener.Start();
        //返回可以用以处理连接的Socket实例 
        socketForClient = tcpListener.AcceptSocket();
        try
        {
            //如果返回值是"true",则产生的套节字已经接受来自远方的连接请求 
            if (socketForClient.Connected)
            {
                ListBox1.Items.Add("已经和客户端成功连接!");
                while (true)
                {
                    //创建networkStream对象通过网络套节字来接受和发送数据 
                    networkStream = new NetworkStream(socketForClient);
                    //从当前数据流中读取一行字符,返回值是字符串 
                    streamReader = new StreamReader(networkStream);
                    string msg = streamReader.ReadLine();
                    ListBox1.Items.Add("收到客户端信息:" + msg);
                    streamWriter = new StreamWriter(networkStream);
                    if (textBox1.Text != "")
                    {
                        ListBox1.Items.Add("往客户端反馈信息:" + textBox1.Text);
                        //往当前的数据流中写入一行字符串 
                        streamWriter.WriteLine(textBox1.Text);
                        //刷新当前数据流中的数据 
                        streamWriter.Flush();
                    }
                }
            }
        }
        catch (Exception ey)
        {
            MessageBox.Show(ey.ToString());
        }
    }
    static void Main()
    {
        Application.Run(new Form1());
    }

    private void button1_Click(object sender, System.EventArgs e)
    {
        ListBox1.Items.Add("服务已经启动!");
        _thread1 = new Thread(new ThreadStart(Listen));
        _thread1.Start();

    }

    private void button2_Click(object sender, System.EventArgs e)
    {
        //关闭线程和流 
        networkStream.Close();
        streamReader.Close();
        streamWriter.Close();
        _thread1.Abort();
        tcpListener.Stop();
        socketForClient.Shutdown(SocketShutdown.Both);
        socketForClient.Close();
    }
    private void Form1_Closed(object sender, System.EventArgs e)
    {
        //关闭线程和流 
        networkStream.Close();
        streamReader.Close();
        streamWriter.Close();
        _thread1.Abort();
        tcpListener.Stop();
        socketForClient.Shutdown(SocketShutdown.Both);
        socketForClient.Close();
    }
}
Contributors: FHL