C# Socket多线程编程实例

来源:互联网 发布:匿名短信软件 编辑:程序博客网 时间:2024/05/05 07:03

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类如下:

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]);
}
EndPoint ep = client.RemoteEndPoint;
Client c = new Client(tokens[1], ep, clientservice, client);
clients.Add(c);
string message = "LIST|" + GetChatterList() +" ";
SendToClient(c, message);


lbClients.Items.Add(c);


}
if (tokens[0] == "CHAT")
{
for(int n=0; n
{
Client cl = (Client)clients[n];
SendToClient(cl, clientcommand);
}
}
if (tokens[0] == "PRIV")
{
string destclient = tokens[3];
for(int n=0; n
{
Client cl = (Client)clients[n];
if(cl.Name.CompareTo(tokens[3]) == 0)
SendToClient(cl, clientcommand);
if(cl.Name.CompareTo(tokens[1]) == 0)