我们知道C#和C++的差异之一
就是他本身没有类库
所使用的类库是
Net框架中的类库
Net FrameWork SDK
在
Net FrameWork SDK中为网络编程提供了二个名称空间
System
Net
和
System
Net
Sockets
C#就是通过这二个名称空间中封装的类和方法实现网络通讯的
首先我们解释一下在网络编程时候经常遇到的几个概念同步(synchronous)异步(asynchronous)阻塞(Block)和非阻塞(Unblock)
所谓同步方式就是发送方发送数据包以后不等接受方响应就接着发送下一个数据包异步方式就是当发送方发送一个数据包以后一直等到接受方响应后才接着发送下一个数据包而阻塞套接字是指执行此套接字的网络调用时直到调用成功才返回否则此套节字就一直阻塞在网络调用上比如调用StreamReader 类的Readlin ( )方法读取网络缓沖区中的数据如果调用的时候没有数据到达那么此Readlin ( )方法将一直挂在调用上直到读到一些数据此函数调用才返回而非阻塞套接字是指在执行此套接字的网络调用时不管是否执行成功都立即返回同样调用StreamReader 类的Readlin ( )方法读取网络缓沖区中数据不管是否读到数据都立即返回而不会一直挂在此函数调用上在Windows网络通信软件开发中最为常用的方法就是异步非阻塞套接字平常所说的C/S(客户端/服务器)结构的软件采用的方式就是异步非阻塞模式的
其实在用C#进行网络编程中我们并不需要了解什么同步异步阻塞和非阻塞的原理和工作机制因为在Net FrameWrok SDK中已经已经把这些机制给封装好了下面我们就用C#开一个具体的网络程序来说明一下问题
一.本文中介绍的程序设计及运行环境
()微软视窗 服务器版
()Net Framework SDK Beta 以上版本
二.服务器端程序设计的关键步骤以及解决办法
在下面接受的程序中我们采用的是异步阻塞的方式
()首先要要在给定的端口上面创建一个tcpListener对象侦听网络上面的请求当接收到连结请求后通过调用tcpListener对象的AcceptSocket方法产生一个用于处理接入连接请求的Socket的实例下面是具体实现代码
//创建一个tcpListener对象此对象主要是对给定端口进行侦听
tcpListener = new TcpListener ( ) ;
//开始侦听
tcpListenerStart ( ) ;
//返回可以用以处理连接的Socket实例
socketForClient = tcpListenerAcceptSocket ( ) ;
()接受和发送客户端数据
此时Socket实例已经产生如果网络上有请求在请求通过以后Socket实例构造一个NetworkStream对象NetworkStream对象为网络访问提供了基础数据流我们通过名称空间SystemIO中封装的二个类StreamReader和StreamWriter来实现对NetworkStream对象的访问其中StreamReader类中的ReadLine ( )方法就是从NetworkStream对象中读取一行字符StreamWriter类中的WriteLine ( )方法就是对NetworkStream对象中写入一行字符串从而实现在网络上面传输字符串下面是具体的实现代码
try
{
//如果返回值是true则产生的套节字已经接受来自远方的连接请求
if ( socketForClientConnected )
{
ListBoxItemsAdd ( 已经和客户端成功连接! ) ;
while ( true )
{
//创建networkStream对象通过网络套节字来接受和发送数据
networkStream = new NetworkStream ( socketForClient ) ;
//从当前数据流中读取一行字符返回值是字符串
streamReader = new StreamReader ( networkStream ) ;
string msg = streamReaderReadLine ( ) ;
ListBoxItemsAdd ( 收到客户端信息 + msg ) ;
streamWriter = new StreamWriter ( networkStream ) ;
if ( textBoxText != )
{
ListBoxItemsAdd ( 往客户端反馈信息 + textBoxText ) ;
//往当前的数据流中写入一行字符串
streamWriterWriteLine ( textBoxText ) ;
//刷新当前数据流中的数据
streamWriterFlush ( ) ;
}
}
}
}
catch ( Exception ey )
{
MessageBoxShow ( eyToString ( ) ) ;
}
()最后别忘了要关闭所以流停止侦听网络关闭套节字具体如下
//关闭线程和流
networkStreamClose ( ) ;
streamReaderClose ( ) ;
streamWriterClose ( ) ;
_threadAbort ( ) ;
tcpListenerStop ( ) ;
socketForClientShutdown ( SocketShutdownBoth ) ;
socketForClientClose ( ) ;
三.C#网络编程服务器端程序的部分源代码(servercs)
由于在此次程序中我们采用的结构是异步阻塞方式所以在实际的程序中为了不影响服务器端程序的运行速度我们在程序中设计了一个线程使得对网络请求侦听接受和发送数据都在线程中处理请在下面的代码中注意这一点下面是servercs的完整代码
using System ;
using SystemDrawing ;
using SystemCollections ;
using SystemComponentModel ;
using SystemWindowsForms ;
using SystemData ;
using SystemNetSockets ;
using SystemIO ;
using SystemThreading ;
using SystemNet ;
//导入程序中使用到的名字空间
public class Form : Form
{
private ListBox ListBox ;
private Button button ;
private Label label ;
private TextBox textBox ;
private Button button ;
private Socket socketForClient ;
private NetworkStream networkStream ;
private TcpListener tcpListener ;
private StreamWriter streamWriter ;
private StreamReader streamReader ;
private Thread _thread ;
private SystemComponentModelContainer components = null ;
public Form ( )
{
InitializeComponent ( ) ;
}
//清除程序中使用的各种资源
protected override void Dispose ( bool disposing )
{
if ( disposing )
{
if ( components != null )
{
componentsDispose ( ) ;
}
}
baseDispose ( disposing ) ;
}
private void InitializeComponent ( )
{
label = new Label ( ) ;
button = new Button ( ) ;
button = new Button ( ) ;
ListBox = new ListBox ( ) ;
textBox = new TextBox ( ) ;
SuspendLayout ( ) ;
labelLocation = new Point ( ) ;
labelName = label ;
labelSize = new Size ( ) ;
labelTabIndex = ;
labelText = 往客户端反馈信息 ;
//同样的方式设置其他控件这里略去
thisControlsAdd ( button ) ;
thisControlsAdd ( textBox ) ;
thisControlsAdd ( label ) ;
thisControlsAdd ( button ) ;
thisControlsAdd ( ListBox ) ;
thisMaximizeBox = false ;
thisMinimizeBox = false ;
thisName = Form ;
thisText = C#的网络编程服务器端! ;
thisClosed += new SystemEventHandler ( thisForm_Closed ) ;
thisResumeLayout ( false ) ;
}
private void Listen ( )
{
//创建一个tcpListener对象此对象主要是对给定端口进行侦听
tcpListener = new TcpListener ( ) ;
//开始侦听
tcpListenerStart ( ) ;
//返回可以用以处理连接的Socket实例
socketForClient = tcpListenerAcceptSocket ( ) ;
try
{
//如果返回值是true则产生的套节字已经接受来自远方的连接请求
if ( socketForClientConnected )
{
ListBoxItemsAdd ( 已经和客户端成功连接! ) ;
while ( true )
{
//创建networkStream对象通过网络套节字来接受和发送数据
networkStream = new NetworkStream ( socketForClient ) ;
//从当前数据流中读取一行字符返回值是字符串
streamReader = new StreamReader ( networkStream ) ;
string msg = streamReaderReadLine ( ) ;
ListBoxItemsAdd ( 收到客户端信息 + msg ) ;
streamWriter = new StreamWriter ( networkStream ) ;
if ( textBoxText != )
{
ListBoxItemsAdd ( 往客户端反馈信息 + textBoxText ) ;
//往当前的数据流中写入一行字符串
streamWriterWriteLine ( textBoxText ) ;
//刷新当前数据流中的数据
streamWriterFlush ( ) ;
}
}
}
}
catch ( Exception ey )
{
MessageBoxShow ( eyToString ( ) ) ;
}
}
static void Main ( )
{
ApplicationRun ( new Form ( ) ) ;
}
private void button_Click ( object sender SystemEventArgs e )
{
ListBoxItems Add ( 服务已经启动! ) ;
_thread = new Thread ( new ThreadStart ( Listen ) ) ;
_threadStart ( ) ;
}
private void button_Click ( object sender SystemEventArgs e )
{
//关闭线程和流
networkStreamClose ( ) ;
streamReaderClose ( ) ;
streamWriterClose ( ) ;
_threadAbort ( ) ;
tcpListenerStop ( ) ;
socketForClientShutdown ( SocketShutdownBoth ) ;
socketForClientClose ( ) ;
}
private void Form_Closed ( object sender SystemEventArgs e )
{
//关闭线程和流
networkStreamClose ( ) ;
streamReaderClose ( ) ;
streamWriterClose ( ) ;
_threadAbort ( ) ;
tcpListenerStop ( ) ;
socketForClientShutdown ( SocketShutdownBoth ) ;
socketForClientClose ( ) ;
}
}
四.客户端程序设计的关键步骤以及解决办法
()连接到服务器端的指定端口
我们采用的本地机既做服务器也做客户机你可以通过修改IP地址来确定自己想要连接的服务器我们在连接的时候采用了TcpClient类此类是在较高的抽象级别(高于Socket类)上面提供TCP服务下面代码就是连接到本地机(端口为)并获取响应流
//连接到服务器端口在这里是选用本地机器作为服务器你可以通过修改IP地址来改变服务器
try
{
myclient = new TcpClient ( localhost ) ;
}
catch
{
MessageBoxShow ( 没有连接到服务器! ) ;
return ;
}
//创建networkStream对象通过网络套节字来接受和发送数据
networkStream = myclientGetStream ( ) ;
streamReader = new StreamReader ( networkStream ) ;
streamWriter = new StreamWriter ( networkStream ) ;
()实现接受和发送数据
在接受和发送数据上面我们依然采用了NetworkStream类因为对他进行操作比较简单具体实现发送和接受还是通过命名空间SystemIO中StreamReader类ReadLine ( )方法和StreamWriter类的WriteLine ( )方法具体的实现方法如下
if ( textBoxText == )
{
MessageBoxShow ( 请确定文本框为非空! ) ;
textBoxFocus ( ) ;
return ;
}
try
{
string s ;
//往当前的数据流中写入一行字符串
streamWriterWriteLine ( textBoxText ) ;
//刷新当前数据流中的数据
streamWriterFlush ( ) ;
//从当前数据流中读取一行字符返回值是字符串
s = streamReaderReadLine ( ) ;
ListBoxItemsAdd ( 读取服务器端发送内容 + s ) ;
}
catch ( Exception ee )
{
MessageBoxShow ( 从服务器端读取数据出现错误类型为 + eeToString ( ) ) ;
}
()最后一步和服务器端是一样的就是要关闭程序中创建的流具体如下
streamReaderClose ( ) ;
streamWriterClose ( ) ;
networkStreamClose ( ) ;
五.客户端的部分代码
由于在客户端不需要侦听网络所以在调用上面没有程序阻塞情况所以在下面的代码中我们没有使用到线程这是和服务器端程序的一个区别的地方总结上面的这些关键步骤可以得到一个用C#网络编程 完整的客户端程序(clientcs)具体如下
using System ;
using SystemDrawing ;
using SystemCollections ;
using SystemComponentModel ;
using SystemWindowsForms ;
using SystemData ;
using SystemNetSockets ;
using SystemIO ;
using SystemThreading ;
//导入程序中使用到的名字空间
public class Form : Form
{
private ListBox ListBox ;
private Label label ;
private TextBox textBox ;
private Button button ;
private NetworkStream networkStream ;
private StreamReader streamReader ;
private StreamWriter streamWriter ;
TcpClient myclient ;
private Label label ;
private SystemComponentModelContainer components = null ;
public Form ( )
{
InitializeComponent ( ) ;
}
//清除程序中使用的各种资源
protected override void Dispose ( bool disposing )
{
if ( disposing )
{
if ( components != null )
{
componentsDispose ( ) ;
}
}
baseDispose ( disposing ) ;
}
private void InitializeComponent ( )
{
label = new Label ( ) ;
button = new Button ( ) ;
ListBox = new ListBox ( ) ;
textBox = new TextBox ( ) ;
label = new Label ( ) ;
SuspendLayout ( ) ;
labelLocation = new Point ( ) ;
labelName = label ;
labelSize = new Size ( ) ;
labelTabIndex = ;
labelText = 信息 ;
//同样方法设置其他控件
AutoScaleBaseSize = new Size ( ) ;
ClientSize = new Size ( ) ;
thisControlsAdd ( button ) ;
thisControlsAdd ( textBox ) ;
thisControlsAdd ( label ) ;
thisControlsAdd ( label ) ;
thisControlsAdd ( ListBox ) ;
thisMaximizeBox = false ;
thisMinimizeBox = false ;
thisName = Form ;
thisText = C#的网络编程客户器端! ;
thisClosed += new SystemEventHandler ( thisForm_Closed ) ;
thisResumeLayout ( false ) ;
//连接到服务器端口在这里是选用本地机器作为服务器你可以通过修改IP地址来改变服务器
try
{
myclient = new TcpClient ( localhost ) ;
}
catch
{
MessageBoxShow ( 没有连接到服务器! ) ;
return ;
}
//创建networkStream对象通过网络套节字来接受和发送数据
networkStream = myclientGetStream ( ) ;
streamReader = new StreamReader ( networkStream ) ;
streamWriter = new StreamWriter ( networkStream ) ;
}
static void Main ( )
{
ApplicationRun ( new Form ( ) ) ;
}
private void button_Click ( object sender SystemEventArgs e )
{
if ( textBoxText == )
{
MessageBoxShow ( 请确定文本框为非空! ) ;
textBoxFocus ( ) ;
return ;
}
try
{
string s ;
//往当前的数据流中写入一行字符串
streamWriterWriteLine ( textBoxText ) ;
//刷新当前数据流中的数据
streamWriterFlush ( ) ;
//从当前数据流中读取一行字符返回值是字符串
s = streamReaderReadLine ( ) ;
ListBoxItemsAdd ( 读取服务器端发送内容 + s ) ;
}
catch ( Exception ee )
{
MessageBoxShow ( 从服务器端读取数据出现错误类型为 + eeToString ( ) ) ;
}
}
private void Form_Closed ( object sender SystemEventArgs e )
{
streamReaderClose ( ) ;
streamWriterClose ( ) ;
networkStreamClose ( ) ;
}
}
六.总结
虽然在Net FrameWrok SDK 中只为网络编程提供了二个命名空间但这二个命名空间中的内容却是十分丰富的C#利用这二个命名空间既可以实现同步和异步也可以实现阻塞和非阻塞本文通过用C#编写一个网络上信息传输的程序展现了其丰富的内容由于篇幅所限更深更强大的功能还需要读者去实践探索