C#Socket 服务器&客户端学习笔记

发布于:2023-02-12 ⋅ 阅读:(578) ⋅ 点赞:(0)

C#Socket 服务器&客户端学习笔记
学习代码中加入自己的注释和理解,完全可以复制使用,但是窗体要自己画,根据代码里的名称去修改窗体内空间的Name属性
*****************************************************************************************************************************服务器窗体
在这里插入图片描述
服务器代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _06Socket监听
{
public partial class Form1 : Form
{
//窗体加载初始化
public Form1()
{
InitializeComponent();
}

    //创建端口,开始监听
    private void btnStart_Click(object sender, EventArgs e)//Protocol:协议,Stream:流
    {
        Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//当电机开始监听的时候,在服务器端创建一个负责监IP地址和端口号的Socket
        IPAddress ip = IPAddress.Any;                                                                    //获取计算机的IP
        IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));                            //创建端口号对象,网络终结点表示为 IP 地址和端口号
        socketWatch.Bind(point);                                                                         //监听网络终结点的数据
        ShowMsg("监听成功,等待客户端连接进来");
        socketWatch.Listen(10);                                                                          //限制连接数量为10,防止服务器过载
        Thread th = new Thread(Listen);                                                                  //新建线程,负责等待连接
        th.IsBackground = true;                                                                          //将线程设置为后台线程,特点是前台程序关闭后,后台线程自动关闭
        th.Start(socketWatch);                                                                           //启动线程,告诉CPU,我可以被执行了,具体什么时候执行,由CPU决定
    }

    //公共字段
    /// <summary>
    /// 等待客户端的连接,并创建与之通信用的Socket
    /// </summary>
    Socket socketSend;

    //服务器监听方法
    void Listen(object o)                                                        //如果线程有参数,参数类型必须是object类型
    {
        Socket socketWatch = o as Socket;                                        //将object类型数据强转成Socket类型
        while (true)
        {
            try                                                                  //将网络连接中可能出错的时候,什么都不显示,相当于卡死状态
            {
                socketSend = socketWatch.Accept();                               //一直等待客户端的连接
                dicSocket.Add(socketSend.RemoteEndPoint.ToString(), socketSend); // 将远程连接的客户端的IP地址和Socket存入集合中
                cboUser.Items.Add(socketSend.RemoteEndPoint.ToString());         //将远程连接的客户端的IP地址和端口存储在下拉筐中
                ShowMsg(socketSend.RemoteEndPoint.ToString() + ":" + "连接成功");//连接成功后,显示客户端的IP和端口
                Thread th = new Thread(Recive);                                  //开一个新线程不停的去接受客户端发过来的数据
                th.IsBackground = true;                                          //设定线程为后台线程
                th.Start(socketSend);                                            //线程启动,线程内参数为连接的客户端
            }
            catch { }
        }
    }

    //新建多客户端键值对结构
    /// <summary>
    /// 将远程连接的客户端的IP地址和Socket存入集合中
    /// </summary>
    Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();//新建一个键值对集合,键是String类型数据,值是Socket类型数据
    
    //数据接收方法
    /// <summary>
    /// 服务器端不停的接受客户端发过来的消息
    /// </summary>
    /// <param name="o"></param>
    void Recive(object o)//持续接收客户端数据
    {
        Socket socketSend = o as Socket;
        while (true)
        {
            try                                                      //将网络连接中可能出错的时候,设么都不显示,相当于卡死状态
            {
                byte[] buffer = new byte[1024 * 1024 * 2];           //新建一个字节数组做缓存,客户端连接成功后,服务器接受客户端发来的消息都存在这个缓存里
                int r = socketSend.Receive(buffer);                  //实际接收到的有效字节数
                if (r == 0)                                          //判断接受到是数据长度为0时,代表客户端被关闭,退出接受数据的循环
                { break; }                                           //客户端关闭,退出循环接收数据黄铜
                string str = Encoding.UTF8.GetString(buffer, 0, r);  //将接收到的数据按编码转换成字符串
                ShowMsg(socketSend.RemoteEndPoint + ":" + str);      //信息显示,显示客户端的IP地址和端口号
            }
            catch { }
        }
    }

    //信息显示方法
    void ShowMsg(string str)                                        //信息显示方法,形参是显示的字符串类型数据
    {
        txtLog.AppendText(str + "\r\n");                            //在文本中累计添加显示
    }
   
    //窗体加载开始事件
    /// <summary>
    /// 加载Form时,不检查多线程间的调用
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Form1_Load(object sender, EventArgs e)
    {
        Control.CheckForIllegalCrossThreadCalls = false;            //关闭系统中线程相互调用的检查报错
    }
   
    //消息发送事件
    /// <summary>
    /// 服务器给客户端发消息
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnSend_Click(object sender, EventArgs e)
    {
        try
        {
            string str = txtMsg.Text;                               //获取文本框中输入的信息
            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);//将获取的文本信息转换成字节数组
            List<byte> list = new List<byte>();                     //创建泛型集合去存放字节数组            
            list.Add(0);                                            //在集合的第一位添加类型,0:文字,1:文件,2:震动            
            list.AddRange(buffer);                                  //加入原有需要发送的内容           
            byte[] newBuffer = list.ToArray();                      //将集合转换为新的字节数组  
            string ip = cboUser.SelectedItem.ToString();            // 获得用户在下拉框中选择的IP地址,键
            dicSocket[ip].Send(newBuffer);                          //键对应的值,就是连接到的端口(socketWatch.Accept())去发送数据,发送的内容是新的带文件类型标志的字符数组
        }
        catch
        {
        }

    }
    
    //文件打开事件
    /// <summary>
    /// 选择要发送的文件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void button2_Click(object sender, EventArgs e)
    {
        OpenFileDialog ofd = new OpenFileDialog();                        //新建文件打开功能实例
        ofd.InitialDirectory = @"C:\Users\admin\Desktop";                 //文件打开的路径
        ofd.Title = "请选择要发送的文件";                                 //文件打开窗口的标题
        ofd.Filter = "所有文件|*.*";                                      //文件打开选择的文件类型
        ofd.ShowDialog();                                                 // 用默认的所有者运行通用对话框,返回结果: System.Windows.Forms.DialogResult.OK 如果用户单击确定在对话框中;否则为 System.Windows.Forms.DialogResult.Cancel。         
        txtPath.Text = ofd.FileName;                                      //将文件的完整路径显示在文本框中
    }

    //发送文件事件
    private void btnSendFile_Click(object sender, EventArgs e)
    {
        string path = txtPath.Text;                                                                     //从上面显示的文本框中获取要发送的完整的文件路径
        using (FileStream fsRead = new FileStream(path, FileMode.Open, FileAccess.Read))                //将文件转换成字节流,using在线程结束后会自动释放内存,
                                                                                                        //新建文件流,参数包括,路径,文件模式(打开文件),访问方式(读取)
        {
            byte[] buffer = new byte[1024 * 1024 * 5];                                                  //新建读取数据流的缓存大小
            int r = fsRead.Read(buffer, 0, buffer.Length);                                              //将数据流放入缓存,并返回一个读取的大小
            List<byte> list = new List<byte>();                                                         //新建一个泛型集合,用来组合文件类型的定义值和读取的文件的数据流字节数组
            list.Add(1);                                                                                //将文件类型放入泛型结合的第一位
            list.AddRange(buffer);                                                                      //将读取到的文件的字符数组加入到泛型集合中
            byte[] newBuffer = list.ToArray();                                                          //将组合后的泛型结合重新转换成一个字符数组,新数组的长度比原有的字符数组长度+1 
            dicSocket[cboUser.SelectedItem.ToString()].Send(newBuffer, 0, r + 1, SocketFlags.None);     //下拉框中连接的客户端去发送新的字符数组内容,参数(新数组,从0开始,到原有字符数组长度+1,通讯标志:无)
        }
    }

    //震动发送事件
    private void btnZD_Click(object sender, EventArgs e)
    {
        byte[] buffer = new byte[1];                                                                      //新建一个字符数组,长度为1,因为只要发个标志数据给客户端就行
        buffer[0] = 2;                                                                                     //定义了第一个字符的数据为2就代表震动
        dicSocket[cboUser.SelectedItem.ToString()].Send(buffer);                                          //还是将下拉菜单中选择的客户端发送字符数组
    }
}

}
客户端窗体****************
在这里插入图片描述
客户端代码****************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.IO;

namespace _07Client
{
public partial class Form1 : Form
{
//窗体加载初始化
public Form1()
{
InitializeComponent();
}

    //公共字段
    Socket socketSend;                                                                                //新建通讯实例对象

    //按钮事件,创建通讯端口,开始接收服务器数据
    private void btnStart_Click(object sender, EventArgs e)                                           //客户端连接服务器按钮
    {
        try
        {
            socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建Socket通讯连接
            IPAddress ip = IPAddress.Parse(txtServer.Text);                                          //将文本框中的数据转成IP地址类型数据
            IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));                    //新建网络终结点,包括了IP地址和端口号
            socketSend.Connect(point);                                                               //连接服务器
            showMsg("服务器连接成功");                                                              //显示服务器连接成功的信息
            Thread th = new Thread(Recive);                                                         //开启一个线程,不停的接收服务器发来的消息
            th.IsBackground = true;                                                                  //将线程设置为后台线程
            th.Start();                                                                              //线程启动
        }
        catch { }
    }

    //数据接收方法
    /// <summary>
    /// 不停的接收服务器发来的消息
    /// </summary>
    void Recive()                                                                                     //接收服务器发来的信息
    {
        try
        {
            while (true)
            {
                byte[] buffer = new byte[1024 * 1024 * 3];                                           //新建一个用于接收的缓存字节数组
                int r = socketSend.Receive(buffer);                                                  //将接收到的数据存入缓存,并返回一个值,代表接收数据的大小
                if (r == 0)                                                                          //如果接收到的数据长度为0,代表连接断开,则跳出循环,不接收数据
                { break; }

                int n = buffer[0];//判断接受到的数据的第一位,去判断收到的是什么类型的数据,0:文字,1:文件,2:震动

                if (n == 0)                                                                                  //如果第一个字符是0,代表接受到的数据类型是文字
                {
                    string s = Encoding.UTF8.GetString(buffer, 1, r - 1);                                    //将接收的数据解码成一个字符串,格式是UTF-8,从接收的0位开始到整个数据长度位置结束
                    showMsg(socketSend.RemoteEndPoint + ":" + s);                                            //显示数据信息,包括服务器的IP地址和端口,+ 接收到的内容
                }
                else if (n == 1)                                                                             //如果第一个字符是1,代表发送过来的数据时文件数据
                {
                    SaveFileDialog sfd = new SaveFileDialog();                                               //新建一个文件保存的实例化对象
                    sfd.InitialDirectory = @"C:\Users\admin\Desktop";                                        //要保存文件的路径
                    sfd.Title = "请选择要保存的文件";                                                        //弹出对话框的标题
                    sfd.Filter = "所有文件|*.*";                                                             //弹出对话框的文件类型
                    sfd.ShowDialog(this);                                                                    //开始弹出对话框
                    string path = sfd.FileName;                                                              //保存文件的路径,用于下面的方法中
                    using (FileStream fsWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))//using便于释放内存,新建一个文件对象,参数(路径,文件模式.打开或新建,文件方式.写文件
                    {
                        fsWrite.Write(buffer, 1, r - 1);                                                      //写文件的内容是接收到的字符数组,从第1位开始写文件(因为第0个代表的是文件类型),写的长度是接收长度-1(因为第一个文件类型不用写入)
                    }
                    MessageBox.Show("保存成功");                                                              //显示写完成的信息
                }
                else if (n == 2)                                                                              //如果第一个字符是2,代表接受到的数据类型是震动
                {
                    ZD();                                                                                     //调用震动方法
                }
            }
        }
        catch { }
    }

    //窗体震动方法
    /// <summary>
    /// 震动方法
    /// </summary>
    void ZD()
    {
        for (int i = 0; i < 500; i++)            //循环震动,就是不停的变换文件框的位置
        {
            this.Location = new Point(200, 200);
            this.Location = new Point(220, 220);
        }
    }

    //信息显示方法
    void showMsg(string str)
    { txtLog.AppendText(str + "\r\n"); }                                                                       //信息显示

    //发送信息给服务器事件
    /// <summary>
    /// 客户端给服务器发送消息
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnSend_Click(object sender, EventArgs e)                                           //客户端给服务器发送数据
    {
        string str = txtMsg.Text.Trim();                                                             //新建一个字符串,从文本框获得数据,并将字符串的开头和结尾删除所有空白字符后剩余的字符串                                               
        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);                                     //将字符串按UTF-8的格式转换成字节数组
        socketSend.Send(buffer);                                                                     //数据发送,
    }

    //窗体加载时,取消多线程调用的检查,防止保存
    private void Form1_Load(object sender, EventArgs e)
    {
        Control.CheckForIllegalCrossThreadCalls = false;                                              //在窗体加载时,关闭多线程之间的调用检查,解决报错问题
    }
}

}

本文含有隐藏内容,请 开通VIP 后查看

今日签到

点亮在社区的每一天
去签到