C#应用程序设计教程 第10章 多线程和Socket编程初步.ppt

上传人:本田雅阁 文档编号:3097801 上传时间:2019-07-07 格式:PPT 页数:144 大小:416.54KB
返回 下载 相关 举报
C#应用程序设计教程 第10章 多线程和Socket编程初步.ppt_第1页
第1页 / 共144页
C#应用程序设计教程 第10章 多线程和Socket编程初步.ppt_第2页
第2页 / 共144页
C#应用程序设计教程 第10章 多线程和Socket编程初步.ppt_第3页
第3页 / 共144页
C#应用程序设计教程 第10章 多线程和Socket编程初步.ppt_第4页
第4页 / 共144页
C#应用程序设计教程 第10章 多线程和Socket编程初步.ppt_第5页
第5页 / 共144页
点击查看更多>>
资源描述

《C#应用程序设计教程 第10章 多线程和Socket编程初步.ppt》由会员分享,可在线阅读,更多相关《C#应用程序设计教程 第10章 多线程和Socket编程初步.ppt(144页珍藏版)》请在三一文库上搜索。

1、第10章 多线程和Socket编程初步,Socket编程技术广泛用于即时通信系统(如QQ、MSN等)、网络游戏、BT下载、Internet视频直播等C/S结构客户端网络程序,是一个程序员必须掌握的技术,本章介绍Socket编程初步知识。在Socket编程中,必须使用多线程技术,因此在本章首先介绍多线程,然后再介绍Socket编程。,10.1 创建线程,如果在一个程序中,有多个工作要同时做,可以采用多线程。在Windows操作系统中可以运行多个程序,把一个运行的程序叫做一个进程。一个进程又可以有多个线程,所有程序的线程轮流共同占用CPU的运行时间,Windows操作系统将时间分为时间片,每个线程

2、分配一个时间片,一个线程用完一个时间片后,操作系统将此线程挂起,将另一个线程唤醒,使其使用下一个时间片,操作系统不断的把线程挂起,唤醒,再挂起,再唤醒,如此反复,由于现在CPU的速度比较快,给人的感觉象是多个线程同时执行。,Windows操作系统中有很多这样的例子,例如复制文件时,一方面在进行磁盘的读写操作,同时一张纸不停的从一个文件夹飘到另一个文件夹,这个飘的动作实际上是一段动画,两个动作是在不同线程中完成的,就像两个动作是同时进行的。又如Word程序中的拼写检查也是在另一个线程中完成的。每个进程最少有一个线程,叫主线程,是进程自动创建的,每进程可以创建多个线程。本节介绍线程类(Thread

3、)的属性和方法以及如何创建线程。,10.1.1 线程类(Thread)的属性和方法,线程类在命名空间System.Threading中定义的,因此如果要创建多线程,必须引入命名空间System.Threading。Thread类的常用属性和方法如下: 属性Priority:设置线程优先级,有5种优先级类别:AboveNormal(稍高)、BelowNormal(稍低)、Normal(中等,默认值)、Highest(最高)和Lowest(最低)。例如语句myThread.Priority= ThreadPriority.Highest设置线程myThread的优先级为最高。一个线程的优先权并不是

4、越高越好,应考虑到整个进程中所有线程以及其他进程的情况做出最优选择。优先级相同的线程按照时间片轮流运行。优先级高的线程先运行,只有优先级高的线程停止、休眠或暂停时,低优先级的线程才能运行。,构造函数:New(new ThreadStart(线程中要执行的无参数方法名),参数中指定的方法需要程序员自己定义,这个方法完成线程所要完成的任务,退出该方法,线程结束。该方法必须为公有void类型的方法,无参数。如果希望有参数,可使用VB.Net2.0中新构造函数:New(new ParameterizedThreadStart(线程中要执行的只能有一个参数的方法名)。 方法Start():建立线程类对象

5、后,线程处于未启动状态,这个方法使线程改变为就绪状态,如果能获的CPU运行时间,线程变为运行状态。,方法IsAlive():判断线程对象是否存在,=true,线程存在。 方法Abort():撤销线程对象。不能撤销一个已不存在的线程对象,因此在撤销一个线程对象前,必须用方法IsAlive()判断线程对象是否存在。 静态方法Sleep():线程休眠参数设定的时间,单位为毫秒,此时线程处于休眠状态。线程休眠后,允许其他就绪线程运行。休眠指定时间后,线程变为就绪状态。 方法Suspend()和Resume():Suspend()方法使线程变为挂起状态。Resume方法使挂起线程变为就绪状态,如能获的C

6、PU的运行时间,线程变为运行状态。如线程多次被挂起,调用一次Resume()方法就可以把线程唤醒。由于不安全建议不使用这两个函数。,10.1.2 创建线程例子,【例10.1】本例使用线程类Thread创建一个新的线程,在标签控件中显示该线程运行的时间。在窗体放置2个按钮,单击按钮完成新建和停止线程的功能。 (1)新建项目。在窗体中放置2个按钮和1个标签控件(label1)。button1的属性Text=“新线程“, Enabled= true。button2的属性Text=“撤销“,Enabled=false。 (2)在Form1.cs头部增加语句: using System.Threadin

7、g (3)为Form1类中声明一个委托类dFun、定义一个类dFun的变量和线程类变量:,/dFun类可代表无返回值有一个string参数方法 delegate void dFun(string text); /dFun类变量 dFun dFun1; /线程类变量 private Thread thread; (4)为标题为“新线程”的按钮(button1)增加单击事件处理函数如下: private void button1_Click(object sender, EventArgs e) /生成线程类对象,fun为自定义方法名称 thread=new Thread(new ThreadSt

8、art(fun); Label1.Text = “0“ 运行时间从0开始,/线程变为就绪状态,如能获的CPU运行时间, thread.Start() /线程变为运行状态 /标题为“新线程”的按钮,创建线程后, Button1.Enabled = False /不允许再创建线程 /标题为“撤销”的按钮,允许对运行状态的线程撤销 /Button2.Enabled = True (5)为标题为“撤销”的按钮(button2)增加单击事件处理函数如下: private void button2_Click(object sender, EventArgs e) if(thread.IsAlive) t

9、hread.Abort(); /撤销线程对象,button1.Enabled=true; button2.Enabled=false; (6)C#线程模型允许将任何一个公有过程(静态或非静态)作为线程过程,因此允许在任何一个类(不要求这个类是某个类的子类)中定义线程过程,而且同一个类中可以定义多个线程过程。 C#不允许在此过程中直接修改线程外控件属性,这是防止多个线程同时修改同一控件的同一属性发生错误,必须使用控件的Invoke方法修改线程外控件属性,Invoke方法有两个参数,参数1是修改控件属性的方法的委托, 参数2是object数组,是传递给参数1代表的方法的参数。为Form1类定义一个

10、线程方法如下:,/C#1.x中在线程中执行的方法,退出该方法,线程结束 public void fun() /必须为公有void类型方法,无参数 while(true) /这里是死循环,线程将一直运行 /允许得到线程外控件属性值 int x=Convert.ToInt32(label1.Text); x+; string s=Convert.ToString(x); /dFun1代表修改label1.Text的方法 label1.Invoke(dFun1,new objects); /线程休眠1秒钟,休眠一次,线程运行了1秒钟 Thread.Sleep(1000); ,(7)为Form1类定义

11、一个修改label1.Text的方法如下: private void SetText(string text) label1.Text = text; (8)在Form1类的Load事件函数的最后增加如下语句: dFun1=new dFun(SetText); (9)在关闭程序之前,必须撤销线程对象。为主窗体的FormClosing事件增加事件处理函数如下: private void Form1_FormClosing(object sender, FormClosingEventArgs e) if(thread.IsAlive) thread.Abort(); (10)编译运行,单击标题为

12、“新线程“的按钮,新线程开始,计数器从0开始计数。单击标题为“撤销“的按钮,线程对象被撤销,计数器停止计数。,【例10.2】本例重做例9.19,查找文件在另一个线程中进行,当单击“停止搜索”按钮后,停止搜索线程,以便停止查找文件。本例修改例9.19。请同学课后自己完成。,10.2 多个线程互斥,多个线程同时修改共享数据可能发生错误。假设2个线程分别监视2个入口进入的人数,每当有人通过入口,线程用C#语句对总人数变量执行加1操作。一条C#语句可能包含若干机器语言语句,假设C#语句加1操作包含的机器语言语句是:取总人数,加1,再存回。操作系统可以在一条机器语言语句结束后,挂起运行的线程。如当前总人

13、数为5,线程1运行,监视到有人通过入口,取出总人数(=5)后,线程1时间用完挂起。线程2唤醒,也监视到有人通过入口,并完成了总人数加1并送回的操作,总人数为6,线程2挂起。线程1唤醒,对已取出的总人数(此时为5)加1,存回去,总人数应为7,实为6,少算一个。,为了防止此类错误,在一个线程修改共享资源(例如上例的总人数变量)时,不允许其他线程对同一共享资源进行修改,这叫线程的互斥。这样的实例很多,例如计算机中的许多外设,网络中的打印机等都是共享资源,只允许一个进程或线程使用。,10.2.1 多个线程同时修改共享数据可能发生错误,【例10.3】下边的例子模拟2个线程同时修改同一个共享数据时可能发生

14、的错误。 (1)新建项目。在Form1.cs头部增加语句: using System.Threading; (2)为Form1类定义2个Thread线程类变量:thread1,thread2。定义整形变量:num=0。 (3)在窗体中放置一个标签和按钮控件,按钮的事件处理函数如下: private void button1_Click(object sender, EventArgs e) label1.Text = num.ToString(); ,(4)为Form1类构造函数增加语句如下: thread1= new Thread(new ThreadStart(Fun1); thread2

15、= new Thread(new ThreadStart(Fun2); thread1.Start(); thread2.Start(); (5)为Form1类中定义Fun1() 方法如下: public void Fun1() int k,n; for(k=0;k4;k+) n=num; /取出num,可以把把num想象为总人数 n+; /加1 Thread.Sleep(20); /模拟复杂的费时运算,num=n; /存回num Thread.Sleep(50); /退出该方法,线程结束 public void Fun2() int k,n; for(k=0;k4;k+) n=num; n+

16、; Thread.Sleep(10); num=n; Thread.Sleep(100); (6)编译运行,单击按钮,标签控件应显示8,实际运行多次,显示的数要小于8。,10.2.2 用Lock语句实现互斥,Lock语句的形式如下:lock(e)访问共享资源的代码。其中e指定要锁定的对象,锁定该对象内所有临界区,必须是引用类型,一般为this。Lock语句将访问共享资源的代码标记为临界区。临界区的意义是:假设线程1正在执行e对象的临界区中的代码时,如其他线程也要求执行这个e对象的任何临界区中代码,将被阻塞,一直到线程1退出临界区。 【例10.4】用C#语句Lock实现互斥。修改例10.2中的F

17、un1()和Fun2()方法如下:,public void Fun1() int k,n; for(k=0;k4;k+) lock(this) /这里的this是Form1类的对象 n=num; /这对大括号中代码为this的临界区 /this的临界区包含两部分, n+; /函数Fun1和Fun2中的临界区 Thread.Sleep(10); num=n; Thread.Sleep(50); /退出该方法,线程结束,public void Fun2() int k,n; for(k=0;k4;k+) lock(this) /如有线程进入此临界区, n=num; /其他线程就不能进入这个临界区

18、/this的临界区包含两部分, n+; /函数Fun1和Fun2中的临界区 Thread.Sleep(10); num=n; Thread.Sleep(100); /退出该方法,线程结束 编译运行,单击按钮标签控件应显示8。,10.3 TCP/IP协议和Socket,本节首先介绍TCP/IP协议的基础知识,然后介绍Socket类的基本概念。,10.3.1 TCP/IP协议,把分布在不同地理区域的计算机和网络设备利用通信设备互连,使各个计算机之间能够相互通信,实现信息和资源共享,就组成了计算机网络。网络的目的是为了通信,共享资源。通信即传输数据,为了传输数据各个网络系统应遵守一定规则,这个规则叫

19、网络传输协议。当前广泛采用的网络协议是TCP/IP协议。,网络中有成千上万台计算机,应允许任何两台计算机之间进行通信,为了区分不同的计算机,必须给每一台连网计算机一个唯一的编号,这个编号在TCP/IP协议中叫计算机的IP地址,它是一个32位二进制数,用四个十进制数表示,中间用点隔开,每个十进制数允许值为0-255(一个字节),例如,202.112.10.105,这种记录方法叫点数记法。一个计算机要和网络中其他计算机连接,必须有自己的IP地址。C#语言使用IPAddress类表示IP地址,用静态方法Parse可将IP地址字符串转换为IPAddress实例。例如:,/127.0.0.1表示本机IP

20、地址 IPAddress ip = IPAddress.Parse(“127.0.0.1”); IPAddress类提供了几个静态只读字段,其中字段Any表示本地系统所有可用的IP地址,字段Broadcast表示本地网络广播地址。 Dns类提供了一系列静态的方法,其中GetHostAddresses方法获取指定主机的IP地址,返回一个IPAddress类型的数组(一台计算机可能有多个IP地址)。 例如获得CCTV网站的所有IP地址: IPAddress ip=Dns.GetHostAddresses(““);,Dns类GetHostName方法,获取本机主机名。 string hostname

21、 = Dns.GetHostName(); IPAddress ip=Dns.GetHostAddresses(hostname); 一台计算机上可能运行多个网络通信软件,它们的IP地址是相同的。为了访问IP地址相同的不同网络通信软件,可为运行的每个网络通信软件编号,这个编号叫端口号。 IPEndPoint类包含了IP地址和端口信息,IPEndPoint类常用的构造函数如下,第一个参数指定IP地址,第二个参数指定端口号 public IPEndPoint(IPAddress, int);,10.3.2 套接字(Socket),套接字可以理解为编写网络通信软件的函数库,在套接字中封装了为进行网络

22、通信而设计的一组公共函数,网络通信软件通过调用这些公共函数,完成和在网络其他计算机中运行的指定网络通信软件间的双向通信。在.Net中,System.Net.Sockets 命名空间为开发人员提供了开发基于Socket套接字的网络通信程序的一些类,包括Socket类、TcpClient类、TcpListener类和UdpClient类,如果开发基于TCP/IP网络协议网络通信程序,可以使用TcpClient类、TcpListener类和UdpClient类,使用上比较简单,本书所有例子基本上都是使用这三个类。如果为了提高效率或者采用其他网络通信协议,可采用Socket类。,套接字有两种不同的类型

23、:一种是流套接字,又称面向连接的协议,如 TCP;另一种是数据报套接字,又称无连接协议,例如 UDP。基于流套接字的网络通信采用连接方式,通信前要进行网络连接,一旦建立了这种连接,就可以在设备之间可靠的传输数据,建立连接后数据以流的形式在被连接的两个计算机中运行程序间进行流动。这有些像打电话。基于流套接字的网络通信一般采用客户机/服务器模式。基于数据报套接字,采用不连接方式,两个计算机中运行程序间使用单个信息包进行数据传输,这种方式类似邮局,不保证数据包按照发送顺序传送,也可能丢失。以下简单介绍Socket类的用法,后续章节将详细介绍TcpClient类、TcpListener类和UdpCli

24、ent类的使用。,Socket类的构造方法定义如下,其中,addressFamily 参数指定 Socket 使用的寻址方案,socketType 参数指定 Socket 的类型,protocolType 参数指定 Socket 使用的协议。 public Socket(AddressFamily addressFamily,SocketType socketType, ProtocolType protocolType); 生成基于 TCP协议的Socket类对象的例子如下: Socket s = new Socket(AddressFamily.InterNetwork, SocketTy

25、pe.Stream, ProtocolType.Tcp);,一旦创建 基于 TCP协议连接的Socket类对象,在客户端将通过Connect方法连接到指定的服务器,通过Send/SendTo方法向远程服务器发送数据,通过Receive/ReceiveFrom从服务端接收数据;而在服务器端,需要使用Bind方法将Socket对象绑定到本地指定的IP地址和端口号,并通过Listen方法侦听该接口上的请求,当侦听到用户端的连接时,调用Accept完成连接的操作,创建新的Socket以处理传入的连接请求。使用完 Socket 后,使用 Shutdown 方法禁用 Socket,并使用 Close 方法

26、关闭 Socket。,生成基于 UDP协议的Socket类对象的例子如下: Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 由于UDP不存在固定连接,所以可直接使用SendTo方法发送数据,用ReceiveFrom方法接收数据,如不再使用 Socket对象,用 Shutdown 方法禁用Socket对象,用 Close 方法关闭 Socket对象。,10.4 基于TCP协议的Socket编程,本节详细介绍编写基于基于TCP协议的Socket程序方法和步骤。在System

27、.Net.Sockets命名空间下,TcpClient类与TcpListener类是两个专门用于TCP协议编程的类。这两个类封装了底层的套接字,并分别提供了对Socket进行封装后的同步和异步操作的方法,降低了TCP应用编程的难度。TcpClient类用于连接、发送和接收数据。TcpListener类则用于监听是否有传入的连接请求。基于TCP协议的网络通信一般采用客户机/服务器模式,因此必须分别建立客户机和服务器程序。,10.4.1 TcpClient类,利用TcpClient类提供的方法,可以通过网络进行连接、发送和接收网络数据流。该类的构造函数有四种重载形式,常用属性方法如下: 无参数构造

28、函数:创建一个TcpClient类对象,该对象自动选择客户端IP地址和尚未使用的端口号。创建该对象后,即可用Connect方法与服务器端进行连接。例如: TcpClient tcpClient=new TcpClient(); tcpClient.Connect(““, 51888);,构造函数New(AddressFamily family):创建的TcpClient类对象也能自动选择客户端IP地址和尚未使用的端口号,但是使用AddressFamily枚举指定了使用哪种网络协议。创建该对象后,即可用Connect方法与服务器端进行连接。例如: TcpClient tcpClient = ne

29、w TcpClient(AddressFamily.InterNetwork); tcpClient.Connect(““, 51888); 构造函数New(IPEndPoint iep):iep是IPEndPoint类的对象,iep指定了客户端的IP地址与端口号。当客户端的主机有一个以上的IP地址时,可使用此构造函数选择要使用的客户端主机IP地址。例如:,IPAddress address = Dns.GetHostAddresses(Dns.GetHostName(); IPEndPoint iep = new IPEndPoint(address0, 51888); TcpClient

30、tcpClient = new TcpClient(iep); tcpClient.Connect(““, 51888); 构造函数New(string hostname,int port):这是使用最方便的一种构造函数。该构造函数可直接指定服务器端域名和端口号,而且不需使用connect方法。客户端主机的IP地址和端口号则自动选择。例如: TcpClient tcpClient=new TcpClient(““, 51888);,方法Connect():和服务器进行连接,参数分别是服务器IP地址和端口号。 方法Close():释放此 TcpClient 实例,而不关闭基础连接。 方法GetS

31、tream():返回用于发送和接收数据的 NetworkStream。见后边例子。 属性SendTimeout和ReceiveTimeout:等待发送和接收成功完成时间,超过这个时间,将产生SocketException异常。 属性SendBufferSize和ReceiveBufferSize:发送和接收缓冲区大小。 属性Connected:是否已和服务器连接。 属性Client:TcpClient类对象使用的Socket类对象。,10.4.2 TcpListener类,TcpListener类用于监听和接收传入的连接请求。该类的构造函数及常用函数如下: TcpListener(IPEndP

32、oint iep):该构造函数通过IPEndPoint类型的对象在指定的IP地址与端口监听客户端连接请求。 TcpListener(IPAddress localAddr, int port):建立TcpListener对象,在参数中直接指定本机IP地址和端口,并通过指定的本机IP地址和端口号监听传入的连接请求。 AcceptTcpClient():等待连接,直到有新的连接,获取并返回一个用来接收和发送数据的套接字对象后,才执行后续语句。这种方式称作同步阻塞方式。,AcceptSocket:在同步阻塞方式下获取并返回一个可以用来接收和发送数据的封装了Socket的TcpClient对象。 St

33、art():启动监听,其构造函数为: Start(int backlog):整型参数backlog为请求队列的最大长度,即最多允许的客户端连接个数。 Stop():停止监听请求。,10.4.3 服务器程序,使用TCP和流套接字建立服务器,服务器将等待来自客户机的连接请求。在接到请求后,服务器建立和客户机的连接,利用这个连接,服务器和客户机实现通信。IE浏览器(客户机)和Web服务器就是一个典型的客户机/服务器模式,IE浏览器向Web服务器请求网页,Web服务器接到请求,发送请求的网页到IE浏览器。VB.Net语言使用TCP和流套接字建立服务器需要五步。具体步骤如下: (1)System.Net

34、.Sockets命名空间的TcpListener类对象用来等待来自客户机的连接请求,TcpListener类采用TCP协议。创建TcpListener类对象例子如下:,/采用本机IP地址,端口号为1300 TcpListener server = new TcpListener(1300); 客户端程序必须知道服务器的IP地址和端口号,才能和服务器建立连接。使用如下方法获得IP地址和端口号,IPEndPoint和IPAddress在System.Net命名空间。 IPEndPoint iPEndPoint = server.LocalEndpoint; IPAddress iPAddress

35、= iPEndPoint.Address; int port = iPEndPoint.Port;,(2)使用TcpListener类方法Start()开始等待来自客户机的连接请求,代码如下: Server.Start() 或者采用下条语句 Server.Start(200)参数是允许的最大的连接客户机数 (3)使用TcpListener类方法AcceptSocket()等待来自客户机的连接请求,如果没有客户机的连接请求,程序将被阻塞,既不能执行这条语句的后续语句。如果有一个客户机的连接请求,将返回一个Socket或TcpClient类对象,将继续执行后续语句。代码如下: /返回Socket类

36、对象,然后执行后续语句 Socket socket = server.AcceptSocket(); /或采用本语句返回TcpClient类对象 TcpClient tcpClient = server.AcceptTcpClient();,得到Socket或TcpClient类对象,已经和客户机建立了连接,就可以和客户机进行通信。在通信时,将不再侦听其他客户机的连接要求。很多服务器是不允许这种情况发生的,例如Web服务器必须随时等待众多的浏览器的访问。解决的方法是建立一个线程用来和这个客户机进行通信,而TcpListener类对象server将继续侦听其他客户机的连接要求。 (4)如果使用s

37、erver.AcceptSocket方法建立连接,返回的Socket类对象,就可以使用Socket类的Send方法发送数据(返回TcpClient用法见下节)。代码如下:,byte msg=Encoding.UTF8.GetBytes(“This is a test“); int i=socket.Send(msg); /i为发送数据的字节数 可以使用Socket类的方法Receive接收数据(TcpClient用法见下节),代码如下: byte bytes = new byte256; i=socket.Receive(bytes);/i为发送数据的字节数(5)最后,如果不再通信,使用Soc

38、ket类的Close方法终止连接,代码如下: Socket.Close(),10.4.4 客户机程序,网络中的计算机可以运行客户机端网络程序访问服务器,例如,通过IE浏览器(客户机)可以访问Internet中的Web服务器,浏览网页。编写运行于客户机端的网络程序程序需要四个步骤。具体步骤如下: (1)创建System.Net.Sockets命名空间的TcpClient类对象用来和服务器建立连接,代码如下: /自动选择最合适的本地 IP 地址和端口号 TcpClient tcpClient = new TcpClient (); /和本机的服务器连接 tcpClient.Connect (“lo

39、calhost“,1300); Connect方法的第一个参数也可以是远程服务器的域名,例如,“”。如果知道远程服务器的IP地址,可以采用如下代码: TcpClient tcpClient = new TcpClient ();,/参数为远程服务器的IP地址 IPAddress ServerIP= IPAddress.Parse(“202.206.96.204“); tcpClient.Connect (ServerIP,1300) (2)使用TcpClient类的GetStream方法得到一个NetworkStream类对象,用来对服务器进行读写。 NetworkStream netStre

40、am = tcpClient.GetStream(); (3)使用NetworkStream类对象读写服务器数据代码如下: if (netStream.CanWrite) Byte sendBytes = Encoding.UTF8.GetBytes (“Is anybody there?“); ,if (netStream.CanRead) byte bytes = new bytetcpClient.ReceiveBufferSize; netStream.Read (bytes, 0, (int)tcpClient.ReceiveBufferSize); string returndat

41、a = Encoding.UTF8.GetString (bytes); (4)关闭NetworkStream类对象后,关闭和服务器的连接。 netStream.Close() tcpClient.Close(),10.4.5 TCP协议Socket实例,本节首先实现一个时间服务器,客户端访问这个时间服务器系统,可以得到时间服务器系统所在地点的时间,在例子中时间服务器直接使用侦听线程和客户机通信,因此本例仅支持客户机顺序访问和多次访问,但由于服务器发送时间的代码很少,很快能够完成,所以客户机程序感觉没有延迟很快就能得到时间。这是一个最简单的基于TCP协议的Socket程序实例,通过这个例子读者

42、可以清楚地理解Socket编程的基本步骤。实际服务器要比这个时间服务器复杂的多,一般情况下,服务器和客户机通信也许需要较多的时间,例如客户机访问文件下载服务器下载文件,服务器直接使用侦听线程和客户机通信显然不能实现多客户机同时访问服务器功能。例10.7和例10.8实现了一个文件下载系统,该系统实现了多客户机同时访问服务器功能。,【例10.5】本例实现一个时间服务器,客户端访问这个时间服务器系统,可以得到时间服务器系统所在地点的时间。这是一个最简单的Scoket编程实例。具体实现步骤如下: (1)建立一个新的Windows应用项目 。在Form1.cs头部增加命名空间引用: using Syst

43、em.Net; using System.Net.Sockets; using System.Threading; (2)为Form1类增加变量: Thread thread; /线程类变量 bool ifStop = true; /是否停止时间服务器 /负责侦听是否有客户机访问服务器 TcpListener server;,/服务器端和客户机连接的Socket类对象 Socket socket; (3)修改构造函数如下: public Form1() InitializeComponent(); /建立侦听线程,TimeThread是线程执行的方 /法名称,退出该方法,线程结束 thread

44、 = new Thread (new ThreadStart(TimeThread); thread.Start(); /线程启动 ifStop=false;/变量表示是否退出线程,false不退出 Text= “时间服务器“; /Form1窗体的标题栏内容 ,(4)侦听工作不能在主线程中进行,否则当侦听工作被阻塞后,将不能执行其他任何语句,程序看起来就像死了一样,不能执行任何动作。因此侦听工作必须在另一线程中进行。在线程为Form1类定义一个侦听线程方法如下,采用本机IP地址,端口号为1300。 public void TimeThread() try server = new TcpLis

45、tener(1300); server.Start();/开始侦听是否有客户机连接服务器 catch MessageBox.Show(“不能建立服务器“, “提示“, MessageBoxButtons.OK); Return; /原因可能是端口号1300被占用 /或网络不可用,退出线程,while (!ifStop) /如退出while语句,线程结束 Try /下句等待客户端的连接 aSocket = server.AcceptSocket() /阻塞 /得到用字符串表示的时间 string s = DateTime.Now.ToString(); /将时间字符串转换为字节数组 byte m

46、sg = Encoding.UTF8.GetBytes(s); /本例发送时间方法Send和侦听方法 /AcceptSocket()在同一线程。在发送时间时不能 /继续侦听是否有客户机连接服务器。本例发送 /数据较少,发送后很快开始侦听,基本不影响,/其他客户机的连接。本方法支持客户机顺序访 /问和多次访问。如果发送数据较多占用较多时 /间或者客户机要长时间和服务器连接,必须建 /立新线程用来发送数据使侦听可以继续,见后 /续例子。发送时间到客户机,完成之前被阻塞 /完成后执行后续语句。 aSocket.Send(msg) aSocket.Close() 送出时间后关闭和客户机的连接 /退出前

47、,要使ifStop=true,关闭socket和server, /如果这两个对象正在使用必定产生异常,执行catch中 /语句,继续while循环,由于ifStop=true,将退出while /循环语句,即退出TimeThread方法结束线程。,/如果仅仅是在程序运行时,socket = /server.AcceptSocket()或socket.Send(msg)语句发生 /异常,由于ifStop=false,仅仅重新开始侦听。 catch if (socket != null) socket.Close();/关闭关闭和客户机的连接 if (socket != null) /运行到此,线

48、程将结束 socket.Close(); /关闭关闭和客户机的连接 server.Stop(); /关闭TcpListener类对象取消侦听 /运行到此,线程将结束,要关闭所有建立的对象,(5)在关闭程序之前,必须撤销线程对象。为主窗体的Closing事件增加事件处理函数如下: private void Form1_FormClosing(object sender, FormClosingEventArgs e) ifStop = true; if (socket != null) socket.Close(); if (server != null) server.Stop(); if (thread != null ,(6)编译得到可执行文件。请注意,所建立的时间服务器必须在另一个线程中运行,而不能在主线程中,否则主线程将不会响应用户的任何动作,包括关闭程序。这是由于函数TimeThread()中包括一个死循环,如在主线程中运行,将占用主线程的所有时间,没有时间去运行其他代码。读

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 其他


经营许可证编号:宁ICP备18001539号-1