Socket 案例

简介: Socket 案例

文章目录


Socket

简单介绍一下Socket

Socket是什么呢?

先简单了解一下,下面是项目实战

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。


Scoket项目实战

项目介绍

本项目为自己做的一个小程序,客户端与服务器连接通过Socket传输字符串消息、文件以及闪屏。

此程序为C# winform窗体应用程序,分为客户端与服务器端,废话不多说,直接上干货

4edc953e2c684bbe819ffa954c899c08.png

服务器端

1. 窗体设计

这个窗体设计的可能有些丑陋,但是实现的功能是齐全的,适用于初学Socket的学者,接下来进入代码编写阶段

4edc953e2c684bbe819ffa954c899c08.png

2. 服务器功能实现

下面这个是服务器整体代码结构,一一展示出来,注释都写的很清楚不明白的慢慢理解,私信我也可以

4edc953e2c684bbe819ffa954c899c08.png

下面这个是整个服务器端的代码,有些长,每一个方法,每一个重要的代码我都用 #region #endregion,认真查看,执行时如有报错或其他问题私信我!!!

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace ChatDemo
{
    public partial class 服务器端_MainFrm : Form
    {
        #region 属性定义
        //代理Socket对象(客户端点击发送时,ClentProxSocketList发送到服务器端)
        //存储客户端发送的数据
        List<Socket> ClentProxSocketList = new List<Socket>();
        #endregion
        public 服务器端_MainFrm()
        {
            InitializeComponent();
        }
        #region 启动服务(创、绑、开、接)
        private void btnStart_Click(object sender, EventArgs e)
        {
            #region 1. 创建Socket(注释)
            /*
            * AddressFamily.InterNetwork 寻址的方式(IPV4)
            * SocketType.Stream 传播方式
            * ProtocolType.Tcp 传输协议
            */
            #endregion
            Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            #region 2. 绑定端口IP(注释)
            /*
             * 指定地址和端口号
             * txtIP.Text       IP
             * txtPort.Text     端口号    
             * **/
            #endregion
            socket.Bind(new IPEndPoint(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text)));
            #region 3. 开启侦听(注释)
            /*
             * 链接:同时来了100链接请求,只能处理1个链接,队列里面放10个等待链接的客户端,其他的返回错误消息
             * 连接等待连接队列
             * **/
            #endregion
            socket.Listen(10);
            #region 4. 接收客户端连接(注释)
            /*
            * 开始接收客户端链接  (proxSocket 代理客户端执行对象)
            * 启动了一个新的线程,不断的接收客户端的连接,并且服务器端参数socket,传到委托里
            * 
            * ThreadPool(思ruai的胖)创建线程池
            * **/
            //AcceptClientConnect 委托方法 接收客户端数据
            //Accept() 每被执行一次,就代表一个客户端连接上来 阻塞当前线程
            //new WaitCallback  声明一个委托   把客户端方法传到委托里  将服务器端参数socket
            #endregion
            ThreadPool.QueueUserWorkItem(new WaitCallback(this.AcceptClientConnect),socket);//线程池
        }
        #endregion
        #region 向客户端发送数据
        public void AcceptClientConnect(object socket)
        {
            //对socket进行类型转换
            var serverSocket = socket as Socket;
            //提示
            this.AppendTextToTxtLog("服务情开始接收客户端连接");
            //调用Accept方法,接收客户端连接
            //循环接收客户端发送的数据
            while (true)
            {
                //proxSocket 代理Socket对象
                var proxSocket = serverSocket.Accept();
                this.AppendTextToTxtLog(string.Format("客户端:{0}链接上了", proxSocket.RemoteEndPoint.ToString())); ;
                ClentProxSocketList.Add(proxSocket);//只要一个客户端连接就把Socket代理放到集合中
                #region QueueUserWorkItem(异步线程池)
                //不停的接收当前连接的客户端发送来的消息
                /*
                 * Receive 阻塞当前线程
                 * WaitCallback 委托方法
                 * **/
                //使用异步线程池线程池
                #endregion
                ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveData), proxSocket);
            }
        }
        #endregion
        #region 文本框显示数据
        //往(日志)文本框上追加数据
        public void AppendTextToTxtLog(string txt)
        {
            //考虑跨线程访问控件
            if (txtLog.InvokeRequired)
            {
                //传入一个字符串不需要返回值
                txtLog.Invoke(new Action<string>(s =>
                {
                    this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);
                }), txt);
            }
            else
            {
                this.txtLog.Text = string.Format("{0}\r\n{1}", txt, txtLog.Text);//不考虑跨线程访问控件
            }
        }
        #endregion
        #region 接收客户端消息
        public void ReceiveData(object socket)
        {
            var proxSocket = socket as Socket;//转换Socket 对象
            //创建缓冲区 
            byte[] data = new byte[1024 * 1024];
            while (true)
            {
                //客户端发送过来的消息 len实际接收字节数(数据)
                int len = 0;
                #region 客户端异常退出
                try
                {
                    /*
                     * data:从什么时候开始写入数据
                     * 0:从哪开始写
                     * data.Length:写入最大长度
                     * **/
                    len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
                }
                catch (Exception)
                {
                    //客户端异常退出
                    AppendTextToTxtLog(string.Format("客户端:{0}非正常退出",
                                   proxSocket.RemoteEndPoint.ToString()));
                    ClentProxSocketList.Remove(proxSocket);//移除当前代理Socket对象
                    StopContent(proxSocket);
                    return;//让方法结束,终结当前接收客户端数据的异步线程
                }
                #endregion
                #region 客户端正常退出
                if (len <= 0)
                {
                    //客户端正常退出
                    AppendTextToTxtLog(string.Format("客户端:{0}正常退出",
                                   proxSocket.RemoteEndPoint.ToString()));
                    ClentProxSocketList.Remove(proxSocket);//移除当前代理Socket对象
                    StopContent(proxSocket);
                    return;//让方法结束,终结当前接收客户端数据的异步线程
                }
                #endregion
                #region 接收到的数据放到文本框(注释)
                //把接收到的数据放到文本框上去
                //把字节数组转换成字符串放到日志文本框进行显示
                /*
                 * data, 0  从哪个地方开始转
                 * len 一共转多少个长度
                 * **/
                #endregion
                string str =Encoding.Default.GetString(data, 0, len);
                AppendTextToTxtLog(string.Format("接收到客户端:{0}的消息是:{1}",
                                   proxSocket.RemoteEndPoint.ToString(),str));
            }
        }
        #endregion
        #region 发送消息(字符串)
        private void btnSendMsg_Click(object sender, EventArgs e)
        {
            foreach (var proxSocket in ClentProxSocketList)
            {
                if (proxSocket.Connected)//判断Socket 是否是连接状态 
                {
                    //获取需要发送到客户端的数据,转换成字节数组,进行发送
                    byte[] data = Encoding.Default.GetBytes(txtMsg.Text);
                    #region 验证发送类型
                    //对原始的数据数组加上协议的头部字节
                    byte[] result = new byte[data.Length+1];
                    //设置当前的协议头部字节 是1:1代表字符串
                    result[0] = 1;
                    #region BlockCopy(注释)
                    //把原始的数据放到最终的字节数组里去
                    /*
                     * Buffer.BlockCopy:块拷贝 
                     * data:从哪里烤
                     *:0:从data那个地方开始烤:从0开始烤
                     * result:拷到result里去
                     * 1:从1(result)开始烤
                     * data.Length:一共烤多少个字节数,把整个data.Length考进去
                     * **/
                    #endregion
                    Buffer.BlockCopy(data,0,result,1,data.Length);
                    #endregion
                    proxSocket.Send(result, 0, result.Length, SocketFlags.None);//从0开始发送  SocketFlags.None?
                }
            }
        }
        #endregion
        #region 发送闪屏
        private void btnSendShake_Click(object sender, EventArgs e)
        {
            //循环遍历ClentProxSocketList 集合
            foreach (var proxSocket in ClentProxSocketList)
            {
                if (proxSocket.Connected)//判断Socket 是否是连接状态 
                {
                    //每个Socket对象发送一个,new byte字节数组,初始化2
                    proxSocket.Send(new byte[] { 2},SocketFlags.None);
                }
            }
        }
        #endregion
        #region 发送文件
        private void btnSendFile_Click(object sender, EventArgs e)
        {
            //把要发送的文件读取出来,打开文件
            using (OpenFileDialog ofd = new OpenFileDialog())
            {
                if (ofd.ShowDialog()!=DialogResult.OK)
                {
                    return;
                }
                //读取文件数据
                byte[] data = File.ReadAllBytes(ofd.FileName);
                //将文件数据拷贝到result中
                byte[] result = new byte[data.Length + 1];
                //添加前缀 设置当前的协议头部字节 是3:3代表文件
                result[0] = 3;
                Buffer.BlockCopy(data, 0, result, 1, data.Length);
                //循环遍历ClentProxSocketList 集合
                foreach (var proxSocket in ClentProxSocketList)
                {
                    if (!proxSocket.Connected)//判断Socket 是否是连接状态 
                    {
                        continue;
                    }
                    //发送到客户端
                    //每个Socket对象发送一个,new byte字节数组,初始化2
                    proxSocket.Send(result, SocketFlags.None);
                }
            }
        }
        #endregion
        #region 关闭连接
        private void StopContent(Socket proxSocket)
        {
            try
            {
                if (proxSocket.Connected)
                {
                    proxSocket.Shutdown(SocketShutdown.Both);
                    proxSocket.Close(100);//如果100秒没有自动关闭,那就强行关闭
                }
            }
            catch (Exception)
            {
                throw new Exception("连接关闭失败!");
            }
        }
        #endregion
    }
}

客户端

1. 窗体设计

这个窗体设计的可能有些丑陋,但是实现的功能是齐全的,适用于初学Socket的学者,接下来进入代码编写阶段

4edc953e2c684bbe819ffa954c899c08.png

2. 客户端功能实现

using System;
using System.Drawing;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace SocketClient
{
    public partial class 客户端_MainFrm : Form
    {
        #region 属性定义
        //绑定当前Socket(全局)
        public Socket ClientSocket { get; set; }
        #endregion
        public 客户端_MainFrm()
        {
            InitializeComponent();
            //不捕获调用线程错误,此处应该使用异步调用线程池
            Control.CheckForIllegalCrossThreadCalls = false;
        }
        #region 客户端连接服务器端
        private void btnConnect_Click(object sender, EventArgs e)
        {
            #region 1. 创建Socket对象
            Socket socket = new Socket
               (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            ClientSocket = socket;//赋值全局socket
            #endregion
            #region 2. 连接服务器端
            try
            {
                //获取IP地址和端口
                socket.Connect(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text));
            }
            catch (Exception err)
            {
                MessageBox.Show("重新连接");
                //Thread.Sleep(500);//设置连接等待时间
                //btnConnect_Click(this,e);
                return;
            }
            #endregion
            #region 3. 发送消息(接收服务器端消息)
            Thread thread = new Thread(new ParameterizedThreadStart(ReceiveData));
            thread.IsBackground = true;
            thread.Start(ClientSocket);
            #endregion
        }
        #endregion
        #region 客户端接收数据
        public void ReceiveData(object socket)
        {
            var proxSocket = socket as Socket;//转换Socket 对象
            //创建缓冲区 
            byte[] data = new byte[1024 * 1024];
            while (true)
            {
                //客户端发送过来的消息 len实际接收字节数(数据)
                int len = 0;
                #region 客户端异常退出
                try
                {
                    /*
                    * data:从什么时候开始写入数据
                    * 0:从哪开始写
                    * data.Length:写入最大长度
                    * **/
                    len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
                }
                catch (Exception)
                {
                    //客户端异常退出
                    //AppendTextToTxtLog(string.Format("服务器端:{0}非正常退出",
                    //               proxSocket.RemoteEndPoint.ToString()));
                    StopContent();//关闭连接
                    return;//让方法结束,终结当前接收客户端数据的异步线程
                }
                #endregion
                #region 客户端正常退出
                if (len <= 0)
                {
                    //服务器端正常退出
                    AppendTextToTxtLog(string.Format("服务器端:{0}正常退出",
                                   proxSocket.RemoteEndPoint.ToString()));
                    StopContent();//关闭连接
                    return;//让方法结束,终结当前接收客户端数据的异步线程
                }
                #endregion
                #region 验证接收类型
                #region 接收的是字符串(1 是字符串)
                if (data[0] == 1)
                {
                    string strMes = ProcessRecieveString(data);
                    AppendTextToTxtLog(string.Format("接收到服务器端:{0}的消息是:{1}",
                                   proxSocket.RemoteEndPoint.ToString(), strMes));
                }
                #endregion
                #region 接收闪屏(2 是闪屏)
                else if (data[0] == 2)//判断字节头是否为2
                {
                    SHake();//抖动窗口
                }
                #endregion
                #region 接收文件(3 是文件)
                else if (data[0] == 3)//判断字节头是否为3
                {
                    ProcessRecieveFile(data,len);//接收文件
                }
                #endregion
                #endregion
            }
        }
        #endregion
        #region 发送
        private void btnSendMsg_Click(object sender, EventArgs e)
        {
            if (ClientSocket.Connected)
            {
                byte[] data = Encoding.Default.GetBytes(txtMsg.Text);//获取需要发送的数据,转换成字节数组
                ClientSocket.Send(data, 0, data.Length, SocketFlags.None);//从0开始发送  SocketFlags.None?
            }
        }
        #endregion
        #region 向服务器端发送数据
        public void AppendTextToTxtLog(string txt)
        {
            //考虑跨线程访问控件
            if (txtLog.InvokeRequired)
            {
                //传入一个字符串不需要返回值
                txtLog.Invoke(new Action<string>(s =>
                {
                    this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);
                }), txt);
            }
            else
            {
                this.txtLog.Text = string.Format("{0}\r\n{1}", txt, txtLog.Text);//不考虑跨线程访问控件
            }
        }
        #endregion
        #region 发送闪屏(抖动窗口)
        private void SHake()
        {
            //把窗体最原始的点记住
            Point o = this.Location;
            //随机设定在某个位置开始左右上下抖动
            Random r = new Random();
            for (int i = 0; i < 50; i++)
            {
                //设置抖动位置
                this.Location = new Point(r.Next(o.X-10,o.X+10),
                    r.Next(o.Y-10,o.Y+10));
                Thread.Sleep(200);
                //还原初始位置
                this.Location = o;
            }
        }
        #endregion
        #region 处理接收到的文件
        public void ProcessRecieveFile(byte[] data,int len)  
        {
            using (SaveFileDialog sfd=new SaveFileDialog())
            {
                //设置发送文件后缀
                sfd.DefaultExt = "txt";
                //设置文件类型
                sfd.Filter = "文本文件(*.txt)|*.txt|所有文件(*.*)|*.*";
                if (sfd.ShowDialog(this) != DialogResult.OK)
                {
                    return;
                }
                #region BlockCopy(注释)
                /*
                 * Buffer.BlockCopy:块拷贝 
                * data:从哪里烤
                *:0:从data那个地方开始烤:从0开始烤
                * result:拷到result里去
                * 1:从1(result)开始烤
                * data.Length:一共烤多少个字节数,把整个data.Length考进去
                 * **/
                #endregion
                byte[] fileData = new byte[len - 1];
                Buffer.BlockCopy(data, 1, fileData, 0, len - 1);
                File.WriteAllBytes(sfd.FileName, fileData);
            }
        }
        #endregion
        #region 处理接收到的字符串
        private string ProcessRecieveString(byte[] data)
        {
            //把实际的字符串拿到
            string str = Encoding.Default.GetString(data, 1, data.Length-1);
            return str;
        }
        #endregion
        #region 关闭客户端事件(如果未关闭,自动调用关闭方法)
        private void MainFrm_FormClosing(object sender, FormClosingEventArgs e)
        {
            //判断是否已连接,如果连接,那么就关闭连接
            StopContent();
        }
        #endregion
        #region 关闭连接
        private void StopContent()
        {
            try
            {
                if (ClientSocket.Connected)
                {
                    ClientSocket.Shutdown(SocketShutdown.Both);
                    ClientSocket.Close(100);//如果100秒没有自动关闭,那就强行关闭
                }
            }
            catch (Exception)
            {
                throw new Exception("关闭连接失败!");
            }
        }
        #endregion
    }
}

这就是整个程序代码实现与窗体设计,不明白或有报错的地方私信!!!

目录
相关文章
|
7月前
|
安全 网络协议 Java
Thread类的用法 && 线程安全 && 多线程代码案例 && 文件操作和 IO && 网络原理初识 &&UDP socket
Thread类的用法 && 线程安全 && 多线程代码案例 && 文件操作和 IO && 网络原理初识 &&UDP socket
40 0
|
监控 网络协议 Perl
[原创]结合案例深入解析orphan socket产生与消亡(一)
本文看点:结合服务器运行案例和TCP代码分析orphan socket产生与消亡以及对系统的影响。精彩的部分在(二)细节分析章节。 ##问题背景 tengine服务器发生过多次orphan socket数量很多的情况,例如有一次使用ss -s命令查看: ``` $ss -s T
8055 0
|
7月前
|
前端开发 JavaScript Java
SpringBoot整合Socket实战案例,实现单点、群发,1对1,1对多
本篇内容: 后端 + 前端简单HTML页面 功能场景点: 群发,所有人都能收到 局部群发,部分人群都能收到 单点推送, 指定某个人的页面
|
8月前
|
弹性计算 负载均衡 监控
记一次socket read导致业务线程阻塞的案例分析
记一次socket read导致业务线程阻塞的案例分析
250 3
|
10月前
|
存储 安全
socket编程应用案例详细分析
socket编程应用案例详细分析
82 0
|
前端开发 JavaScript Java
Springboot 整合 Socket 实战案例 ,实现 单点发送、广播群发,1对1,1对多
Springboot 整合 Socket 实战案例 ,实现 单点发送、广播群发,1对1,1对多
1184 0
Springboot 整合 Socket 实战案例 ,实现 单点发送、广播群发,1对1,1对多
java.nio.* 篇(1) FileChannel AsynchronousFileChannel ServerSocket Socket 使用案例
java.nio.* 篇(1) FileChannel AsynchronousFileChannel ServerSocket Socket 使用案例
|
JavaScript Windows
JS案例:Socket聊天室(两种方式)
JS案例:Socket聊天室(两种方式)
58 0
|
存储 分布式计算 网络协议
Sparkstreaming 案例 — socket 回顾 | 学习笔记
快速学习 Sparkstreaming 案例 — socket 回顾
135 0
Sparkstreaming 案例 — socket 回顾 | 学习笔记
|
网络协议 Java API
java网络编程(2)socket通信案例(TCP和UDP)
java生下来一开始就是为了计算机之间的通信,因此这篇文章也将开始介绍一下java使用socket进行计算机之间的通信,在上一篇文章中已经对网络通信方面的基础知识进行了总结,这篇文章将通过代码案例来解释说明。
226 0
java网络编程(2)socket通信案例(TCP和UDP)