C#实现QQ


1. 网络通讯编程的基础便是协议,信息的发送常用的协议有面向连接的TCP协议,以及不面向连接的UDP协议

2. TCP:TransmissionControlProtocol传输控制协议,其是一种面向连接的、可靠的字节流服务。面向连接意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据之前必须先建立一个TCP连接。这一过程与打电话很相似,先拨号振铃,等待对方摘机说“喂”,然后才说明是谁。

3. UDP:UserDatagramProtocol用户数据报协议(RFC768),UDP传送数据前并不与对方建立连接,即UDP是无连接的,在传输数据前,发送方和接收方相互交换信息使双方同步。

4. 系统也要定义自己的通讯协议,来完成一些系统的功能,如用户上,下线的通知,都要定义自己的通讯协议来完成相应的功能!也可以称这种自定义的协议为“命令”.

5. 下面以著名的飞鸽传书为例,说明其自定义的协议(命令)

IPMSG_NOOPERATION不进行任何操作

IPMSG_BR_ENTRY用户上线

IPMSG_BR_EXIT用户退出

IPMSG_ANSENTRY通报在线

IPMSG_SENDMSG发送消息

IPMSG_RECVMSG通报收到消息

IPMSG_GETFILEDATA请求通过TCP传输文件

IPMSG_RELEASEFILES停止接收文件

IPMSG_GETDIRFILES请求传输文件夹以“IPMSG_BR_ENTRY用户上线”和“IPMSG_ANSENTRY通报在线”为例说明命令处理流程:当程序启动时,命令IPMSG_BR_ENTRY被广播到网络中,向所有在线的用户提示一个新用户的到达(即表示“我来了”);所有在线用户将把该新上线用户添加到自己的用户列表中,并向该新上线用户发送IPMSG_ANSENTRY命令(即表示“我在线”);该新上线用户接收到IPMSG_ANSENTRY命令后即将在线用户添加到自己的用户列表中。

PS:根据本系统的特征,可以在聊天部分采用UDP协议,在文件传输,视频,语音功能上采用TCP协议

6. 程序启动就要发送广播消息,如何发送广播消息,以及C#如何实现广播.

第一部分.什么是广播地址,以及广播地址怎么计算

1.1广播地址是什么?

主机号全为1,用于向一个网络内的所有主机发送信息的IP地址.如:受限的广播地址是255.255.255.255。该地址用于主机配置过程中IP数据报的目的地址,此时,主机可能还不知道它所在网络的网络掩码,甚至连它的IP地址也不知道。在任何情况下,路由器都不转发目的地址为受限的广播地址的数据报,这样的数据报仅出现在本地网络中。

PS:一般无特殊要求广播地址选择255.255.255.255即可.

1.2计算方法

首先计算网络地址=IP地址逻辑与(&)子网掩码

先把IP,子网掩码转为2进制,然后进行逻辑与运算,得出网络地址

例:

IP192.168.1.3子网掩码255.255.0.0

IP转二进制11000000.10100100.00000001.00000011

子网掩码11111111.11111111.00000000.00000000

与运算后11000000.10100100.00000000.00000000

192.168.0.0这就是网络地址,其中子网掩码全1对应为网络号,全0对应的是主机号,即192.168.0.0对应的网络号为192.168,主机号为0.0.将网络地址主机部分全取反后得到的地址便是广播地址:

广播地址11000000.10100100.11111111.11111111

换成10进制则为192.168.0.0

第二部分.C#利用UDP协议如何实现广播
2.1如何实现UDP广播,直接举例说明:
button1_Click时使用了UDP广播向外发送了数据
RecData()在后台接受UDP协议的消息

//UDP通过广播实现群发功能
namespace BroadcastExample
{
public partial class Form1:Form
{
delegate   void   AppendStringCallback(stringtext);
AppendStringCallback   appendstringcallback;
//使用的接收端口51008
///


///端口号
///

private int port=51008;
///
///udp连接对象
///

private UdpClient udpclient;
public Form1()
{
InitializeComponent();
appendstringcallback = new AppendStringCallback(AppendString);
}
///
///委托对象的处理过程
///

///
private void AppendString(stringtext)
{
if(richtextBox2.InvokeRequired==true)
{
this.Invoke(appendstringcallback,text);
}
else
{
richtextBox2.AppendText(text+"\r\n");
}
}
///
///在后台运行的接收线程
///

private  void  RecData()
{
//本机指定端口接收
udpclient=new UdpClient(port);
IPEndPoint remote=null;
//接收从远程主机发送过来的信息
while(true)
{
try
{
//关闭udpclient时此句会产生异常
byte[]bytes=udpclient.Receive(refremote);
stringstr=Encoding.UTF8.GetString(bytes,0,bytes.Length);
AppendString(string.Format("来自{0}:{1}",remote,str));
}
catch
{
//退出循环,结束线程
break;
}
}
}
privatevoidForm1_Load(objectsender,EventArgse)
{
//创建一个线程接收接收远程主机发来的信息
Thread mythread=new Thread(new ThreadStart(RecData));
//将线程设为后台运行
mythread.IsBackground=true;
mythread.Start();
}
private void Form1_FormClosing(objectsender,FormClosingEventArgse)
{
udpclient.Close();
}
private void button1_Click(objectsender,EventArgse)
{
UdpClient myUdpclient=newUdpClient();

try
{
IPEndPoint iep=new IPEndPoint(IPAddress.Broadcast,port);
byte[]bytes=System.Text.Encoding.UTF8.GetBytes(textBox1.Text);
myUdpclient.Send(bytes,bytes.Length,iep);
textBox1.Clear();
myUdpclient.Close();
textBox1.Focus();
}
catch(Exceptionerr)
{
MessageBox.Show(err.Message,"发送失败");
}
finally
{
myUdpclient.Close();
}
}
}
}

 

启动主程序时,同时启动UDP的监听,这时应该使用集合来做为消息队列的缓存,以便用户能在任何时候浏览到消息.这个集合一般在主程序中定义,而用户接受消息,一般我们会弹出窗口给用户来浏览消息,以及在新窗口中回复消息,那如何将主窗口中的消息,传递到消息显示窗体中呢?
如何是Web(ASP.net)我们可以封装到form中传值,或者request传值,甚至可以在URL中接参数直接传值,而winform中窗体传值以上方法就都不在能用了.
在windowsform之间传值,我总结了有四个方法:全局变量、属性、窗体构造函数和delegate。
第一个全局变量:
这个最简单,只要把变量描述成static就可以了,在form2中直接引用form1的变量,代码如下:
在form1中定义一个static变量publicstaticinti=9;
Form2中的钮扣按钮如下:
privatevoidbutton1_Click(objectsender,System.EventArgse)
{
textBox1.Text=Form1.i.ToString();
}


第二个方法是利用属性:
假设我们需要点击主窗体FMMain中的某一个按钮时打开子窗体FMChild并将某一个值传给子窗体FMChild,一般情况下,我们点击按钮显示子窗体FMChild的代码为:  FMChildfmChild=newFMChild();fmChild.ShowDialog();fmChild.Dispose();
  如果我们需要将主窗体FMMain中的stringstrValueA的值传给FMChild,那么我们首先对strValueA进行如下处理:
  privatestringstrValueA;publicstringStrValueA{get{returnstrValueA;}set{strValueA=value;}}
  使其成为主窗体FMMain的一个属性,接着修改显示子窗体的代码为以下两种的其中一种。
  方法一:
  FMChildfmChild=newFMChild();fmChild.ShowDialog(this);fmChild.Dispose();
  方法二:
  FMChildfmChild=newFMChild();FMChild.Owner=this;fmChild.ShowDialog();fmChild.Dispose();
  然后在修改子窗体FMChild中申明一个主窗体FMMain对象,
  FMMainfmMain;
  在需要使用主窗体FMMain的stringstrValueA的地方加上如下代码:
  fmMain=(FMMain)this.Owner;
  这样,就可以获得主窗体FMMain中strValueA的值了。
  这时,如果你需要将子窗体FMChild中的stringstrValueB传给主窗体FMMain,同样处理stringstrValueB.
  privatestringstrValueB;publicstringStrValueB{get{returnstrValueB;}set{strValueB=value;}}
  那么你在关闭子窗体代码fmChild.Dispose();后,可以写一些代码来保存或者处理FMChild的strValueB,例如:
  stringstrTmp=fmChild.StrValueB;


第三个方法是用构造函数:
Form1的button按钮这样写:
privatevoidbutton1_Click(objectsender,System.EventArgse)
{
Form2temp=newForm2(9);
temp.Show();
}

Form2的构造函数这样写:
publicForm2(inti)
{
InitializeComponent();
textBox1.Text=i.ToString();
}


第四个方法是用delegate,代码如下:
Form2中先定义一个delegate
publicdelegatevoidreturnvalue(inti);
publicreturnvalueReturnValue;
form2中的button按钮代码如下:
privatevoidbutton1_Click(objectsender,System.EventArgse)
{
if(ReturnValue!=null)
ReturnValue(8);
}

Form1中的button按键如下:
privatevoidbutton1_Click(objectsender,System.EventArgse)
{
Form2temp=newForm2();
temp.ReturnValue=newtemp.Form2.returnvalue(showvalue);
temp.Show();
}

privatevoidshowvalue(inti)
{
textBox1.Text=i.ToString();
}

点击form2的button,form1中的textbox中的值就会相应变化。


在这四个方法中,
第一个是双向传值,也就是说,form1和form2改变i的值,另一方也会受到影响。
第二个方法可以单向也可以双向传值。
第三个方法是form1->form2单向传值。
第四个方法是form2->form1单向传值。

现在很多程序都有托盘功能,而我们的聊天工具更是如此,无论是QQ,旺旺,飞鸽传书等等,都是以托盘的形式工作在后台,对消息进行监听的.而VS2005给我们提供了现成的控件,来完成托盘的功能,下面我结合代码讲解下项目中可能用到的托盘技巧.
1.如何实现托盘功能:
在VS2005中直接添加notifyIcon控件,然后设置下icon属性,给其设置个图标即可,使用托盘功能.
但是托盘并不能实现我们要求的功能,具体的功能实现,需要我们手工添加代码实现.
2.如何最小化时自动到托盘
private  void  Form1_Resize(objectsender,System.EventArgse)
{
if(this.WindowState==FormWindowState.Minimized)
{
this.Visible=false;
this.notifyIcon1.Visible=true;
}
}
3.如何双击托盘恢复原状
private void  notifyIcon1_Click(objectsender,System.EventArgse)
{
this.Visible=true;
this.WindowState=FormWindowState.Normal;
this.notifyIcon1.Visible=false;
}
4.实现托盘的闪烁功能(如QQ有消息时的闪烁)
(1).首先我们在空白窗体中拖入一个NotifyIcon控件和定时控件
privateSystem.Windows.Forms.NotifyIconnotifyIcon1;
privateSystem.Windows.Forms.Timertimer1;
(2).其次,我们准备两张ico图片,用来显示在任务栏,其中一张可用透明的ico图片,分别叫做1.ico和2.ico;并且建立两个icon对象分别用来存放两个ico图片;
privateIconico1=newIcon("1.ico");
privateIconico2=newIcon("2.ICO");//透明的图标
(3).在Form_load中初始化notifyicon:
privatevoidForm1_Load(objectsender,System.EventArgse)
{
this.notifyIcon1.Icon=ico1;//设置程序刚运行时显示在任务栏的图标
this.timer1.Enable=true;//将定时控件设为启用,默认为false;
}
(4).先设置一个全局变量i,用来控制图片索引,然后创建定时事件,双击定时控件就可以编辑
inti=0;
privatevoidtimer1_Tick(objectsender,System.EventArgse)
{
//如果i=0则让任务栏图标变为透明的图标并且退出
if(i<1)
{
this.notifyIcon1.Icon=ico2;
i++;
return;
}
//如果i!=0,就让任务栏图标变为ico1,并将i置为0;
else
this.notifyIcon1.Icon=ico1;
i=0;
}

由于消息传输要求较低,而且为了简化聊天的步骤,在局域网聊天中,采用UDP是非常好的选择.因为UDP可以不用连接,在获取用户列表后,直接点击用户名就可以发送消息,减少了等待连接等繁琐的步骤,很好的满足了聊天的即时性,实时性特点.
著名局域网聊天工具飞鸽,以及即时聊天工具QQ.在消息传输方面都是采用的UDP.
这讲,我将直接把我程序的源代码贴出来,来讲解,发送消息,接受消息的处理.
1.UDP发送信息
namespaceXChat.SendMes
{
public   class  MsgSend
{
private   UdpClient   udp=null;
private  int  PORT;
private  IPEndPoint   endP=null;
public  MsgSend()
{
this.PORT=58888;
}
publicMsgSend(intport)
{
this.PORT=port;
}

///


///发送信息
///

///要发送到的主机名
///要发送的信息
publicvoidSendMessage(stringhostName,stringmessage)
{
this.udp=newUdpClient();
endP=newIPEndPoint(Dns.GetHostEntry(hostName).AddressList[0],PORT);
try
{
//byte[]b=Encoding.ASCII.GetBytes(hostName);
byte[]b=Encoding.UTF8.GetBytes(message);
udp.Send(b,b.Length,endP);
}
catch
{
System.Windows.Forms.MessageBox.Show("发送出错!");
}
finally
{
this.udp.Close();
}
}

}
}
要使用时直接new MsgSend().SendMessage(主机名,消息);

2.UDP接收消息
namespaceXChat.SendMes
{
//设置消息到前台的委托
public  delegate  voidSet  Message(stringmes);
public  class  MsgRecive
{
private  int  PORT;
private  UdpClient  udp=null;
private  Thread  recThread=null;
private  IPEndPoint  ipep=null;
private  SetMessages  etMes;
public  MsgRecive()
{
this.PORT=58888;
}
publicMsgRecive(intport)
{
this.PORT=port;
}
///
///开启后台接受消息线程
///

///传入设置消息的委托
publicvoidStartReciveMsg(SetMessagesetMes)
{
this.setMes=setMes;
udp=newUdpClient(PORT);
recThread=newThread(newThreadStart(ReciveMsg));
recThread.Start();
}

///
///关闭后台消息接收线程
///

publicvoidCloseReciveMsg()
{
recThread.Abort();
//recThread.Join();
udp.Close();
}
privatevoidReciveMsg()
{
while(true)
{
//这句很重要,否则CPU很容易100%
Thread.Sleep(500);
byte[] b = udp.Receive(refipep);
string message=Encoding.UTF8.GetString(b,0,b.Length);
this.setMes(message);
}
}
}
}
在前台private MsgRecive mr=null;
public xchatFrm()
{
……
this.mr=newMsgRecive(port);
this.mr.StartReciveMsg(newSetMessage(GetMes));
……
}

 

 

 

 

 

这几天一直想写一个类似QQ文件发送的东西,上网找了一些资料,都不是很理想,下面我把我的思路和基本实现代码说下。 

         为了把问题说清楚,把一些变量都直接附值了,并没有通过输入附值

 

                      private string path = "F:\\SmartMovie.EXE";    //要发送的文件

 

                      private Socket s;                              

 

         private void listen()

        {

            string ip = "127.0.0.1";   //远程IP 这里定义为自己的机器

            IPAddress[] ih = Dns.GetHostAddresses(ip);       //获得IP列表

            IPAddress newip = ih[0];      //获取IP地址            

            int port = 6789;              //定义端口

            IPEndPoint Conncet = new IPEndPoint(newip, port);     //构造结点

            s = new Socket(AddressFamily.InterNetwork, SocketType.Stream,  ProtocolType.Tcp);                   //初始化socket 

 

     try

        {                                  

            s.Connect(Conncet);      //连接远程服务器

            if (s.Connected)         //如果连接成功 s.Connected 则为true 否则为 false

            {

 

 

                Console.WriteLine("连接成功");

                Thread t = new Thread(new ThreadStart(set)); //创建进程

                t.Start();                             //开始进程

                Console.WriteLine("发送完毕")      

 

            }

 

               }

                catch(NullReferenceException e)

            {

 

              Console.WriteLine("{0}",e);

 

            }

 

            private void set()                       //创建set函数

        {

            Console.WriteLine("开始发送数据");

            byte[] b = new byte[10000000];            //创建文件缓冲区,这里可以认为文件的最大值

            FileStream file = File.Open(path, FileMode.Open,FileAccess.Read);   //创建文件流

            int start = 0;

            int end = (int)file.Length;               //获取文件长度 文件传送如果有需要超过int的范围估计就要改写FileStream类了

 

      try

          {

            while (end != 0)

            {

                int count = file.Read(b, start, end);      //把数据写进流

                start += count;

                end -= count;

            }

            while (start != 0)

            {

                int n = s.Send(b, end, start, SocketFlags.None);  //用Socket的Send方法发送流

                end += n;

                start -= n;

            }

        

            file.Close();     //关闭文件流

            s.Close();        //关闭Socket

          }  

      catch (NullReferenceException e)

            {

                Console.WriteLine("{0}", e);

            }             

        }   

 

这样文件发送的模型就实现了

 

    接下去实现文件的接收,首先要确定对方发送文件的长度,其实上面的那段还要加入发送文件长度的功能,实现很简单,就是发送int变量end ,然后要求接收代码返回一个Boolean确定是否发送,这里为了更简明的说清楚原理并没有实现

 

             private void get()

        {

            string path = "G:\\da.exe";  //接收的文件

            FileStream file = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write); //写入文件流

            TcpListener listen = new TcpListener(6789);  //监听端口

            Socket s1 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //定义Socket并初始化

            try

            {

             listen.Start();        //开始监听

            s1 = listen.AcceptSocket();            //获取Socket连接

            byte[] data = new byte[10000000];      //定义缓冲区

            int longer = data.Length;               

            int start = 0;                 

            int mid = 0;

            if (s1.Connected)             //确定连接

            {

                Console.WriteLine("连接成功");

                int count = s1.Receive(data, start, longer, SocketFlags.None);  //把接收到的byte存入缓冲区

                mid += count;

                longer -= mid;

                while (count != 0)

                {

                    count = s1.Receive(data, mid, longer, SocketFlags.None);

                    mid += count;

                    longer -= mid;

                }

                file.Write(data, 0, 1214134); //写入文件,1214134为文件大小,可以用socket发送获得,代码前面已经说明。

                s1.Close();

                file.Close();

            }

            }

                catch(NullReferenceException e)

            {

                    Console.WriteLine("{0}",e);

            }

          }

 

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/yangminjie/archive/2007/12/21/1957989.aspx

你可能感兴趣的:(C#实现QQ)