zoukankan      html  css  js  c++  java
  • 使用SOCKET实现TCP/IP协议的通讯

    一、原理: 

        首先要理解基本的原理,2台电脑间实现TCP通讯,首先要建立起连接,在这里要提到服务器端与客户端,两个的区别通俗讲就是主动与被动的关系,两个人对话,肯定是先有人先发起会话,要不然谁都不讲,谈什么话题,呵呵!一样,TCPIP下建立连接首先要有一个服务器,它是被动的,它只能等待别人跟它建立连接,自己不会去主动连接,那客户端如何去连接它呢,这里提到2个东西,IP地址和端口号,通俗来讲就是你去拜访某人,知道了他的地址是一号大街2号楼,这个是IP地址,那么1号楼这么多门牌号怎么区分,嗯!门牌号就是端口(这里提到一点,我们访问网页的时候也是IP地址和端口号,IE默认的端口号是80),一个服务器可以接受多个客户端的连接,但是一个客户端只能连接一台服务器,在连接后,服务器自动划分内存区域以分配各个客户端的通讯,那么,那么多的客户端服务器如何区分,你可能会说,根据IP么,不是很完整,很简单的例子,你一台计算机开3个QQ,服务器怎么区分?所以准确的说是IP和端口号,但是客户端的端口号不是由你自己定的,是由计算机自动分配的,要不然就出现端口冲突了,说的这么多,看下面的这张图就简单明了了。

        在上面这张图中,你可以理解为程序A和程序B是2个SOCKET程序,服务器端程序A设置端口为81,已接受到3个客户端的连接,计算机C开了2个程序,分别连接到E和D,而他的端口是计算机自动分配的,连接到E的端口为789,连接到D的为790。

        了解了TCPIP通讯的基本结构后,接下来讲解建立的流程,首先声明一下我用的开发环境是Visual Studio2008版的,语言C#,组件System.Net.Sockets,流程的建立包括服务器端的建立和客户端的建立,如图所示:

    二、实现:

     

          1.客户端:

     

          第一步,要创建一个客户端对象TcpClient(命名空间在System.Net.Sockets),接着,调用对象下的方法BeginConnect进行尝试连接,入口参数有4个,address(目标IP地址),port(目标端口号),requestCallback(连接成功后的返调函数),state(传递参数,是一个对象,随便什么都行,我建议是将TcpClient自己传递过去),调用完毕这个函数,系统将进行尝试连接服务器。

          第二步,在第一步讲过一个入口参数requestCallback(连接成功后的返调函数),比如我们定义一个函数void Connected(IAsyncResult result),在连接服务器成功后,系统会调用此函数,在函数里,我们要获取到系统分配的数据流传输对象(NetworkStream),这个对象是用来处理客户端与服务器端数据传输的,此对象由TcpClient获得,在第一步讲过入口参数state,如果我们传递了TcpClient进去,那么,在函数里我们可以根据入口参数state获得,将其进行强制转换TcpClient tcpclt = (TcpClient)result.AsyncState,接着获取数据流传输对象NetworkStream ns = tcpclt.GetStream(),此对象我建议弄成全局变量,以便于其他函数调用,接着我们将挂起数据接收等待,调用ns下的方法BeginRead,入口参数有5个,buff(数据缓冲),offset(缓冲起始序号),size(缓冲长度),callback(接收到数据后的返调函数),state(传递参数,一样,随便什么都可以,建议将buff传递过去),调用完毕函数后,就可以进行数据接收等待了,在这里因为已经创建了NetworkStream对象,所以也可以进行向服务器发送数据的操作了,调用ns下的方法Write就可以向服务器发送数据了,入口参数3个,buff(数据缓冲),offset(缓冲起始序号),size(缓冲长度)。

          第三步,在第二步讲过调用了BeginRead函数时的一个入口参数callback(接收到数据后的返调函数),比如我们定义了一个函数void DataRec(IAsyncResult result),在服务器向客户端发送数据后,系统会调用此函数,在函数里我们要获得数据流(byte数组),在上一步讲解BeginRead函数的时候还有一个入口参数state,如果我们传递了buff进去,那么,在这里我们要强制转换成byte[]类型byte[] data= (byte[])result.AsyncState,转换完毕后,我们还要获取缓冲区的大小int length = ns.EndRead(result),ns为上一步创建的NetworkStream全局对象,接着我们就可以对数据进行处理了,如果获取的length为0表示客户端已经断开连接。

        具体实现代码,在这里我建立了一个名称为Test的类:

    [csharp] view plaincopyprint?
     
    1. usingSystem;  
    2. using System.Collections.Generic;  
    3. using System.Net.Sockets;  
    4. namespace test  
    5. {  
    6.   public class Test  
    7.   {  
    8.         protected TcpClient tcpclient = null;  //全局客户端对象  
    9.         protected NetworkStream networkstream = null;//全局数据流传输对象  
    10.         /// <summary>  
    11.         /// 进行远程服务器的连接  
    12.         /// </summary>  
    13.         /// <param name="ip">ip地址</param>  
    14.         /// <param name="port">端口</param>  
    15.         public Test(string ip, int port)  
    16.         {  
    17.             networkstream = null;  
    18.             tcpclient = new TcpClient();  //对象转换成实体  
    19.             tcpclient.BeginConnect(System.Net.IPAddress.Parse(ip), port, new AsyncCallback(Connected), tcpclient);  //开始进行尝试连接  
    20.         }  
    21.          /// <summary>  
    22.         /// 发送数据  
    23.         /// </summary>  
    24.         /// <param name="data">数据</param>  
    25.         public void SendData(byte[] data)  
    26.         {  
    27.               if (networkstream != null)   
    28.                   networkstream.Write(data, 0, data.Length);  //向服务器发送数据  
    29.         }  
    30.         /// <summary>  
    31.         /// 关闭  
    32.         /// </summary>  
    33.         public void Close()  
    34.         {  
    35.             networkstream.Dispose(); //释放数据流传输对象  
    36.             tcpclient.Close(); //关闭连接  
    37.         }  
    38.         /// <summary>  
    39.         /// 关闭  
    40.         /// </summary>  
    41.         /// <param name="result">传入参数</param>  
    42.         protected void Connected(IAsyncResult result)  
    43.         {  
    44.                 TcpClient tcpclt = (TcpClient)result.AsyncState;  //将传递的参数强制转换成TcpClient  
    45.                 networkstream = tcpclt.GetStream();  //获取数据流传输对象  
    46.                 byte[] data = new byte[1000];  //新建传输的缓冲  
    47.                 networkstream.BeginRead(data, 0, 1000, new AsyncCallback(DataRec), data); //挂起数据的接收等待  
    48.         }  
    49.         /// <summary>  
    50.         /// 数据接收委托函数  
    51.         /// </summary>  
    52.         /// <param name="result">传入参数</param>  
    53.         protected void DataRec(IAsyncResult result)  
    54.         {  
    55.                 int length = networkstream.EndRead(result);  //获取接收数据的长度  
    56.                 List<byte> data = new List<byte>(); //新建byte数组  
    57.                 data.AddRange((byte[])result.AsyncState); //获取数据  
    58.                 data.RemoveRange(length, data.Count - length); //根据长度移除无效的数据  
    59.                 byte[] data2 = new byte[1000]; //重新定义接收缓冲  
    60.                 networkstream.BeginRead(data2, 0, 1000, new AsyncCallback(DataRec), data2);  //重新挂起数据的接收等待  
    61.                 //自定义代码区域,处理数据data  
    62.                 if (length == 0)  
    63.                 {  
    64.                     //连接已经关闭  
    65.                 }  
    66.         }  
    67.     }  
    68. }  


          2.服务器端:

        相对于客户端的实现,服务器端的实现稍复杂一点,因为前面讲过,一个服务器端可以接受N个客户端的连接,因此,在服务器端,有必要对每个连接上来的客户端进行登记,因此服务器端的程序结构包括了2个程序结构,第一个程序结构主要负责启动服务器、对来访的客户端进行登记和撤销,因此我们需要建立2个类。

        第一个程序结构负责服务器的启动与客户端连接的登记,首先建立TcpListener网络侦听类,建立的时候构造函数分别包括localaddr和port2个参数,localaddr指的是本地地址,也就是服务器的IP地址,有人会问为什么它自己不去自动获得本机的地址?关于这个举个很简单的例子,服务器安装了2个网卡,也就有了2个IP地址,那建立服务器的时候就可以选择侦听的使用的是哪个网络端口了,不过一般的电脑只有一个网络端口,你可以懒点直接写个固定的函数直接获取IP地址System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0],GetHostAddresses函数就是获取本机的IP地址,默认选择第一个端口于是后面加个[0],第2个参数port是真侦听的端口,这个简单,自己决定,如果出现端口冲突,函数自己会提醒错误的。第二步,启动服务器,TcpListener.Start()。第三步,启动客户端的尝试连接,TcpListener.BeginAcceptTcpClient,入口2个参数,callback(客户端连接上后的返调函数),state(传递参数,跟第二节介绍的一样,随便什么都可以,建立把TcpListener自身传递过去),第四步,建立客户端连接上来后的返调函数,比如我们建立个名为void ClientAccept(IAsyncResult result)的函数,函数里,我们要获取客户端的对象,第三步里讲过我们传递TcpListener参数进去,在这里,我们通过入口参数获取它TcpListener tcplst = (TcpListener)result.AsyncState,获取客户端对象TcpClient bak_tcpclient = tcplst.EndAcceptTcpClient(result),这个bak_tcpclient我建议在类里面建立个列表,然后把它加进去,因为下一个客户端连接上来后此对象就会被冲刷掉了,客户端处理完毕后,接下来我们要启动下一个客户端的连接tcplst.BeginAcceptTcpClient(new AsyncCallback(sub_ClientAccept), tcplst),这个和第三步是一样的,我就不重复了。

         第二个程序结构主要负责单个客户端与服务器端的处理程序,主要负责数据的通讯,方法很类似客户端的代码,基本大同,除了不需要启动连接的函数,因此这个程序结构主要启动下数据的侦听的功能、判断断开的功能、数据发送的功能即可,在第一个程序第四步我们获取了客户端的对象bak_tcpclient,在这里,我们首先启动数据侦听功能NetworkStream ns= bak_tcpclient.GetStream();ns.BeginRead(data, 0, 1024, new AsyncCallback(DataRec), data);这个跟我在第二节里介绍的是一模一样的(第二节第10行),还有数据的处理函数,数据发送函数,判断连接已断开的代码与第二节也是一模一样的,不过在这里我们需要额外的添加一段代码,当判断出连接已断开的时候,我们要将客户端告知第一个程序结构进行删除客户端操作,这个方法我的实现方法是在建立第二个程序结构的时候,将第一个程序结构当参数传递进来,判断连接断开后,调用第一个程序结构的公开方法去删除,即从客户端列表下删除此对象。

        第一个程序结构我们定义一个TSever的类,第二个程序结构我们一个TClient的类,代码如下:

    [csharp] view plaincopyprint?
     
      1. public class TSever  
      2.     {  
      3.         public List<TClient> Clients = new List<TClient>();  //客户端列表  
      4.         private TcpListener tcplistener = null;  //侦听对象  
      5.         /// <summary>  
      6.         /// 构造函数  
      7.         /// </summary>  
      8.         /// <param name="port">侦听端口</param>  
      9.         public TSever(int port)  
      10.         {  
      11.             tcplistener = new TcpListener(System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0], port);  //启动侦听  
      12.             tcplistener.Start(); //启动侦听  
      13.             tcplistener.BeginAcceptTcpClient(new AsyncCallback(ClientAccept), tcplistener); //开始尝试客户端的连接  
      14.         }  
      15.         private void ClientAccept(IAsyncResult result)  
      16.         {  
      17.             TcpListener tcplst = (TcpListener)result.AsyncState;  
      18.             TcpClient bak_tcpclient = tcplst.EndAcceptTcpClient(result);  
      19.             TClient bak_client = new TClient(bak_tcpclient, this);  
      20.             Clients.Add(bak_client);  
      21.             tcplst.BeginAcceptTcpClient(new AsyncCallback(ClientAccept), tcplst);  
      22.         }  
      23. }  
      24.         public class TClient  
      25.         {  
      26.             private TcpClient tcpclient = null;  //客户端对象  
      27.             private NetworkStream networkstream = null;  //数据发送对象  
      28.             private TSever m_Parent=null;  //父级类  
      29.             /// <summary>  
      30.             /// 构造函数  
      31.             /// </summary>  
      32.             /// <param name="tcpclt">客户端对象</param>  
      33.             /// <param name="parent">父级</param>  
      34.             public TClient(TcpClient tcpclt, TSever parent)  
      35.             {  
      36.                     this.tcpclient = tcpclt;  
      37.                     this.m_Parent = parent;  
      38.                     string ip = ((IPEndPoint)tcpclient.Client.RemoteEndPoint).Address.ToString(); //获取客户端IP  
      39.                     string port = ((IPEndPoint)tcpclient.Client.RemoteEndPoint).Port.ToString();  //获取客户端端口  
      40.                     this.networkstream = tcpclt.GetStream();  //获取数据传输对象  
      41.                     byte[] data = new byte[1024];  
      42.                     this.networkstream.BeginRead(data, 0, 1024, new AsyncCallback(DataRec), data);//启动数据侦听  
      43.             }  
      44.             /// <summary>  
      45.             /// 数据接收  
      46.             /// </summary>  
      47.             /// <param name="result"></param>  
      48.             private void DataRec(IAsyncResult result)  
      49.             {  
      50.                     int length = networkstream.EndRead(result);  
      51.                     List<byte> data = new List<byte>();  
      52.                     data.AddRange((byte[])result.AsyncState);  
      53.                     byte[] data2 = new byte[1024];  
      54.                     networkstream.BeginRead(data2, 0, MaxRec, new AsyncCallback(DataRec), data2);  
      55.                     if (length == 0)  
      56.                     {  
      57.                         m_Parent.Clients.Remove(this);  //告知父类删除此客户端  
      58.                     }  
      59.                     else  
      60.                     {  
      61.                         data.RemoveRange(length, data.Count - length);  
      62.                          //数据处理代码data  
      63.                     }  
      64.             }  
      65.             /// <summary>  
      66.             /// 发送数据  
      67.             /// </summary>  
      68.             /// <param name="data">数据</param>  
      69.             /// <returns></returns>  
      70.             public bool SendData(byte[] data)  
      71.             {  
      72.                 networkstream.Write(data, 0, data.Length);  
      73.                 return (true);  
      74.             }  
      75.         }  
  • 相关阅读:
    Java 8 Lambda 表达式
    OSGi 系列(十二)之 Http Service
    OSGi 系列(十三)之 Configuration Admin Service
    OSGi 系列(十四)之 Event Admin Service
    OSGi 系列(十六)之 JDBC Service
    OSGi 系列(十)之 Blueprint
    OSGi 系列(七)之服务的监听、跟踪、声明等
    OSGi 系列(六)之服务的使用
    OSGi 系列(三)之 bundle 事件监听
    OSGi 系列(三)之 bundle 详解
  • 原文地址:https://www.cnblogs.com/geowu/p/4762738.html
Copyright © 2011-2022 走看看