http://download.linuxsir.org/fontconfig/linuxsir-fontconfig-3.4.bin

一、最小化窗口

点击“X”或“Alt+F4”时,最小化窗口,
如:
protected override void WndProc(ref Message m)
{
const int WM_SYSCOMMAND = 0×0112;
const int SC_CLOSE = 0xF060;
if (m.Msg == WM_SYSCOMMAND && (int) m.WParam == SC_CLOSE)
{
// User clicked close button
this.WindowState = FormWindowState.Minimized;
return;
}
base.WndProc(ref m);
}

二、如何让Foreach 循环运行的更快
foreach是一个对集合中的元素进行简单的枚举及处理的现成语句,用法如下例所示:
using System;
using System.Collections;
namespace LoopTest
{
class Class1
{
static void Main(string[] args)
{
// create an ArrayList of strings
ArrayList array = new ArrayList();
array.Add("Marty");
array.Add("Bill");
array.Add("George");
// print the value of every item
foreach (string item in array)
{
Console.WriteLine(item);
}
}
}
你可以将foreach语句用在每个实现了Ienumerable接口的集合里。如果想了解更多foreach的用法,你可以查看.NET Framework SDK文档中的C# Language Specification。

在编译的时候,C#编辑器会对每一个foreach 区域进行转换。IEnumerator enumerator = array.GetEnumerator();
try
{
string item;
while (enumerator.MoveNext())
{
item = (string) enumerator.Current;
Console.WriteLine(item);
}
}
finally
{
IDisposable d = enumerator as IDisposable;
if (d != null) d.Dispose();
}
这说明在后台,foreach的管理会给你的程序带来一些增加系统开销的额外代码。

三、将图片保存到一个XML文件
WinForm的资源文件中,将PictureBox的Image属性等非文字内容都转变成文本保存,这是通过序列化(Serialization)实现的,
例子://
using System.Runtime.Serialization.Formatters.Soap;
Stream stream = new FileStream("E:\\Image.xml",FileMode.Create,FileAccess.Write,FileShare.None);
SoapFormatter f = new SoapFormatter();
Image img = Image.FromFile("E:\\Image.bmp");
f.Serialize(stream,img);
stream.Close();

四、屏蔽CTRL-V
在WinForm中的TextBox控件没有办法屏蔽CTRL-V的剪贴板粘贴动作,如果需要一个输入框,但是不希望用户粘贴剪贴板的内容,可以改用RichTextBox控件,并且在KeyDown中屏蔽掉CTRL-V键,例子:

private void richTextBox1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if(e.Control && e.KeyCode==Keys.V)
e.Handled = true;
}

一、判断文件或文件夹是否存在
使用System.IO.File,要检查一个文件是否存在非常简单:
bool exist = System.IO.File.Exists(fileName);

如果需要判断目录(文件夹)是否存在,可以使用System.IO.Directory:
bool exist = System.IO.Directory.Exists(folderName);

二、使用delegate类型设计自定义事件
在C#编程中,除了Method和Property,任何Class都可以有自己的事件(Event)。定义和使用自定义事件的步骤如下:
(1)在Class之外定义一个delegate类型,用于确定事件程序的接口
(2)在Class内部,声明一个public event变量,类型为上一步骤定义的delegate类型
(3)在某个Method或者Property内部某处,触发事件
(4)Client程序中使用+=操作符指定事件处理程序

例子: // 定义Delegate类型,约束事件程序的参数
public delegate void MyEventHandler(object sender, long lineNumber) ;

public class DataImports
{
// 定义新事件NewLineRead
public event MyEventHandler NewLineRead ;

public void ImportData()
{
long i = 0 ; // 事件参数
while()
{
i++ ;
// 触发事件
if( NewLineRead != null ) NewLineRead(this, i);
//…
}
//…
}
//…
}

// 以下为Client代码

private void CallMethod()
{
// 声明Class变量,不需要WithEvents
private DataImports _da = null;
// 指定事件处理程序
_da.NewLineRead += new MyEventHandler(this.DA_EnterNewLine) ;
// 调用Class方法,途中会触发事件
_da.ImportData();
}
// 事件处理程序
private void DA_EnterNewLine(object sender, long lineNumber)
{
// …
}

三、IP与主机名解析
使用System.Net可以实现与Ping命令行类似的IP解析功能,例如将主机名解析为IP或者反过来: private string GetHostNameByIP(string ipAddress)
{
IPHostEntry hostInfo = Dns.GetHostByAddress(ipAddress);
return hostInfo.HostName;
}
private string GetIPByHostName(string hostName)
{
System.Net.IPHostEntry hostInfo = Dns.GetHostByName(hostName);
return hostInfo.AddressList[0].ToString();
}

In some scenarios, you may wish to ensure that a user can run only one instance of your application at a time. Besides ensuring that only a single instance of your application is running, you may also want to bring the instance already running to the front and restore it, if it is minimized.
First, to ensure that only one instance of your application is running at a time, the best method I've found is to create a mutex that is held by the operating system (thanks to Michael Covington). This will put a request to the operating system that a mutex be created if one does not already exist. Only one mutex can ever be created at a time, so if you request a new one and it cannot be created, you can safely assume that your application is already running.

using System.Threading
using System.Runtime.InteropServices;

public class Form1 : Form
{
[STAThread]
static void Main()
{
bool createdNew;

Mutex m = new Mutex(true, "YourAppName", out createdNew);

if (! createdNew)
{
// app is already running…
MessageBox.Show("Only one instance of this application is allowed at a time.");
return;
}

Application.Run(new Form1());

// keep the mutex reference alive until the normal termination of the program
GC.KeepAlive(m);
}
}

The above code will work for the vast majority of your needs. It will also run under scenarios where your code is executing with less than FullTrust permissions (see Code Access Security in MSDN for further information).

If your application can run with Full Trust permissions, we can take this a step further and find the window of the application instnace already running and bring it to the front for the user:

public class Form1 : Form
{
[STAThread]
static void Main()
{
bool createdNew;

System.Threading.Mutex m = new System.Threading.Mutex(true, "YourAppName", out createdNew);

if (! createdNew)
{
// see if we can find the other app and Bring it to front
IntPtr hWnd = FindWindow("WindowsForms10.Window.8.app3", "YourAppName");

if(hWnd != IntPtr.Zero)
{
Form1.WINDOWPLACEMENT placement = new Form1.WINDOWPLACEMENT();
placement.length = Marshal.SizeOf(placement);

GetWindowPlacement(hWnd, ref placement);

if(placement.showCmd != SW_NORMAL)
{
placement.showCmd = SW_RESTORE;

SetWindowPlacement(hWnd, ref placement);
SetForegroundWindow(hWnd);
}
}

return;
}

Application.Run(new Form1());

// keep the mutex reference alive until the normal termination of the program
GC.KeepAlive(m);
}

private const int SW_NORMAL = 1; // see WinUser.h for definitions
private const int SW_RESTORE = 9;

[DllImport("User32",EntryPoint="FindWindow")]
static extern IntPtr FindWindow(string className, string windowName);

[DllImport("User32",EntryPoint="SendMessage")]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

[DllImport("User32",EntryPoint="SetForegroundWindow")]
private static extern bool SetForegroundWindow(IntPtr hWnd);

[DllImport("User32",EntryPoint="SetWindowPlacement")]
private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);

[DllImport("User32",EntryPoint="GetWindowPlacement")]
private static extern bool GetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);

private struct POINTAPI
{
public int x;
public int y;
}

private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}

private struct WINDOWPLACEMENT
{
public int length;
public int flags;
public int showCmd;
public POINTAPI ptMinPosition;
public POINTAPI ptMaxPosition;
public RECT rcNormalPosition;
}
}

As you can see, with minimal effort, you can easily add a polished touch to your application. This might even help you avoid some extra legwork in ensuring that there are no issues with running multiple instances of your app at the same time that you might have to address.

For more information about the Platform Invoke mechanisms to call Win32 API functions, I recommend that you check out .NET Framework Solutions: In Search of the Lost Win32 API by John Mueller and Charles Petzold's seminal classic Programming Windows.

Until Longhorn comes out and more of the Windows platform becomes managed, platform invokes and interop will remain a key technology to understand and use to your advantage to fill the gaps left by the Windows Forms framework.

UPDATE:
Visual Studio .NET 2005 (aka. “Whidbey”) will let you specify Single Instance behavior for Visual Basic .NET projects through the Project Properties page. Curious as to why this option is available for VB projects and not for C# projects, as this seems to instruct the runtime or some framework library not to load more than one instance.

For performance test, it is very important to measure code execution time. Without measurement, there is no way to tell if we meet performance goal.

System.Environment.TickCount is not suitable for high resolution timing. Its resolution cannot be less than 500 milliseconds.

System.Datetime.Now returns the current time of type DateTime. With start datetime and end datetime, we can get the interval as a value of TimeSpan by (end – start ) . TimeSpan.TotalMilliseconds or TimeSpan.Ticks may be used to read interval. From MSDN, the resolution of System.Datetime.Now depends on the system timer.

System Approximate Resolution

Windows NT 3.5 and later 10 milliseconds

Windows 98 55 milliseconds

So it is better but not high resolution at all.

In .NET framework v1 and v1.1, we have to use P/Invoke to get high resolution reading. The class below is commonly used in performance test measurement. It is querying hardware to get high resolution performance counter. For more information (including what happens if the hardware does not support high resolution performance counter) please check MSDN for QueryPerformanceCounter and QueryPerformanceFrequency.

public class HighResolutionTimer

{

private long start;

private long stop;

private long frequency;

public HighResolutionTimer()

{

QueryPerformanceFrequency (ref frequency);

}

public void Start ()

{

QueryPerformanceCounter (ref start);

}

public void Stop ()

{

QueryPerformanceCounter (ref stop);

}

public float ElapsedTime

{

get{

float elapsed = (((float)(stop – start)) / ((float) frequency));

return elapsed;

}

}

[System.Runtime.InteropServices.DllImport("KERNEL32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]

private static extern bool QueryPerformanceCounter( [In, Out] ref long performanceCount);

[System.Runtime.InteropServices.DllImport("KERNEL32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]

private static extern bool QueryPerformanceFrequency( [In, Out] ref long frequency);

}

To illustrate the use of this class, check the code below.

HighResolutionTimer timer = new HighResolutionTimer();

timer.Start();

//Perf Test

timer.Stop();

Console.WriteLine(timer.ElapsedTime);

(This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm)

作者:孙展波

来源: http://blogs.gotdotnet.com/ZHANBOS

http://smsinter.sina.com.cn/ws/smswebservice0101.wsdl,这是一个WSDL文件格式,可以直接在您的VS.NET环境中直接添加Web引用,把该地址输入即可。

该Web Service就只有一个方法,即string sendXml(carrier,userid,password,mobilenumber,content,msgtype)。各个参数全部为string类型,其含义基本如下(可能不正确)。

carrier:运营商名称,这里面可以随便输,不过似乎没有任何显示,不知道里面有没有其它奥秘。
userid:您在新浪无线上注册的手机ID,即http://sms.sina.com.cn。
password:您在新浪无线上注册手机时所使用的密码。
mobilenumber:对方的手机号码;
content:发送短消息的内容;
msgtype:发送短消息的类型,我估计支持彩信,不过我目前仅使用文本短信方式,似乎随便输什么都可以,我使用的是“Text”。

示例如下:
Sina.SMSWS ws = new Sina.SMSWS();
string result = ws.sendXml("Sina",textBox1.Text,textBox2.Text,textBox3.Text,textBox4.Text,"new");

资费标准请参看新浪无线网站上的相关说明,应该是一条一角钱,不过也或者是一条两角线。由于其后台可能使用了消息队列机制,在繁忙的时候,可能会有几秒钟延迟。

这里是几个主要非英文语系字符范围(google上找到的):
2E80~33FFh:中日韩符号区。收容康熙字典部首、中日韩辅助部首、注音符号、日本假名、韩文音符,中日韩的符号、标点、带圈或带括符文数字、月份,以及日本的假名组合、单位、年号、月份、日期、时间等。
3400~4DFFh:中日韩认同表意文字扩充A区,总计收容6,582个中日韩汉字。
4E00~9FFFh:中日韩认同表意文字区,总计收容20,902个中日韩汉字。
A000~A4FFh:彝族文字区,收容中国南方彝族文字和字根。
AC00~D7FFh:韩文拼音组合字区,收容以韩文音符拼成的文字。
F900~FAFFh:中日韩兼容表意文字区,总计收容302个中日韩汉字。
FB00~FFFDh:文字表现形式区,收容组合拉丁文字、希伯来文、阿拉伯文、中日韩直式标点、小符号、半角符号、全角符号等。
比如需要匹配所有中日韩非符号字符,那么正则表达式应该是^[\u3400-\u9FFF]+$
理论上没错, 可是我到msn.co.ko随便复制了个韩文下来, 发现根本不对, 诡异
再到msn.co.jp复制了个’お’, 也不得行..
然后把范围扩大到^[\u2E80-\u9FFF]+$, 这样倒是都通过了, 这个应该就是匹配中日韩文字的正则表达式了, 包括我們臺灣省還在盲目使用的繁體中文
而关于中文的正则表达式, 应该是^[\u4E00-\u9FFF]+$, 和论坛里常被人提起的^[\u4E00-\u9FA5]+$很接近
需要注意的是论坛里说的^[\u4E00-\u9FA5]+$这是专门用于匹配简体中文的正则表达式, 实际上繁体字也在里面, 我用测试器测试了下’中華人民共和國’, 也通过了, 当然, ^[\u4E00-\u9FFF]+$也是一样的结果

1、添加了一个Windows窗体Form1,在Form1中添加了一个文本框textBox1
2、在textBox1的KeyDown()事件中加入了以下代码:
private void textBox1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if ( e.KeyValue < 48 && e.KeyValue >57 ) //如果输入的字符是从 ‘0’ 到 ‘9’
{
//什么都不做
}
else
{
e.Handled=true; //如果输入的是非数字字符,则提前将这个事件结束掉,而不添加
MessageBox.Show( e.Handled.ToString() );
}
}
没写完,待续…
MSDN上的例子http://msdn.microsoft.com/library/chs/default.asp?url=/library/CHS/cpref/html/frlrfsystemwindowsformscontrolclasskeydowntopic.asp

[C#]
// Boolean flag used to determine when a character other than a number is entered.
private bool nonNumberEntered = false;

// Handle the KeyDown event to determine the type of character entered into the control.
private void textBox1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
// Initialize the flag to false.
nonNumberEntered = false;

// Determine whether the keystroke is a number from the top of the keyboard.
if (e.KeyCode < Keys.D0 || e.KeyCode > Keys.D9)
{
// Determine whether the keystroke is a number from the keypad.
if (e.KeyCode < Keys.NumPad0 || e.KeyCode > Keys.NumPad9)
{
// Determine whether the keystroke is a backspace.
if(e.KeyCode != Keys.Back)
{
// A non-numerical keystroke was pressed.
// Set the flag to true and evaluate in KeyPress event.
nonNumberEntered = true;
}
}
}
}

// This event occurs after the KeyDown event and can be used to prevent
// characters from entering the control.
private void textBox1_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
{
// Check for the flag being set in the KeyDown event.
if (nonNumberEntered == true)
{
// Stop the character from being entered into the control since it is non-numerical.
e.Handled = true;
}
}

using System;
using System.Text.RegularExpressions;
namespace bobomousecom.crm
{
///
/// Regexlib 的摘要说明。
///
public class Regexlib
{
public Regexlib()
{
//
// TODO: 在此处添加构造函数逻辑
//
}

//搜索输入字符串并返回所有 href=“…”值
string DumpHrefs(String inputString)
{
Regex r;
Match m;
r = new Regex(“href\\s*=\\s*(?:\”(?<1>[^\"]*)\”|(?<1>\\S+))”,
RegexOptions.IgnoreCase|RegexOptions.Compiled);
for (m = r.Match(inputString); m.Success; m = m.NextMatch())
{
return(“Found href ” + m.Groups[1]);
}
}

//验证Email地址
bool IsValidEmail(string strIn)
{
// Return true if strIn is in valid e-mail format.
return Regex.IsMatch(strIn, @”^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$”);
}

//dd-mm-yy 的日期形式代替 mm/dd/yy 的日期形式。
string MDYToDMY(String input)
{
return Regex.Replace(input,”\\b(?\\d{1,2})/(?\\d{1,2})/(?\\d{2,4})\\b”,”${day}-${month}-${year}”);
}

//验证是否为小数
bool IsValidDecimal(string strIn)
{

return Regex.IsMatch(strIn,@”[0].\d{1,2}|[1]“);
}

//验证是否为电话号码
bool IsValidTel(string strIn)
{
return Regex.IsMatch(strIn,@”(\d+-)?(\d{4}-?\d{7}|\d{3}-?\d{8}|^\d{7,8})(-\d+)?”);
}

//验证年月日
bool IsValidDate(string strIn)
{
return Regex.IsMatch(strIn,@”^2\d{3}-(?:0?[1-9]|1[0-2])-(?:0?[1-9]|[1-2]\d|3[0-1])(?:0?[1-9]|1\d|2[0-3]):(?:0?[1-9]|[1-5]\d):(?:0?[1-9]|[1-5]\d)$”);
}

//验证后缀名
bool IsValidPostfix(string strIn)
{
return Regex.IsMatch(strIn,@”\.(?i:gif|jpg)$”);
}

//验证字符是否在4至12之间
bool IsValidByte(string strIn)
{
return Regex.IsMatch(strIn,@”^[a-z]{4,12}$”);
}

//验证IP
bool IsValidIp(string strIn)
{
return Regex.IsMatch(strIn,@”^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$”);
}
}
}

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);
n
s.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();
}
}
}

把上面两段程序分别编译运行,OK,我们已经用C#实现了基于TCP协议的网络通讯,怎么样?很简单吧!

使用上面介绍的基本方法,我们可以很容易的编写出一些很有用的程序,如FTP,电子邮件收发,点对点即时通讯等等,你甚至可以自己编制一个QQ来!

C#是微软随着VS.net新推出的一门语言。它作为一门新兴的语言,有着C++的强健,又有着VB等的RAD特性。而且,微软推出C#主要的目的是为了对抗Sun公司的Java。大家都知道Java语言的强大功能,尤其在网络编程方面。于是,C#在网络编程方面也自然不甘落后于人。本文就向大家介绍一下C#下实现套接字(Sockets)编程的一些基本知识,以期能使大家对此有个大致了解。首先,我向大家介绍一下套接字的概念。

套接字基本概念:
套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。可以将套接字看作不同主机间的进程进行双向通信的端点,它构成了单个主机内及整个网络间的编程界面。套接字存在于通信域中,通信域是为了处理一般的线程通过套接字通信而引进的一种抽象概念。套接字通常和同一个域中的套接字交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序)。各种进程使用这个相同的域互相之间用Internet协议簇来进行通信。
套接字可以根据通信性质分类,这种性质对于用户是可见的。应用程序一般仅在同一类的套接字间进行通信。不过只要底层的通信协议允许,不同类型的套接字间也照样可以通信。套接字有两种不同的类型:流套接字和数据报套接字。

套接字工作原理:
要通过互联网进行通信,你至少需要一对套接字,其中一个运行于客户机端,我们称之为ClientSocket,另一个运行于服务器端,我们称之为ServerSocket。
根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
所谓服务器监听,是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
所谓客户端请求,是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
所谓连接确认,是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

C#中的套接字编程实例:
通过向大家简单的介绍套接字的基本概念和实现套接字编程的基本原理,我想大家对套接字编程已有了初步的了解。不过,上面介绍的仅仅是基本概念和原理,要真正运用还是需要一定的工作的。对基本概念和原理的真正理解的最好方法莫过于自己动手做一个实例,下面我就向大家介绍一个很好的用C#实现套接字编程的实例――聊天室程序。
本程序是基于C/S(服务器/客户端)构架的,程序包含一个服务器端的应用程序和一个客户端的应用程序。首先,在服务器上运行服务器端的应用程序,该程序一运行就开始服务器监听。然后,在客户机上就可以打开客户端的应用程序。程序打开后可以与服务器端应用程序进行连接,即进行客户端请求。在连接确认后,客户端用户可以和其他的客户端用户进行聊天。客户端人数没有限制,同时还支持“悄悄话”聊天模式,支持聊天记录。所以这是一个学习套接字编程的相当不错的例子。而且,程序中为了处理每个客户端的信息还用到了多线程机制。在每个客户端与服务器端连接成功后,它们之间就建立一个线程。这样运用了多线程之后,客户端之间就不会相互影响,即使其中一个出了错误也不会影响到另一个。

下面,我就向大家具体介绍该实例:
服务器端程序:
1. 打开VS.net,新建一个C#的模板为“Windows 应用程序”的项目,不妨命名为“ChatServer”。
2. 布置界面。只需在界面上添加一个ListBox控件即可,该控件主要用于显示客户端的用户的一些信息的。图象如下:
3. 服务器端程序的代码编写。
对于服务器端,主要的作用是监听客户端的连接请求并确认其请求。程序一开始便打开一个StartListening()线程。
private void StartListening()
{
listener = new TcpListener(listenport);
listener.Start();
while (true)
{
try
{
Socket s = listener.AcceptSocket();
clientsocket = s;
clientservice = new Thread(new ThreadStart(ServiceClient));
clientservice.Start();
}
catch(Exception e)
{
Console.WriteLine(e.ToString() );
}
}
}

该线程是一直处于运行状态的。当服务器端接收到一个来自客户端的连接请求后,它就打开一个ServiceClient()线程来服务客户端。当一个连接被建立后,每个客户端就被赋予一个属于它自己的套接字。同时,一个Client类的对象被建立。该对象包含了客户端的一些相关信息,该信息被保存在一个数组列表中。Client类如下(也可参见源代码中的Client.cs文件):
using System;
using System.Threading;
namespace ChatServer
{
using System.Net.Sockets;
using System.Net;

///
/// Client 的摘要说明。
///
public class Client
{
private Thread clthread;
private EndPoint endpoint;
private string name;
private Socket sock;
public Client(string _name, EndPoint _endpoint, Thread _thread, Socket _sock)
{
// TODO: 在此处添加构造函数逻辑
clthread = _thread;
endpoint = _endpoint;
name = _name;
sock = _sock;
}
public override string ToString()
{
return endpoint.ToString()+ " : " + name;
}

public Thread CLThread
{
get{return clthread;}
set{clthread = value;}
}
public EndPoint Host
{
get{return endpoint;}
set{endpoint = value;}
}
public string Name
{
get{return name;}
set{name = value;}
}

public Socket Sock
{
get{return sock;}
set{sock = value;}
}
}
}
程序的主体部分应是ServiceClient()函数。该函数是一个独立的线程,其主要部分是一个while循环。在循环体内,程序处理各种客户端命令。服务器端接收来自客户端的以ASCII码给出的字符串,其中包含了一个“|”形式的分隔符。字符串中“|”以前的部分就是具体的命令,包括CONN、CHAT、PRIV、GONE四种类型。CONN命令建立一个新的客户端连接,将现有的用户列表发送给新用户并告知其他用户有一个新用户加入。CHAT命令将新的信息发送给所有用户。PRIV命令将悄悄话发送给某个用户。GONE命令从用户列表中除去一个已离开的用户并告知其他的用户某某已经离开了。同时,GONE命令可以设置布尔型的变量keepalive为false从而结束与客户端连接的线程。ServiceClient()函数
如下:

private void ServiceClient()

{

Socket client = clientsocket;

bool keepalive = true;

while (keepalive)

{

Byte[] buffer = new Byte[1024];

client.Receive(buffer);

string clientcommand = System.Text.Encoding.ASCII.GetString(buffer);

string[] tokens = clientcommand.Split(new Char[]{'|'});

Console.WriteLine(clientcommand);

if (tokens[0] == "CONN")

{

for(int n=0; n

{

Client cl = (Client)clients[n];

SendToClient(cl, "JOIN|" + tokens[1]);

}
客户端程序:

1. 打开VS.net,新建一个C#的模板为“Windows 应用程序”的项目,不妨命名为“ChatClient”。

2. 布置界面。往界面上添加一个ListBox控件(用于显示用户列表),一个RichTextBox控件(用于显示聊天消息以及系统消息),一个TextBox控件(用于发送消息),一个CheckBox控件(确定是否为悄悄话),一个StatusBar控件以及四个Button控件(分别为“连接”、“断开连接”、“开始记录”、“发送”)。各个控件的属性设置可以参见源代码中的具体设置,这里从略。界面设计好后的图象如下:

3. 客户端程序的代码编写。

当客户端试图和服务器端进行连接时,一个连接必须建立而且得向服务器端进行注册。EstablishConnection()函数运用一个TcpClient来和服务器端取得连接,同时创建一个NetworkStream来发送消息。还有,端口号和服务器端的是保持一致的,均为5555。EstablishConnection()函数如下:

private void EstablishConnection()

{

statusBar1.Text = "正在连接到服务器";

try

{

clientsocket = new TcpClient(serveraddress,serverport);

ns = clientsocket.GetStream();

sr = new StreamReader(ns);

connected = true;

}

catch (Exception)

{

MessageBox.Show("不能连接到服务器!","错误",

MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

statusBar1.Text = "已断开连接";

}

}

在和服务器端连接成功后,程序就用RegisterWithServer()函数向服务器端发送一个CONN命令。该命令先是发送该用户的名称,然后从服务器端获得其他所有用户的列表,所有用户列表是在ListBox控件中显示的。该函数如下:

private void RegisterWithServer()

{

try

{

string command = "CONN|" + ChatOut.Text;

Byte[] outbytes = System.Text.Encoding.ASCII.GetBytes(command.ToCharArray());

ns.Write(outbytes,0,outbytes.Length);

string serverresponse = sr.ReadLine();

serverresponse.Trim();

string[] tokens = serverresponse.Split(new Char[]{'|'});

if(tokens[0] == "LIST")

{

statusBar1.Text = "已连接";

btnDisconnect.Enabled = true;

}

for(int n=1; n

lbChatters.Items.Add(tokens[n].Trim(new char[]{'\r','\n'}));

this.Text = clientname + ":已连接到服务器";

}

catch (Exception)

{

MessageBox.Show("注册时发生错误!","错误",

MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}

}

}

if (tokens[0] == "JOIN")

{

rtbChatIn.AppendText(tokens[1].Trim() );

rtbChatIn.AppendText(" has joined the Chat\r\n");

if(logging)

{

logwriter.WriteLine(tokens[1]+" has joined the Chat");

}

string newguy = tokens[1].Trim(new char[]{'\r','\n'});

lbChatters.Items.Add(newguy);

}

if (tokens[0] == "GONE")

{

rtbChatIn.AppendText(tokens[1].Trim() );

rtbChatIn.AppendText(" has left the Chat\r\n");

if(logging)

{

logwriter.WriteLine(tokens[1]+" has left the Chat");

}

lbChatters.Items.Remove(tokens[1].Trim(new char[]{'\r','\n'}));

}

if (tokens[0] == "QUIT")

{

ns.Close();

clientsocket.Close();

keepalive = false;

statusBar1.Text = "服务器端已停止";

connected= false;

btnSend.Enabled = false;

btnDisconnect.Enabled = false;

}

}

catch(Exception){}

}

}

通过以上的一些函数,客户端程序之间就可以进行自由地聊天了,各个用户之间还可以互相发送悄悄话。所以程序已经实现了聊天室的基本功能了,不过最后各个用户还要正常地退出,那就要用到QuitChat()函数了。该函数的具体实现如下:

private void QuitChat()

{

if(connected)

{

try

{

string command = "GONE|" + clientname;

Byte[] outbytes = System.Text.Encoding.ASCII.GetBytes(command.ToCharArray());

ns.Write(outbytes,0,outbytes.Length);

clientsocket.Close();

}

catch(Exception)

{

}

}

if(logging)

logwriter.Close();

if(receive != null && receive.IsAlive)

receive.Abort();

this.Text = "客户端";

}

到此为止,客户端程序的主要部分都已经介绍完毕。还有一些按钮控件的消息处理函数可以参见源代码。同时,程序中还有一个聊天记录功能,该功能和现在流行的聊天软件的记录功能类似。不过限于篇幅,在这里就不一一介绍了,
有兴趣的读者可以研究一下本文后面的源代码。

这样,客户端程序就完成了。程序运行图示如下:

总结:

本文向大家初步介绍了套接字的基本概念和实现套接字编程的基本原理,还通过一个很好的实例向大家展示了在C#下进行套接字编程的实现方法和一些编程技巧。从中,我们不难发现运用C#进行套接字编程乃至网络编程有许多优越之处。实例程序实现的思路清晰明了而且通俗易懂,是一个相当不错的例子,希望各位能好好研读。同时还希望大家能进一步完善该程序,使之功能更强大、界面更友好。最后还要注明的是:该实例程序是在VS.net正式版下编译、运行成功的,如果你还是Beta版的话可能会有一些差别。

[Edit on 2004-8-24 1:14:54 By Felix]
京ICP备05053527号
经过26次查询历时5.177秒终于生成了此页面
Powered by WordPress & Designed by Felix © 2008