【Windows编程】【网络编程】【基于网络端口通信的客户端应用程序】解决方案【示意程序】...

【课题】:【基于网络端口通信的客户端应用程序】解决方案【示意程序】

【创建日期】:2007年6月7日星期四

【完成日期】:2007年6月8日星期五

【级别】:Demo示例程序

【关键字】:Socket,TCP/IP,UDP,WinForm,Net,C#

【解决方案说明】:

本解决方案用于描述基于网络端口通信的客户端应用程序解决方案。本示例仅为了展示应用程序如何利用端口进行通信。本解决方案包含两个项目(TcpServerClient,UdpServerClient)分别用于展示TCP/IP协议下的强制连接通信过程和UDP协议下的无连接通信过程。两种过程相互独立,可以根据应用程序的具体使用环境而自由选择。

程序均通过自身接收和发送文本消息。所有消息通过端口进行通信。监听消息的部分均采用单独线程进行工作(多线程),以获得发送消息的自由和不间断的消息监听机制。

TCP/IP协议版本可以在单机开启两个应用程序,但是其中一个将显示异常报告,两个窗体进行的输入将在另外一个单独的正常的窗体中显示。UDP协议版本则不可以在单机开启两个应用程序。但这并不表示应用程序之间无法自由通信,原因是同一应用程序对同一计算机的同一端口进行监听将会出现异常,这部分内容有待完善。

本应用程序可在局域网内自由通信,若通信失败,请先检查是否开启了防火墙拦截了此软件的通信。

【程序截图】:

image

image

【程序解读】:

C#:(仅以TCP进行代码讲解,UDP协议的基本思路相同)

  // 以下代码片段展示了ChatTool.cs中的代码,描述了整个应用程序的通信机制。
  1
None.gif using  System;
  2 None.gif using  System.Text;
  3 None.gif using  System.Windows.Forms;
  4 None.gif using  System.Net;
  5 None.gif using  System.Net.Sockets;
  6 None.gif using  System.Threading; 
  7 None.gif
  8 None.gif namespace  TcpServerClient
  9 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 10InBlock.gif    public partial class ChatTool : Form
 11ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 12InBlock.gif        //声明一个监听者
 13InBlock.gif        TcpListener listener; 
 14InBlock.gif
 15InBlock.gif        //指定发送和接收端口
 16InBlock.gif        //单机使用,发送接收端口必须一致
 17InBlock.gif        int sendPort = 11000;
 18InBlock.gif        int receivePort = 11000
 19InBlock.gif
 20InBlock.gif        public ChatTool()
 21ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 22InBlock.gif            InitializeComponent();
 23ExpandedSubBlockEnd.gif        }
 
 24InBlock.gif
 25InBlock.gif        private void Client_Load(object sender, EventArgs e)
 26ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif
 27InBlock.gif
 28InBlock.gif            //当窗体加载的时候创建一个新的线程
 29InBlock.gif            //新的线程负责监听者的动作Listen()
 30InBlock.gif            //线程语法:
 31InBlock.gif            //new Thread(new ThreadStart(具体你所要委托线程执行的目标程序))
 32InBlock.gif            //如此处的Listen
 33InBlock.gif            Thread workTicketThread = new Thread(new ThreadStart(Listen));
 34InBlock.gif            workTicketThread.IsBackground = true;
 35InBlock.gif            workTicketThread.Start();
 36ExpandedSubBlockEnd.gif        }
 
 37InBlock.gif
 38InBlock.gif        private void btnSend_Click(object sender, EventArgs e)
 39ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 40InBlock.gif            //按下发送按钮的时候触发该事件
 41InBlock.gif            //将窗体中的数据发送出去
 42InBlock.gif            Send(txtSpeaker.Text);
 43InBlock.gif            //将窗体中的内容清空
 44InBlock.gif            txtSpeaker.Clear();
 45ExpandedSubBlockEnd.gif        }
 
 46InBlock.gif
 47InBlock.gif        private IPAddress GetIPAddress()
 48ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 49InBlock.gif            IPAddress ip; 
 50InBlock.gif
 51InBlock.gif            //尝试转换IP框中的数据,由于是一个字符串
 52InBlock.gif            //因此首先需要判断它是否可以转换为字符串
 53InBlock.gif            //如果不可以则转换为127.0.0.1,代表本机环回IP地址
 54InBlock.gif            //out语法:在参数前使用out,表示该参数是在外部定义的(像这里的ip,是我们在外部进行定义的)
 55InBlock.gif            //      而该参数经过该函数(这里指TryParse)的执行后,值将由该参数在函数内的执行结果为主
 56InBlock.gif            if (IPAddress.TryParse(txtIPAddress.Text, out ip) == false)
 57InBlock.gif                IPAddress.TryParse("127.0.0.1"out ip);
 58InBlock.gif            return ip;
 59ExpandedSubBlockEnd.gif        }
 
 60InBlock.gif
 61InBlock.gif        private void Listen()
 62ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 63InBlock.gif            //设置Listen方法收取的
 64InBlock.gif            int bufferSize = 1024
 65InBlock.gif
 66InBlock.gif            try
 67ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 68InBlock.gif                //监听者在机器的指定IP地址和端口进行侦听
 69InBlock.gif                listener = new TcpListener(IPAddress.Any, receivePort);
 70InBlock.gif                //只有在运行了Start之后才正式开始侦听
 71InBlock.gif                listener.Start(); 
 72InBlock.gif
 73InBlock.gif                //始终监听指定IP地址和端口
 74InBlock.gif                while (true)
 75ExpandedSubBlockStart.gifContractedSubBlock.gif                dot.gif{
 76InBlock.gif                    //监听者只有在运行AcceptTcpClient,就进入挂起的连接请求
 77InBlock.gif                    //注意:程序运行到这里的时候,就会停顿在这里进行监听,
 78InBlock.gif                    //      后面的工作将被放在接收到TCP连接之后继续执行。
 79InBlock.gif                    //      这也就是为什么要采用单独线程来处理监听事件的缘故了。
 80InBlock.gif                    //      否则程序在一开始就会停顿在这个位置,甚至无法初始化
 81InBlock.gif                    //问题:可以尝试不使用多线程进行监听,看看会发生什么效果?
 82InBlock.gif                    TcpClient client = listener.AcceptTcpClient(); 
 83InBlock.gif
 84InBlock.gif                    //来自TcpClient的消息可以用GetStream来接收
 85InBlock.gif                    NetworkStream clientStream = client.GetStream(); 
 86InBlock.gif
 87InBlock.gif                    //将接收到的流转换成本地流
 88InBlock.gif                    //约定:这里采用Unicode编码方式在二进制和文本之间进行转换
 89InBlock.gif                    byte[] buffer = new byte[bufferSize];
 90InBlock.gif                    int readBytes = 0;
 91InBlock.gif                    readBytes = clientStream.Read(buffer, 0, bufferSize);
 92InBlock.gif                    //将接收到的流还原为文本
 93InBlock.gif                    string request = Encoding.Unicode.GetString(buffer).Substring(0, readBytes); 
 94InBlock.gif
 95InBlock.gif                    //将接收到的文本格式化输出
 96InBlock.gif                    txtMessage.Text = ReceiveMessageFormat(txtMessage.Text, request); 
 97InBlock.gif
 98InBlock.gif                    //注意:在使用完流之后要将其显式关闭,以让垃圾回收器对其进行回收
 99InBlock.gif                    clientStream.Close();
100ExpandedSubBlockEnd.gif                }

101ExpandedSubBlockEnd.gif            }

102InBlock.gif            catch (Exception ex)
103ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
104InBlock.gif                txtMessage.Text = ex.ToString();
105ExpandedSubBlockEnd.gif            }

106InBlock.gif            finally
107ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
108InBlock.gif                //每次监听完毕需要关闭监听者,以保证线程可以被垃圾回收器回收
109InBlock.gif                //垃圾回收器知识:.NET自带的垃圾回收器机制允许调用者不必关心对象的内存释放问题
110InBlock.gif                //      当垃圾回收器检测到该资源没有用的时候,将会对其内存进行释放
111InBlock.gif                //      假设这里不停止监听者的工作,那么该线程将一直处于忙状态,垃圾回收器判断
112InBlock.gif                //      该线程仍然在使用,这时它将得不到释放。
113InBlock.gif                //      这样做的后果将是:当整个应用程序的主线程(窗体所在的线程)关闭的时候,
114InBlock.gif                //      也就是窗体关闭的时候,我们希望属于这个应用程序的所有内存得到释放
115InBlock.gif                //      但是垃圾回收器没有对它进行回收,也就是它仍然工作着。
116InBlock.gif                //      这时候就发生了内存泄漏。这种现象严重的可能导致操作系统的崩溃。
117InBlock.gif                listener.Stop();
118ExpandedSubBlockEnd.gif            }

119ExpandedSubBlockEnd.gif        }
 
120InBlock.gif
121InBlock.gif        private void Send(string message)
122ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
123InBlock.gif            TcpClient client = new TcpClient();
124InBlock.gif            try
125ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
126InBlock.gif                //获取要发送的IP地址
127InBlock.gif                IPAddress ip = GetIPAddress();
128InBlock.gif                //通过IP地址获得实体,可用于获取与主机相关的信息,这里主要为了获得主机名
129InBlock.gif                IPHostEntry host = Dns.GetHostEntry(ip); 
130InBlock.gif
131InBlock.gif                //使用指定的IP地址和端口号将本地主机连接到远程主机
132InBlock.gif                //TCP知识:TCP是建立连接的网络传输协议,需要在通信前经过握手连接以保证二者之间的连通性。
133InBlock.gif                client.Connect(ip, sendPort); 
134InBlock.gif
135InBlock.gif                //将要传输的文本转换成流,将其写入,流将通过网络传输到远程主机,
136InBlock.gif                //远程主机经过监听,将流转换成本地文本进行显示
137InBlock.gif                NetworkStream clientStream = client.GetStream();
138InBlock.gif                string request = SendMessageFormat(host.HostName, message);
139InBlock.gif                byte[] requestBuffer = Encoding.Unicode.GetBytes(request);
140InBlock.gif                clientStream.Write(requestBuffer, 0, requestBuffer.Length);
141ExpandedSubBlockEnd.gif            }

142InBlock.gif            catch (System.Net.Sockets.SocketException)
143ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
144InBlock.gif                txtMessage.Text = "请检查对方接收方应用程序是否开启!如果没有,请将其开启,TCP/IP应用程序要求接收方处于监听状态!";
145ExpandedSubBlockEnd.gif            }

146InBlock.gif            finally
147ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
148InBlock.gif                client.Close();
149ExpandedSubBlockEnd.gif            }

150ExpandedSubBlockEnd.gif        }
 
151InBlock.gif
152InBlock.gif        private string SendMessageFormat(string hostName,string message)
153ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
154InBlock.gif            return string.Format("{0}{1}来自{2}的消息:{1}{1}{3}{1}{1}", DateTime.Now, Environment.NewLine, hostName, message);
155ExpandedSubBlockEnd.gif        }
 
156InBlock.gif
157InBlock.gif        private string ReceiveMessageFormat(string oldMessage, string newMessage)
158ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
159InBlock.gif            string tempMessage = "";
160InBlock.gif            if (oldMessage.Trim() == string.Empty)
161InBlock.gif                tempMessage = newMessage;
162InBlock.gif            else
163InBlock.gif                tempMessage = oldMessage + Environment.NewLine + newMessage;
164InBlock.gif            return tempMessage;
165ExpandedSubBlockEnd.gif        }
 
166InBlock.gif
167InBlock.gif        private void Client_FormClosing(object sender, FormClosingEventArgs e)
168ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
169InBlock.gif            //如果不调用关闭监听,则listener仍然处于监听状态,
170InBlock.gif            //应用程序的主窗体虽然关闭,但是在后台进程中依然
171InBlock.gif            //保留,这将导致再次启动的时候会报错!
172InBlock.gif            listener.Stop();
173ExpandedSubBlockEnd.gif        }

174ExpandedSubBlockEnd.gif    }

175ExpandedBlockEnd.gif}

176 None.gif

 

注意:以上程序并非线程安全的,如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。(本程序在XP、2003下运行均通过,TCP版同样能在Vista版本下通过,而UDP版则在Vista下因为线程安全的问题报错。)
解决方案1:判断窗体InvokeRequired,如果值为true则利用委托进行调用。
解决方案2:使用 BackgroundWorker 进行的线程安全调用(可以在工具箱中找到该控件)

 

  1 None.gif // 以下代码片段为窗体设计代码
  2 None.gif namespace  TcpServerClient
  3 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
  4InBlock.gif    partial class ChatTool
  5ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
  6ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
  7InBlock.gif        /// 必需的设计器变量。
  8ExpandedSubBlockEnd.gif        /// </summary>

  9InBlock.gif        private System.ComponentModel.IContainer components = null
 10InBlock.gif
 11ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 12InBlock.gif        /// 清理所有正在使用的资源。
 13InBlock.gif        /// </summary>
 14ExpandedSubBlockEnd.gif        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>

 15InBlock.gif        protected override void Dispose(bool disposing)
 16ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 17InBlock.gif            if (disposing && (components != null))
 18ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 19InBlock.gif                components.Dispose();
 20ExpandedSubBlockEnd.gif            }

 21InBlock.gif            base.Dispose(disposing);
 22ExpandedSubBlockEnd.gif        }
 
 23InBlock.gif
 24ContractedSubBlock.gifExpandedSubBlockStart.gif        Windows 窗体设计器生成的代码#region Windows 窗体设计器生成的代码 
 25InBlock.gif
 26ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 27InBlock.gif        /// 设计器支持所需的方法 - 不要
 28InBlock.gif        /// 使用代码编辑器修改此方法的内容。
 29ExpandedSubBlockEnd.gif        /// </summary>

 30InBlock.gif        private void InitializeComponent()
 31ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 32InBlock.gif            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ChatTool));
 33InBlock.gif            this.btnSend = new System.Windows.Forms.Button();
 34InBlock.gif            this.txtIPAddress = new System.Windows.Forms.TextBox();
 35InBlock.gif            this.txtSpeaker = new System.Windows.Forms.TextBox();
 36InBlock.gif            this.txtMessage = new System.Windows.Forms.RichTextBox();
 37InBlock.gif            this.SuspendLayout();
 38InBlock.gif            // 
 39InBlock.gif            // btnSend
 40InBlock.gif            // 
 41InBlock.gif            this.btnSend.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
 42InBlock.gif            this.btnSend.Location = new System.Drawing.Point(377247);
 43InBlock.gif            this.btnSend.Name = "btnSend";
 44InBlock.gif            this.btnSend.Size = new System.Drawing.Size(7523);
 45InBlock.gif            this.btnSend.TabIndex = 0;
 46InBlock.gif            this.btnSend.Text = "Send";
 47InBlock.gif            this.btnSend.UseVisualStyleBackColor = true;
 48InBlock.gif            this.btnSend.Click += new System.EventHandler(this.btnSend_Click);
 49InBlock.gif            // 
 50InBlock.gif            // txtIPAddress
 51InBlock.gif            // 
 52InBlock.gif            this.txtIPAddress.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
 53InBlock.gif                        | System.Windows.Forms.AnchorStyles.Right)));
 54InBlock.gif            this.txtIPAddress.Location = new System.Drawing.Point(1247);
 55InBlock.gif            this.txtIPAddress.Name = "txtIPAddress";
 56InBlock.gif            this.txtIPAddress.Size = new System.Drawing.Size(35621);
 57InBlock.gif            this.txtIPAddress.TabIndex = 2;
 58InBlock.gif            this.txtIPAddress.Text = "127.0.0.1";
 59InBlock.gif            // 
 60InBlock.gif            // txtSpeaker
 61InBlock.gif            // 
 62InBlock.gif            this.txtSpeaker.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
 63InBlock.gif                        | System.Windows.Forms.AnchorStyles.Right)));
 64InBlock.gif            this.txtSpeaker.Location = new System.Drawing.Point(1183);
 65InBlock.gif            this.txtSpeaker.Multiline = true;
 66InBlock.gif            this.txtSpeaker.Name = "txtSpeaker";
 67InBlock.gif            this.txtSpeaker.Size = new System.Drawing.Size(46258);
 68InBlock.gif            this.txtSpeaker.TabIndex = 3;
 69InBlock.gif            // 
 70InBlock.gif            // txtMessage
 71InBlock.gif            // 
 72InBlock.gif            this.txtMessage.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
 73InBlock.gif                        | System.Windows.Forms.AnchorStyles.Left)
 74InBlock.gif                        | System.Windows.Forms.AnchorStyles.Right)));
 75InBlock.gif            this.txtMessage.AutoWordSelection = true;
 76InBlock.gif            this.txtMessage.Location = new System.Drawing.Point(10);
 77InBlock.gif            this.txtMessage.Name = "txtMessage";
 78InBlock.gif            this.txtMessage.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.Vertical;
 79InBlock.gif            this.txtMessage.Size = new System.Drawing.Size(462177);
 80InBlock.gif            this.txtMessage.TabIndex = 4;
 81InBlock.gif            this.txtMessage.Text = "";
 82InBlock.gif            // 
 83InBlock.gif            // ChatTool
 84InBlock.gif            // 
 85InBlock.gif            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
 86InBlock.gif            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
 87InBlock.gif            this.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("$this.BackgroundImage")));
 88InBlock.gif            this.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center;
 89InBlock.gif            this.ClientSize = new System.Drawing.Size(464282);
 90InBlock.gif            this.Controls.Add(this.txtMessage);
 91InBlock.gif            this.Controls.Add(this.txtSpeaker);
 92InBlock.gif            this.Controls.Add(this.txtIPAddress);
 93InBlock.gif            this.Controls.Add(this.btnSend);
 94InBlock.gif            this.Name = "ChatTool";
 95InBlock.gif            this.Opacity = 0.9;
 96InBlock.gif            this.Text = "TcpVersion";
 97InBlock.gif            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Client_FormClosing);
 98InBlock.gif            this.Load += new System.EventHandler(this.Client_Load);
 99InBlock.gif            this.ResumeLayout(false);
100InBlock.gif            this.PerformLayout(); 
101InBlock.gif
102ExpandedSubBlockEnd.gif        }
 
103InBlock.gif
104ExpandedSubBlockEnd.gif        #endregion
 
105InBlock.gif
106InBlock.gif        private System.Windows.Forms.Button btnSend;
107InBlock.gif        private System.Windows.Forms.TextBox txtIPAddress;
108InBlock.gif        private System.Windows.Forms.TextBox txtSpeaker;
109InBlock.gif        private System.Windows.Forms.RichTextBox txtMessage;
110ExpandedSubBlockEnd.gif    }

111ExpandedBlockEnd.gif}
 
112 None.gif
113 None.gif  
114 None.gif
115 ExpandedBlockStart.gifContractedBlock.gif /**/ /// <summary>
116InBlock.gif/// 应用程序的主入口点。
117ExpandedBlockEnd.gif/// </summary>

118 None.gif [STAThread]
119 None.gif static   void  Main()
120 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
121InBlock.gif    Application.EnableVisualStyles();
122InBlock.gif    Application.SetCompatibleTextRenderingDefault(false);
123InBlock.gif    Application.Run(new ChatTool());
124ExpandedBlockEnd.gif}

125 None.gif

【免责声明】:

本解决方案只为示意如何进行简单的Socket编程,程序中可能存在有明显的未修复BUG,不影响示意程序的正常表达,可能未作修复。如同一计算机开启两个相同窗体可能引发的异常等。

【欢迎交流】:

与本示意有关的任何问题,请与我取得联系,我将积极为您解答。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值