关于SOCKET使用的例子,写给和我一样的小白

关于SOCKET的使用,原理等,网上早已是一大堆,为什么我还要写这么一篇呢。其实和很多人一样,一是做个备忘,另外一方面也是让需要的人能看到。为什么要这样说呢?

网上很多例子,但是都只是发送一个字符串,接收一个字符串,然后对于按长度接收数据,数据转换,递归接收数据等就没有然后了,只有理论上的说明,对于小白,怎样让代码能实用,用起来才是关键。所有我就觉得我这篇还是可以放上来的,至于写的好与不好请各位指正。多讨论总比自己想的主义多。

这里也先说一下,我以下提供的代码本人已投入使用,但范围有限,如果使用请根据项目另行修改。一切仅做参考!

代码如下

服务器端

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace WaterClassLibrary
{
    /// <summary>
    /// Socket 服务器端类
    /// 客户端尽可能连接一次只能做一次发送    
    /// </summary>
    public class Cls_SocketServer
    {        
        private IPAddress ServerIP;
        private int Port;
        private Socket SocketServer;
        public ArrayList Clients;        
        public delegate void ServiceWork(Socket forClient, String strMessage);
        public event ServiceWork DoServiceWork;
         

        #region 该定义用于处理接收数据分包
        /// <summary>
        /// 当前客户端状态
        /// </summary>
        class Cls_ClientStata
        {
            /// <summary>
            /// 当前连接客户端
            /// </summary>
            public Socket client;

            /// <summary>
            /// 当前接收到的数据
            /// </summary>
            public string data;

            /// <summary>
            /// 用于存储接收的数据,最后转换为 string 赋值 data
            /// </summary>
            public List<byte> byteSource = new List<byte>();

            /// <summary>
            /// 每次接收的数据长度,实际可能没有这么长,需要根据实际情况处理
            /// </summary>
            public byte[] buffer=new byte[1024];

            /// <summary>
            /// 需要接收的数据长度
            /// </summary>
            public int ReceiveDataLength;

            /// <summary>
            /// 
            /// </summary>
            /// <param name="s"></param>
            /// <param name="d"></param>
            public Cls_ClientStata(Socket s)
            {
                client = s;                
            }
        }
        #endregion

        /// <summary>
        /// 实例服务器端Socket
        /// </summary>
        ///<param name="P">端口号</param>
        ///<param name="ip">这里请使用Any,否则可能服务无法从外网访问</param>
        public Cls_SocketServer(IPAddress ip, int P)
        {
            ServerIP = ip;
            Port = P;
            Clients = new ArrayList();
        }

        /// <summary>
        /// 开始服务,可以创建客户连接
        /// </summary>
        /// <returns></returns>
        public bool StartServer()
        {
            try
            {
                //获取IP地址
                IPEndPoint ipep = new IPEndPoint(ServerIP, Port);
                //实例化Socket
                SocketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //将Socket与指定IP绑定
                SocketServer.Bind(ipep);
                //Socket 开始监听,设置监听队列的长度
                SocketServer.Listen(10000);                
                SocketServer.BeginAccept(new AsyncCallback(ClientAccepted), SocketServer);
                return true;
            }
            catch(Exception ex)
            {                
                return false;
            }
        }

        /// <summary>
        /// 接受客户连接
        /// </summary>
        /// <param name="ar"></param>
        private void ClientAccepted(IAsyncResult ar)
        {
            try
            {
                var socket = ar.AsyncState as Socket;//这里应该是SocketServer
                //这就是客户端的Socket实例,我们后续可以将其保存起来
                var client = socket.EndAccept(ar);//连接到的客户端
                Clients.Add(client);//加入当前连接客户端列表
                Cls_ClientStata ccs = new Cls_ClientStata(client);
                //接收客户端的消息(这个和在客户端实现的方式是一样的)                
                client.BeginReceive(ccs.buffer, 0, ccs.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), ccs);
                //准备接受下一个客户端请求
                socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);
            }
            catch
            {                
                
            }
        }

        /// <summary>
        /// 接收收到的信息
        /// </summary>
        /// <param name="ar"></param>        
        private void ReceiveMessage(IAsyncResult ar)
        {
            try
            {               
                //使用自定义类来存储数据,以便于区分是哪个客户端发来的信息
                Cls_ClientStata ccs = (Cls_ClientStata)ar.AsyncState;
                Socket client = ccs.client;
                int length = client.EndReceive(ar);
                if (length == 0)
                {//如果接收不到数据
                    client.Close();
                    return;
                }
                /// 虽然设置了 css.buffer 的长度为1024,但实际接收数据时,不一定能
                /// 接收到1024的数据,如果此时直接认为是接收了1024,那么收到的数据
                /// 就会多出空数据来,这样最终的收到的数据就会与发送的不一致,由其
                /// 是在发送文件时,收到的文件将是不可用,不可以打开的。
                byte[] tmpbyte = new byte[length];
                Array.Copy(ccs.buffer, 0, tmpbyte, 0, length);
                ccs.byteSource.AddRange(tmpbyte);//接收数据 

                #region 使用长度来标记是否已接收结束
                if (ccs.ReceiveDataLength <= 0 
                    && ccs.byteSource.Count>=4)
                {//还未计算接收长度,同时,接收到的长度已经大于或等于长度位长度
                    byte[] byteLengthg = new byte[4];
                    Array.Copy(ccs.byteSource.ToArray(), 0, byteLengthg, 0, 4);
                    ccs.ReceiveDataLength = Cls_WaterSocketMessage.byteArrayToInt(byteLengthg);//使转换方式与JAVA一致
                    //ccs.ReceiveDataLength = BitConverter.ToInt32(ccs.byteSource.ToArray(),0);
                }
                if (ccs.byteSource.Count >= ccs.ReceiveDataLength + 4)
                {//数据已全部接收到
                    if (DoServiceWork != null)
                    {//返回方法不为空
                        ccs.data = Encoding.UTF8.GetString(ccs.byteSource.ToArray(), 4, ccs.ReceiveDataLength);

                        //实际使用中发现会出现数据传输错误,故加入哈希验证,减少错误数据读取率
                        int indexf = ccs.data.LastIndexOf('\f');
                        string resultData = ccs.data.Substring(0, indexf);
                        string hashData = ccs.data.Substring(indexf + 1, ccs.data.Length - indexf - 1);
                        if (Cls_WaterSocketMessage.GetHashValue(resultData) == hashData)
                        {//哈希值字符串比较
                            DoServiceWork(ccs.client, resultData);
                        }
                        else
                        {
                            DoServiceWork(ccs.client, "FALSE");
                        }
                    }
                    ccs.client.Close();//结束连接
                    return;
                }
                //接收下一个消息(因为这是一个递归的调用,所以这样就可以一直接收消息了)                
                client.BeginReceive(ccs.buffer, 0, ccs.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), ccs);
                #endregion               
            }
            catch(Exception ex) 
            {               
            }
        }

        /// <summary>
        /// 结束服务
        /// </summary>
        public void Stop()
        {
            try
            {
                SocketServer.Shutdown(SocketShutdown.Both);
                SocketServer.Close();
                foreach (Socket s in Clients)
                {
                    s.Shutdown(SocketShutdown.Both);
                    s.Close();
                }
            }
            catch (Exception ex)
            {
                //throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 发送网络信息
        /// </summary>
        /// <param name="s">要发送的客户端</param>
        /// <param name="ms">要发送的信息</param>
        public void SendMessage(Socket s, string ms)
        {
            if (!s.Poll(500, SelectMode.SelectRead))
            {                 
                byte[] sendData = Encoding.UTF8.GetBytes(ms);   //报文内容转 byte
                byte[] hashcode = Encoding.UTF8.GetBytes("\f" + Cls_WaterSocketMessage.GetHashValue(ms));//报文内容的哈希值                
                byte[] sendHead = Cls_WaterSocketMessage.intToByteArray(sendData.Length + hashcode.Length);//报文头,使转换方式与JAVA一致
                //byte[] sendHead = BitConverter.GetBytes(sendData.Length + hashcode.Length);//报文头
                //发送报文长度,int 占4个byte
                s.Send(sendHead);   //发送报文头
                s.Send(sendData);   //发送报文信息
                s.Send(hashcode); //发送信息哈希验证码 
            }
        }        
    }    
}

客户端 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace WaterClassLibrary
{
    /// <summary>
    /// Socket 客户端类
    /// </summary>
    public class Cls_SocketClient
    {
        /// <summary>
        /// 连接服务器的Socket
        /// </summary>
        public Socket client;

        /// <summary>
        /// 接收到的数据
        /// </summary>
        //private string ReciveData;

        private StringBuilder strBuilder = new StringBuilder();

        /// <summary>
        /// SOCKET 状态
        /// </summary>
        ManualResetEvent timeoutObject = new ManualResetEvent(false);//通知一个或多个正在等待的线程已发生事件

        /// <summary>
        /// 接收服务器发来的数据后执行的方法
        /// </summary>
        /// <param name="ServerBackBack">服务器返回的数据</param>
        public delegate void DoServerBack(string ServerBackBack);        

        DoServerBack doServerBack;

        /// <summary>
        /// 获取或设置客户端等待获取服务器回应的最长时间(毫秒)
        /// 默认30000(30秒)
        /// </summary>
        public int millisecondsTimeout = 30000;

        /// <summary>
        /// 接收数据缓存
        /// </summary>
        private byte[] receiveBuffer = new byte[1024];

        /// <summary>
        /// 用于存储接收的数据
        /// </summary>
        List<byte> byteSource = new List<byte>();

        /// <summary>
        /// 接收数据长度
        /// </summary>
        private int ReceiveDataLength;

        #region 连接并接收数据
        /// 连接时应当处理超时问题,否则可能在连接时过长时间等待
        /// 最终造成连接失败,或是无反应假像。

        /// <summary>
        /// 连接到服务器
        /// </summary>
        /// <param name="conIP">连接IP</param>
        /// <param name="conPort">连接端品</param>
        /// <param name="ServerBackDeel"></param>
        /// <param name="csmt"></param>
        public void Connect(string conIP, int conPort, DoServerBack ServerBackDeel, Cls_WaterSocketMessage csmt)
        {
            //创建套接字
            client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            
            doServerBack = ServerBackDeel;            
            //开始连接到服务器    
            try
            {                
                #region Old code,without timeout
                //连接指定服务器
                client.Connect(conIP, conPort);                
                if (client.Connected)
                {//连接成功
                    //向服务器发送服务请求
                    string s = csmt.GetSendMessageString();
                    SendMessage(s);

                    //开始接收服务器返回结果                    
                    client.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
                }
                else
                {//连接失败
                    client.Close();//释放资源
                }
                #endregion
            }
            catch (Exception ex)
            {
                client.Close();
                //如果显示提示,那么在后台静默等待服务器开启时
                //就会出现显示即程序暂停的情况
                //System.Windows.Forms.MessageBox.Show(ex.Message);
            }
        }

        /// <summary>
        /// 连接到服务器
        /// </summary>
        /// <param name="conIP">连接IP</param>
        /// <param name="conPort">连接端口</param>
        /// <param name="ServerBackDeel"></param>
        /// <param name="strData"></param>
        public void Connect(string conIP, int conPort, DoServerBack ServerBackDeel, string strData)
        {
            //创建套接字
            client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            doServerBack = ServerBackDeel;            
            //开始连接到服务器    
            try
            {
                //连接指定服务器
                client.Connect(conIP, conPort);
                if (client.Connected)
                {//连接成功
                    //向服务器发送服务请求                                        
                    SendMessage(strData);
                    //开始接收服务器返回结果                    
                    client.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
                }
                else
                {//连接失败
                    client.Close();
                }
            }
            catch (Exception ex)
            {
                client.Close();
                //如果显示提示,那么在后台静默等待服务器开启时
                //就会出现显示即程序暂停的情况                
            }
        }

        /// <summary>
        /// 接收数据调用方法
        /// </summary>
        /// <param name="ar"></param>
        private void ReceiveCallBack(IAsyncResult ar)
        {
            try
            {
                if (client.Connected)
                {
                    int REnd = client.EndReceive(ar);
                    byte[] tmpbyte = new byte[REnd];
                    Array.Copy(receiveBuffer, 0, tmpbyte, 0, REnd);                    
                    byteSource.AddRange(tmpbyte);//接收数据        

                    #region 使用长度来标记是否已接收结束
                    if (ReceiveDataLength <= 0
                        && byteSource.Count >= 4)
                    {//还未计算接收长度,同时,接收到的长度已经大于或等于长度位长度
                        byte[] byteLengthg = new byte[4];
                        Array.Copy(byteSource.ToArray(), 0, byteLengthg, 0, 4);
                        ReceiveDataLength = Cls_WaterSocketMessage.byteArrayToInt(byteLengthg);//使转换方式与JAVA一致
                        
                    }
                    if (byteSource.Count >= ReceiveDataLength + 4)
                    {//数据已全部接收到
                        if (doServerBack != null)
                        {//返回方法不为空
                            string data = Encoding.UTF8.GetString(byteSource.ToArray(), 4, ReceiveDataLength);

                            //实际使用中发现会出现数据传输错误,故加入哈希验证,减少错误数据读取率
                            int indexf = data.LastIndexOf('\f');
                            string resultData = data.Substring(0, indexf);
                            string hashData = data.Substring(indexf + 1, data.Length - indexf - 1);
                            if (Cls_WaterSocketMessage.GetHashValue(resultData) == hashData)
                            {//哈希值字符串比较
                                doServerBack(resultData);
                            }
                            else
                            {
                                doServerBack("FALSE");
                            }
                        }
                        timeoutObject.Set();//这里设置结果超时控制的阻塞 
                        client.Close();//结束连接
                        return;
                    }
                    #endregion                  
                    
                    if (client.Connected)
                    {
                        client.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
                    }
                }                
            }
            catch (Exception ex)
            {
                
            }
        }
        
        #endregion

        /// <summary>
        /// 断开连接
        /// </summary>
        public void DeConnect()
        {
            try
            {
                if (client != null)
                {
                    client.Shutdown(SocketShutdown.Both);
                    client.Close();                    
                }
            }
            catch (Exception ex)
            {
                string a = ex.Message;
            }
        }

        /// <summary>
        /// 修改为添加哈希验证
        /// </summary>
        /// <param name="sendmessage"></param>
        private void SendMessage(string sendmessage)
        {
            byte[] sendData = Encoding.UTF8.GetBytes(sendmessage);   //报文内容转 byte
            byte[] hashcode = Encoding.UTF8.GetBytes("\f" + Cls_WaterSocketMessage.GetHashValue(sendmessage));//报文内容的哈希值                            
            byte[] sendHead = Cls_WaterSocketMessage.intToByteArray(sendData.Length + hashcode.Length);//报文头,使转换方式与JAVA一致
            if (client != null)
            {
                //发送报文长度,int 占4个byte
                client.Send(sendHead);   //发送报文头
                client.Send(sendData);   //发送报文信息
                client.Send(hashcode); //发送信息哈希验证码       
            }            
        }

        /// <summary>
        /// 如果连接失败或是未返回就中断,则返回false
        /// 成功连接返回true
        /// 这里设置了最长等待判断时长为10000毫秒,即10秒,否则可能出现长时间等待
        /// </summary>
        /// <returns></returns>
        public bool CheckConnect()
        {
            try
            {
                if (this.client == null)
                {
                    return false;
                }
                else if (this.client.Connected == false)
                {
                    DeConnect();
                    return false;
                }                
                if (!timeoutObject.WaitOne(millisecondsTimeout,false))
                {
                    DeConnect();
                    return false;
                }                
                return true;
            }
            catch
            {
                return false;
            }
        }
    }
}

其中的byte转int,网上一大堆,这里就不多说了

TCL语言中的socket编程可以通过使用socket命令来实现。\[2\]在服务器端开启socket服务时,可以使用以下格式的命令:socket -server command ?options? port。其中,command是当有客户端连接时执行的过程,port是服务器监听的端口号。在这个过程中,可以使用accept过程来处理客户端的连接请求。\[2\] 以下是一个简单的TCL socket编程的例子,展示了一个简单的服务器端客户端的连接过程:\[3\] 服务器端代码: ``` proc accept {chan addr port} { puts "$addr:$port says \[gets $chan\]" puts $chan goodbye close $chan } socket -server accept 12345 vwait forever ``` 客户端代码: ``` set sock \[socket localhost 12345\] puts $sock "Hello, server!" puts \[gets $sock\] close $sock ``` 在这个例子中,服务器端通过socket命令开启了一个监听端口为12345的服务器。当有客户端连接时,执行accept过程来处理客户端的请求。客户端通过socket命令连接到服务器,并发送一条消息给服务器,然后接收服务器的响应消息。最后,关闭与服务器的连接。\[3\] 希望这个例子能帮助你理解TCL语言中的socket编程。如果还有其他问题,请随时提问。 #### 引用[.reference_title] - *1* [用TCL语言进行socket编程](https://blog.youkuaiyun.com/iteye_13051/article/details/81690964)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [tcl socket](https://blog.youkuaiyun.com/weixin_30433075/article/details/96757443)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值