黑马程序员--学习多线程和Socket小结

------------------Windows Phone 7手机开发.Net培训、---------------------

多线程
在一个程序中,这些独立运行的程序片断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。多线程处理一个常见的例子就是用户界面。利用线程,用户可按下一个按钮,然后程序会立即作出响应,而不是让用户等待程序完成了当前任务以后才开始响应。

什么是多线程?
  在计算机编程中,一个基本的概念就是同时对多个任务加以控制。许多程序设计问题都要求程序能够停下手头的工作,改为处理其他一些问题,再返回主进程。可以通过多种途径达到这个目的。最开始的时候,那些掌握机器低级语言的程序员编写一些“中断服务例程”,主进程的暂停是通过硬件级的中断实现的。尽管这是一种有用的方法,但编出的程序很难移植,由此造成了另一类的代价高昂问题。中断对那些实时性很强的任务来说是很有必要的。但对于其他许多问题,只要求将问题划分进入独立运行的程序片断中,使整个程序能更迅速地响应用户的请求。
  最开始,线程只是用于分配单个处理器的处理时间的一种工具。但假如操作系统本身支持多个处理器,那么每个线程都可分配给一个不同的处理器,真正进入“并行运算”状态。从程序设计语言的角度看,多线程操作最有价值的特性之一就是程序员不必关心到底使用了多少个处理器。程序在逻辑意义上被分割为数个线程;假如机器本身安装了多个处理器,那么程序会运行得更快,毋需作出任何特殊的调校。根据前面的论述,大家可能感觉线程处理非常简单。但必须注意一个问题:共享资源!如果有多个线程同时运行,而且它们试图访问相同的资源,就会遇到一个问题。举个例子来说,两个线程不能将信息同时发送给一台打印机。为解决这个问题,对那些可共享的资源来说(比如打印机),它们在使用期间必须进入锁定状态。所以一个线程可将资源锁定,在完成了它的任务后,再解开(释放)这个锁,使其他线程可以接着使用同样的资源。
  多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。
线程
定义
  英文:Thread
  每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。
  线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.
  线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文.多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定. 线程的运行中需要使用计算机的内存资源和CPU。
使用线程的好处
  ·使用线程可以把占据长时间的程序中的任务放到后台去处理
  ·用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
  ·程序的运行速度可能加快
  ·在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下可以释放一些珍贵的资源如内存占用等等。
  还有其他很多使用多线程的好处,这里就不一一说明了。
一些线程模型的背景
  可以重点讨论一下在Win32环境中常用的一些模型。
  ·单线程模型 
  在这种线程模型中,一个进程中只能有一个线程,剩下的进程必须等待当前的线程执行完。这种模型的缺点在于系统完成一个很小的任务都必须占用很长的时间。
  ·块线程模型(单线程多块模型STA)
  这种模型里,一个程序里可能会包含多个执行的线程。在这里,每个线程被分为进程里一个单独的块。每个进程可以含有多个块,可以共享多个块中的数据。程序规定了每个块中线程的执行时间。所有的请求通过Windows消息队列进行串行化,这样保证了每个时刻只能访问一个块,因而只有一个单独的进程可以在某一个时刻得到执行。这种模型比单线程模型的好处在于,可以响应同一时刻的多个用户请求的任务而不只是单个用户请求。但它的性能还不是很好,因为它使用了串行化的线程模型,任务是一个接一个得到执行的。
  ·多线程块模型(自由线程块模型)
  多线程块模型(MTA)在每个进程里只有一个块而不是多个块。这单个块控制着多个线程而不是单个线程。这里不需要消息队列,因为所有的线程都是相同的块的一个部分,并且可以共享。这样的程序比单线程模型和STA的执行速度都要快,因为降低了系统的负载,因而可以优化来减少系统idle的时间。这些应用程序一般比较复杂,因为程序员必须提供线程同步以保证线程不会并发的请求相同的资源,因而导致竞争情况的发生。这里有必要提供一个锁机制。但是这样也许会导致系统死锁的发生。
  进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。
  线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说main或WinMain函数,将程序的启动点提供给Windows系统。主执行线程终止了,进程也就随之终止。
  每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。Win32 SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步、互斥和临界区等操作。Visual C++ 6.0中,使用MFC类库也实现了多线程的程序设计,使得多线程编程更加方便。[1]
编辑本段多线程在.NET里如何工作?
  在本质上和结构来说,.NET是一个多线程的环境。有两种主要的多线程方法是.NET所提倡的:使用ThreadStart来开始你自己的进程,直接的(使用ThreadPool.QueueUserWorkItem)或者间接的(比如Stream.BeginRead,或者调用BeginInvoke)使用ThreadPool类。一般来说,你可以"手动"为长时间运行的任务创建一个新的线程,另外对于短时间运行的任务尤其是经常需要开始的那些,进程池是一个非常好的选择。进程池可以同时运行多个任务,还可以使用框架类。对于资源紧缺需要进行同步的情况来说,它可以限制某一时刻只允许一个线程访问资源。这种情况可以视为给线程实现了锁机制。线程的基类是System.Threading。所有线程通过CLI来进行管理。
  ·创建线程:
  创建一个新的Thread对象的实例。Thread的构造函数接受一个参数:
  Thread DummyThread = new Thread( new ThreadStart(dummyFunction) );
  ·执行线程:
  使用Threading命名空间里的start方法来运行线程:
  DummyThread.Start ();
  ·暂停线程:
  使得线程暂停给定的秒
  DummyPriorityThread.Sleep(<Time in Second>);
  ·中止线程:
  如果需要中止线程可以使用如下的代码:
  DummyPriorityThread.Abort();

1.什么是socket
  所谓socket通常也称作"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求。以J2SDK-1.3为例,Socket和ServerSocket类库位于java .net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。

如何开发一个Server-Client模型的程序
  开发原理:
  服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。
  客户端,使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。
  Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
C#语言中的SOCKET
  所谓Socket通常也称作”套接字“,应用程序通常通过”套接字”向网络发出请求或者应答网络请求。根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
  服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
  客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
  连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
  Socket通信以其传输速度快且稳定的优点在程序开发中应用非常的广泛,下面的范例就简要的介绍了Socket在C#中应用。

1用多线程和Socket编写服务端
代码如下:
     public partial class ServiceForm : Form
    {
        public ServiceForm()
        {
            InitializeComponent();
            TextBox.CheckForIllegalCrossThreadCalls = false;
        }
        Socket watchSck = null;
        Thread watchThread = null;//负责监听客户端请求的线程
        #region 启动服务器
        private void btnStrartServices_Click(object sender, EventArgs e)
        {
            //创建socket对象
            watchSck = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //创建ip对象
            IPAddress ipaddr = IPAddress.Parse(txtIP.Text);
            //创建ip和端口的节点对象
            IPEndPoint ipEndPoint = new IPEndPoint(ipaddr, int.Parse(txtendPont.Text.Trim()));
        //将负责监听的套接字绑定到唯一的ip 和端口上
            watchSck.Bind(ipEndPoint);
            //设置请求队列
            watchSck.Listen(10);
            ShowMsg("服务端开启监听成功!");
            //负责监听客户端请求的线程
            watchThread = new Thread(watchConnecting);
            watchThread.IsBackground = true;
            //开启线程
            watchThread.Start();
        }
        #endregion

        //创建集合用于保存客户端连接服务器产生的连接套接字
        Dictionary<string, Socket> dictSocket = new Dictionary<string, Socket>();
        Dictionary<string, Thread> dictThread = new Dictionary<string, Thread>();

        #region 监听客户端请求的方法
        //监听客户端请求的方法
        void watchConnecting()
        {
            while (true) //持续监听客户端请求
            {
                Socket connSocket = watchSck.Accept();
                string strAddress = connSocket.RemoteEndPoint.ToString();
                //将远程连接的IP和端口添加到listbox中
                libIPAddress.Items.Add(strAddress);
                //将客户端连接服务器产生的连接套接字添加到集合中
                dictSocket.Add(strAddress, connSocket);
                ShowMsg("客户端连接成功!");
                Thread td = new Thread(ReceiveCLient);
                td.IsBackground = true;
                td.Start(connSocket);
                dictThread.Add(strAddress, td); //将通信的线程保存到集合中
            }
        }
        #endregion

        #region 负责接收客户端发送的数据的方法
        void ReceiveCLient(object socket)
        {
            Socket connSocket = socket as Socket;
            while (true)
            {
                //定义一个接收用的缓存区(字节数组)
                byte[] byteTakeMsg = new byte[1024 * 1024 * 2];
                //将接收到的数据存入byteTakeMsg字节数组中
                int length = connSocket.Receive(byteTakeMsg);
                if (byteTakeMsg[0] == 1)//表示接收到的是文件
                {
                    SaveFileDialog sfd = new SaveFileDialog();
                    if (sfd.ShowDialog() == DialogResult.OK)
                    {
                        string fileSavePath = sfd.FileName;
                        using (FileStream fs = new FileStream(fileSavePath, FileMode.Create))
                        {
                            fs.Write(byteTakeMsg, 1, length - 1);
                            ShowMsg("文件保存成功!" + fileSavePath);
                        }
                    }
                }
                else if (byteTakeMsg[0] == 0)//表示接收到的是消息
                {
                    string strTakeMsg = System.Text.Encoding.UTF8.GetString(byteTakeMsg, 1, length - 1);
                    ShowMsg("客户端说:" + strTakeMsg);
                }

            }
        }
        #endregion

        #region 将信息显示到多行文本框中
        void ShowMsg(string msg)
        {
            txtmsg.AppendText(msg + "\r\n");
        }
        #endregion

        #region 向一个客户端发送信息
        private void btnsend_Click(object sender, EventArgs e)
        {
            string strSendMsg = txtsendmsg.Text.Trim();
            if (string.IsNullOrEmpty(strSendMsg))
            {

                MessageBox.Show("发送信息不能为空!");
                return;
            }
            string strAddress = libIPAddress.Text;
            //连接的套接字发送消息
            if (string.IsNullOrEmpty(strAddress))
            {
                MessageBox.Show("你没有选择发送对象");
                return;
            }
            //将字符串转成utf8的字节数组
            byte[] byteSendMsg = System.Text.Encoding.UTF8.GetBytes(strSendMsg);
            dictSocket[strAddress].Send(byteSendMsg);
            ShowMsg("发送了:" + strSendMsg);
        }
        #endregion

        #region 向客户端群发信息
        private void btnMachSend_Click(object sender, EventArgs e)
        {
            string strSendMsg = txtsendmsg.Text.Trim();
            if (string.IsNullOrEmpty(strSendMsg))
            {
                MessageBox.Show("发送信息不能为空!");
                return;
            }
            //连接的套接字发送消息
            if (dictSocket.Values.Count <= 0)
            {
                MessageBox.Show("你没有发送对象");
                return;
            }
            //将字符串转成utf8的字节数组
            byte[] byteSendMsg = System.Text.Encoding.UTF8.GetBytes(strSendMsg);
            foreach (Socket socket in dictSocket.Values)
            {
                socket.Send(byteSendMsg);
            }
            ShowMsg("群发了:" + strSendMsg);
        }
        #endregion
    }

2用多线程和Socket编写客户端
代码如下:
     public partial class ClientForm : Form
    {
        public ClientForm()
        {
            InitializeComponent();
            TextBox.CheckForIllegalCrossThreadCalls = false;
        }
        Socket clientSck = null;
        Thread ReceiveThread = null;//负责接收数据的线程
        #region 连接服务端
        private void btnConnService_Click(object sender, EventArgs e)
        {
            //创建socket对象
            clientSck = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //创建ip对象
            IPAddress ipaddr = IPAddress.Parse(txtIP.Text);
            //创建ip和端口的节点对象
            IPEndPoint ipEndPoint = new IPEndPoint(ipaddr, int.Parse(txtendPont.Text.Trim()));
            clientSck.Connect(ipEndPoint);
            ReceiveThread = new Thread(ReceiveService);
            ReceiveThread.IsBackground = true;
            ReceiveThread.Start();
        }
        #endregion

        #region 接收服务端的信息
        void ReceiveService()
        {
            while (true)
            {
                //定义一个接收用的缓存区(字节数组)
                byte[] byteTakeMsg = new byte[1024 * 1024 * 1];
                //将接收到的数据存入byteTakeMsg字节数组中
                int length = clientSck.Receive(byteTakeMsg);
                string strTakeMsg = System.Text.Encoding.UTF8.GetString(byteTakeMsg, 0, length);
                ShowMsg("服务器说:" + strTakeMsg);
            }
        }
        #endregion

        #region 在文本框中显示信息
        void ShowMsg(string msg)
        {
            txtmsg.AppendText(msg + "\r\n");
        }
        #endregion

        #region 向服务端发送消息
        private void btnSend_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(txtSendMsg.Text))
            {
                MessageBox.Show("你没有写发送信息");
                return;
            }
            //获取发送的消息
            string strSendMsg = txtSendMsg.Text.Trim();
            byte[] btyMsg = new byte[1024 * 1024 * 2];
            //将字符串转换成utf8的字节数组
            btyMsg = System.Text.Encoding.UTF8.GetBytes(strSendMsg);
            //定义一个带有标示的字节数组
            byte[] byteSendMsg = new byte[btyMsg.Length + 1];
            byteSendMsg[0] = 0;//代表发送的是消息
            //将消息的字节数据复制到要发送的带标示字节数组中
            Buffer.BlockCopy(btyMsg, 0, byteSendMsg,1, btyMsg.Length);
            clientSck.Send(byteSendMsg);
            ShowMsg("发送了:" + strSendMsg);
        }
        #endregion

        #region 选择发送的文件
        private void btnChooseFile_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                txtFilePath.Text = ofd.FileName;
            }
        }
        #endregion

        #region 发送文件void btnSendFile_Click
        private void btnSendFile_Click(object sender, EventArgs e)
        {
            //用文件流打开文件
            using (FileStream fs = new FileStream(txtFilePath.Text, FileMode.Open))
            {
                byte[] btyFile = new byte[1024 * 1024 * 2];
                //将文件读到文件流对象中
                int length = fs.Read(btyFile, 0, btyFile.Length);
                //定义一个带有标示的字节数组
                byte[] bytSendFile = new byte[length + 1];
                bytSendFile[0] = 1;//代表发送的是文件
                //将文件的字节数据复制到要发送的带标示字节数组中
                Buffer.BlockCopy(btyFile, 0, bytSendFile, 1, length);
                //发送
                clientSck.Send(bytSendFile);
            };
        }
        #endregion
    }


----------------------Windows Phone 7手机开发.Net培训、期待与您交流! ---------------------- 详细请查看:http://net.itheima.com/





  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值