C# TCP/UDP 通讯代码讲解

之前写一个文件传输软件的时候不过的了解这 TCP和UDP的 一些概念和 知识,趁着目前还热乎着,写下来温习一下。。第一次写博客 有不好的地方欢迎指出来。

首先介绍下我之前写的思路。大家都知道 UDP是没有连接是不可靠的,而TCP是面向连接可靠的连接。虽然TCP面向连接并且可靠但是同时效率也是相对没有UDP好,但是文件的传输是不允许丢失的就是必须可靠所以选择用TCP,当然也可以用UDP将文件打包传送然后再检验。

TCP还有一个需要UDP的地方因为我们自己的电脑都不是固定的IP所以不能一直给TCP指定一个服务器IP 一般自己写的程序都是指定自己的电脑为服务器这样就导致每次IP都在改变所以这时候就需要 UDP的广播,通过UDP的广播将服务器的IP发送到客户端然后客户端接收到IP再建立TCP的连接。。差不多就是这样一个思路。

接下来讲解代码:

/// <summary>
        /// 发送广播数据包
        /// </summary>
        public virtual void UDPSendInfo(String SendMsg)
        {           
            UDPS = new Thread(UDPSendThread);
            UDPS.IsBackground = true;
            UDPS.Start(SendMsg);
        }

这个是UDP发送信息的函数通过 线程来发送 将要发送的信息 通过参数 SeendMsg 传送给线程

/// <summary>
        /// UDP发送信息
        /// </summary>
        protected void UDPSendThread(Object SendMsg)
        {
            UDPSend = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);//初始化一个Scoket实习,采用UDP传输
            while (true)
            {
                String IpMsg = (String)SendMsg;
                //IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, UDPport);//初始化一个发送广播和指定端口的网络端口实例
                IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, UDPport);
                UDPSend.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);//设置该scoket实例的发送形式
                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(IpMsg + "-");
                UDPSend.SendTo(buffer, iep);
                Thread.Sleep(10);  //间隔一定时间发送一次
            }
        }
根据上面讲的思路我们要利用UDP发送的是我们的IP地址所以 SendMsg 里面装的是我们的IP地址。  作为服务器这边就 隔一段时间就发送 一次 IP地址信息让客户端能够接收到。IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, UDPport); 这句话说明 发送的是广播信息。
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(IpMsg + "-");

这个是吧信息转换为字节流信息

/// <summary>
        /// 获取本地IP
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        public static String GetLocalIP(int pos = 1)
        {
            try
            {
                // 获得本计算机的名称
                string hostName = Dns.GetHostName();

                // 通过名字获得IP地址
                IPHostEntry hostInfo = Dns.GetHostByName(hostName);
                IPAddress[] address = hostInfo.AddressList;
                // 创建 ipAddress 数组来存放字符串形式的IP地址
                string[] ipAddress = new string[address.Length];
                for (int index = 0; index < address.Length; index++)
                {
                    ipAddress[index] = address[index].ToString();
                }
                pos = ipAddress.Length - 1;
                return Dns.Resolve(Dns.GetHostName()).AddressList[pos].ToString();             
            }
            catch (Exception exc)
            {
                //Error_Info = exc.Message;
                //WriteLogger(exc.ToString());
                return "";
            }

        }

通过这个函数获取自己计算机的IP地址 以用于广播发送给客户端

pos = ipAddress.Length - 1;
                return Dns.Resolve(Dns.GetHostName()).AddressList[pos].ToString();    
这2句还是比较关键的 经过测试这获取的就是自己的公网IP 如果是路由器连着就是 路由器分配给自己的IP 总之一般都是可以用的。

 /// <summary>
        /// UDP接受广播信息
        /// </summary>
        public virtual void UDPRecInfo()
        {           
            UDPR = new Thread(UDPRecThread);
            UDPR.IsBackground = true;
            UDPR.Start();          
        }

接下来是 负责接收的这边。同样利用线程来负责接收 以免阻塞主线程卡死 并且设置为后台线程。这里也没有其他好说的

 /// <summary>
        /// UDP接受线程
        /// </summary>
        protected void UDPRecThread()
        {
            UDPRec = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);//初始化一个Scoket协议           
            IPEndPoint iep = new IPEndPoint(IPAddress.Any, UDPport);
            //IPEndPoint iep = new IPEndPoint(IPAddress.Any, UDPport);//初始化一个侦听局域网内部所有IP和指定端口
            EndPoint ep = (EndPoint)iep;
            UDPRec.Bind(iep);//绑定这个实例
            byte[] buffer = new byte[2048];//设置缓冲数据流
            UDPRec.ReceiveFrom(buffer, ref ep);//接收数据,并确把数据设置到缓冲流里
            serveripaddress = System.Text.Encoding.UTF8.GetString(buffer); //获取ip地址
            serveripaddress = serveripaddress.Split('-')[0];
            UDPRec.Close();
            Start();   //启动
            UDPR.Abort();  //释放资源
        }
这里也是 基本上 注释 讲的差不多了 
serveripaddress = serveripaddress.Split('-')[0];
这个地方要注意一下前面我封装的时候就是 在IP最后加了一个 “-”作为 结束标识。可以自己定义

UDPport

这个就是一个端口 基本上TCP和UDP 都是样的通过端口来识别是哪个应用程序来接受这段信息,基本模式也是端口 + IP地址的组合 就可以通讯。

(IPAddress.Any
这个则是侦听所有的

/// <summary>
        /// 开启前发送UDP电报将本身的serveripadress发送给客户端,和SearchStart 选取一个就好
        /// </summary>
        /// <returns></returns>
        public override bool SearchStart()
        {
            UDPSendInfo(serveripaddress);
            // 创建负责监听的套接字,注意其中的参数;  
            socketWatch = new Socket(IPAddress.Parse(serveripaddress).AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            // 获得文本框中的IP对象;  
            IPAddress address = IPAddress.Parse(serveripaddress);
            // 创建包含ip和端口号的网络节点对象;  
            IPEndPoint endPoint = new IPEndPoint(address, port);
            try
            {
                // 将负责监听的套接字绑定到唯一的ip和端口上;  
                socketWatch.Bind(endPoint);
            }
            catch (SocketException se)
            {
                Error_Info = "异常:" + se.Message;
                return false;
            }
            // 设置监听队列的长度;  
            socketWatch.Listen(clent_lenght);
            // 创建负责监听的线程;  
            threadWatch = new Thread(WatchConnecting);
            threadWatch.IsBackground = true;
            threadWatch.Start();
            File_Rec();        //处理流线程
            _is_start = true;
            return true;
        }
这个是启动TCP 的线程 
serveripaddress
这个变量就是之前我们获取到的本机的IP地址,将它与我们开启的TCP进行绑定,然后
threadWatch = new Thread(WatchConnecting);
            threadWatch.IsBackground = true;
            threadWatch.Start();
用这个创建负责监听是否有客户端连接 的线程

 /// <summary>
        /// 服务器监听函数,进行对子客户端的监听
        /// </summary>
		private void WatchConnecting()
        {
            while (true)  // 持续不断的监听客户端的连接请求;  
            {
                try {
                    // 开始监听客户端连接请求,Accept方法会阻断当前的线程;  
                    Socket sokConnection = socketWatch.Accept(); // 一旦监听到一个客户端的请求,就返回一个与该客户端通信的 套接字;  
                                                                 // 想列表控件中添加客户端的IP信息;  
                                                                 //lbOnline.Items.Add(sokConnection.RemoteEndPoint.ToString());
                                                                 // 将与客户端连接的 套接字 对象添加到集合中;
                    String skey = sokConnection.RemoteEndPoint.ToString();
                    AddNewClient(skey, sokConnection);  //添加一个新的客户端的信息
                    SendInitNameInfo(sokConnection, skey);    //给新连接的客户端 初始化其他客户端的信息和给其他客户端添加新客户端的信息                             
                    Thread thr = new Thread(RecMsg); // 为每一个接收消息的链接单独创建一个线程
                    thr.IsBackground = true;
                    thr.Start(sokConnection);
                    dictThread.Add(skey, thr);  //  将新建的线程 添加 到线程的集合中去。  
                                                //sendInitInfo();//发送初始化信息;
                }catch(Exception ex)
                {
                    Error_Info = ex.Message;
                    WriteLogger(ex.ToString());
                }
            }
            
        }
创建一个死循环来不断 监听是否有客户端连接进来如果有客户端连接进来则 给每个 连接客户端创建一个消息线程,然后将这个这个客户端的端口信息和对象信息存入Dictionary字典 一般都是用这个来储存的当然 选择自己最擅长用的 自己觉得最好用的就好。

 AddNewClient(skey, sokConnection);  //添加一个新的客户端的信息

我这里则是封装为一个函数了将这个过程。

SendInitNameInfo(sokConnection, skey);    //给新连接的客户端 初始化其他客户端的信息和给其他客户端添加新客户端的信息  
这个是我自己的处理逻辑 就不解释了。

 /// <summary>
        /// 监听到后信息的检测函数
        /// </summary>
        /// <param name="sokConnectionparn"></param>
		void RecMsg(object sokConnectionparn) // 负责接收消息的线程
        {
            Socket sokClient = sokConnectionparn as Socket;     //套接字对象
            String skey = sokClient.RemoteEndPoint.ToString();   //套接字信息
            while (true)
            {
                // 定义一个2M的缓存区;  
                byte[] arrMsgRec = new byte[Msg_send_size];
                // 将接受到的数据存入到输入  arrMsgRec中;  
                int length = -1;
                try
                {
                    length = sokClient.Receive(arrMsgRec,0, Msg_send_size,SocketFlags.None); // 接收数据,并返回数据的长度;
                      
                }
                catch (SocketException se)
                {
                    Error_Info = "异常:" + se.Message;
                   /* // 从 通信套接字 集合中删除被中断连接的通信套接字;  
                    dict.Remove(sokClient.RemoteEndPoint.ToString());
                    //从名字列表删除被中断的套接字的名字
                    ClientNameList.Remove(sokClient.RemoteEndPoint.ToString());*/
                    ///删除一个中断的客户端信息
                    DelClient(skey); 
                    // 从通信线程集合中删除被中断连接的通信线程对象;  
                    dictThread.Remove(skey);
                    // 从列表中移除被中断的连接IP  
                    //lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString());
                    break;
                }
                catch (Exception ex)
                {
                    Error_Info = "异常:" + ex.Message;
                    /* // 从 通信套接字 集合中删除被中断连接的通信套接字;  
                    dict.Remove(sokClient.RemoteEndPoint.ToString());
                    //从名字列表删除被中断的套接字的名字
                    ClientNameList.Remove(sokClient.RemoteEndPoint.ToString());*/
                    ///删除一个中断的客户端信息
                    DelClient(skey);
                    // 从通信线程集合中删除被中断连接的通信线程对象;
                    dictThread[skey].Abort();
                    dictThread.Remove(skey);
                    // 从列表中移除被中断的连接IP  
                    //lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString());
                    break;
                }

                try {
                    //Infomation_Select(arrMsgRec, length);   //进行处理信息筛选                   
                    AddRecInfo(arrMsgRec);
                    SendByteToAll(arrMsgRec, sokClient);   //转发
                    //SendFileByte(arrMsgRec);            //转发                                 
                } catch (Exception ex)
                {
                    Error_Info = ex.Message;
                }
            }
        }        
这个是每个客户端对应 的一个接受消息的线程 负责接收一个客户端发送的消息同时负责将消息 转发给其他同时在线的客户端 如果客户端连接断开则
Error_Info = "异常:" + se.Message;
                   /* // 从 通信套接字 集合中删除被中断连接的通信套接字;  
                    dict.Remove(sokClient.RemoteEndPoint.ToString());
                    //从名字列表删除被中断的套接字的名字
                    ClientNameList.Remove(sokClient.RemoteEndPoint.ToString());*/
                    ///删除一个中断的客户端信息
                    DelClient(skey); 
                    // 从通信线程集合中删除被中断连接的通信线程对象;  
                    dictThread.Remove(skey);
                    // 从列表中移除被中断的连接IP  
                    //lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString());
                    break;
删除这个客户端的消息同时break跳出死循环结束这个线程

length = sokClient.Receive(arrMsgRec,0, Msg_send_size,SocketFlags.None); // 接收数据,并返回数据的长度;
这里表示要冲 Socket 接收缓存中  一次提取多少长度的内容出来
Msg_send_size

代表要一次提取的内容长度,所以请在发送的时候也 一次发送同样大小的数据包,因为本人制作的是文件传输 一般文件都不是一下子能传完的所以必须将文件分包传送,然后自己封装每个文件包的 头和尾加上判断是否接受完毕和传送完毕。当全部接受的时候 然后写回到磁盘。消息发送的逻辑和封装都是自己自定义

/// <summary>
        /// 发送文件流
        /// </summary>
        /// <param name="msg">文件流信息</param>
        public override void SendFileByte(byte [] msg)
        {
            try
            {
                foreach (Socket s in dict.Values)
                {
                    if (s.Connected)
                        s.Send(msg, Msg_send_size, SocketFlags.None);
                }
            }catch(Exception ex)
            {
                Error_Info = ex.Message;
            }
        }
服务器发送流消息, 发送的信息都是 以字节流的形式发送。提取每个在线的客户端都给他们发送一遍。注意发送的信息量的大小
Msg_send_size

<strong><span style="font-size:32px;">客户端:</span></strong>
/// <summary>
        /// 启动客户端
        /// </summary>
        /// <returns></returns>
        public override Boolean Start()
        {
            IPAddress ip = IPAddress.Parse(serveripaddress);
            IPEndPoint endPoint = new IPEndPoint(ip, port);
            sockClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                Error_Info += "与服务器连接中……\r\n";
                //MessageBox.Show("与服务器连接中……");
                sockClient.Connect(endPoint);

            }
            catch (SocketException se)
            {
                Error_Info = "连接错误:" + se.Message;
                WriteLogger(se.ToString());
                return false;
                //this.Close();  
            }
            Error_Info = "与服务器连接成功!!!\r\n";
            //MessageBox.Show("与服务器连接成功!!!");
            threadClient = new Thread(RecMsg);    //客户端接收信息
            threadClient.IsBackground = true;
            threadClient.Priority = ThreadPriority.Highest;
            threadClient.Start();                 //开启线程           
            Thread intname = new Thread(InitThread);
            intname.IsBackground = true;
            intname.Start();
            File_Rec();                     //处理留信息 线程
            _is_start = true;
            return true;
        }
当客户端接收到服务器发送的UDP数据包的时候 上面我们已经看到客户端 接收UDP数据包并且解析出来的服务器的IP地址这个时候就可以使用解析出来的IP地址
serveripaddress
与客户端这边的TCPSocket进行绑定了然后开启接收服务器发送的消息的线程
/// <summary>
        /// 接受服务器发送来的消息
        /// </summary>
        protected void RecMsg()
        {
            while (true)
            {
                if (!sockClient.Connected)
                {
                    Error_Info = "和服务器连接断开.....\r\n";
                    WriteLogger(Error_Info);
                    break;
                }
                // 定义一个Msg_send_size的缓存区;  
                byte[] arrMsgRec = new byte[Msg_send_size];
                // 将接受到的数据存入到输入  arrMsgRec中;  
                int length = -1;
                try
                {
                    length = sockClient.Receive(arrMsgRec , 0, Msg_send_size, SocketFlags.None); // 接收数据,并返回数据的长度;  
                }
                catch (SocketException se)
                {
                    Error_Info += "异常;" + se.Message;
                    WriteLogger(se.ToString());
                    return;
                }
                catch (Exception se)
                {
                    Error_Info += "异常:" + se.Message;
                    WriteLogger(se.ToString());
                    return;
                }
                try
                {
                    //Infomation_Select(arrMsgRec, length);  //信息筛选                   
                    AddRecInfo(arrMsgRec);                   
                    
                }
                catch (Exception ex)
                {
                    Error_Info = ex.Message;
                    WriteLogger(ex.ToString());
                }
            }
        }
这边接收的基本和服务器一个样没什么好说的同样要注意
Msg_send_size

/// <summary>
        /// 发送文件流
        /// </summary>
        /// <param name="msg">文件流信息</param>
        public override void SendFileByte(byte []msg)
        {
            try {
                sockClient.Send(msg);//发送字节流
            }catch(Exception ex)
            {
                Error_Info = ex.Message;
            }
        }
接下来是客户端发送 字节流信息基本上看下就明白了。



目前就先这样 有问题欢迎大家提出来

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值