简介

  • 练习C#套接字(socket)通信的时候,突然产生一个想法,制作一款软件可以远程关闭他人的电脑,于是便着手写了这个程序。

环境需求

  • 开发:C#
  • 运行环境:.net 4.8及以上

原理分析

  • 我们使用socket的时候,客户端和服务器可以进行通讯,想要达到控制他人电脑的效果,必然需要通过网络进行通讯,在这里我将控制端称作服务端,被控制端称作客户端。服务端配置好IP地址端口(port),开始监听;客户端同样配置好IP端口(port),启动客户端,客户端根据IP和端口对服务器进行通讯,建立连接,服务器收到通讯请求,返回一个反馈,同时记录下该客户端的IP和端口信息。此时,我们可以在服务器端可以查到哪些设备已经跟我们建立了连接。
  • 关闭其实原理很简单,通过客户端调用客户机的命令提示符程序,输入关机指令即可。服务器向客户端发起关机指令,客户端收到指令进行执行,服务器检测到客户端连接中断后,从在线列表中将客户端删除。

运行效果

  • 服务端启动

server

  • 客户端连接

running

  • 客户端离线

turn off

开始

  • 服务端
    为了操作方便,我们创建一个窗口程序,将工程命名为controlserver

绘制好操作界面

draw

由于会使用到配置文件,所以我们新建一个common.cs进行配置文件读取功能的编码

using System.Text;
using System.Runtime.InteropServices;

namespace ControlServer
{
    class common
    {
        [DllImport("kernel32")]//调用DLL
        private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);
        [DllImport("kernel32")]
        private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);
/*读取配置文件ini*/
        public static string readini(string group, string key, string default_value, string filepath)
        {
            StringBuilder temp = new StringBuilder();
            GetPrivateProfileString(group, key, default_value, temp, 255, filepath);
            return temp.ToString();
        }
    }
}

定义三个静态变量,分别用于socket,记录连接和线程

static Socket socketwatch = null;
static Dictionary<string, Socket> clientConnectionItems = new Dictionary<string, Socket> { };
static Thread myThread = null;

取消textbox的跨线程检测

public server()
        {
            InitializeComponent();
            TextBox.CheckForIllegalCrossThreadCalls = false;//取消检测
        }

监听部分

        private void startlisten()
        {
            /*读取配置文件中的ip与端口*/
            string IpValue = common.readini("basic","ip","",".\\config.ini");
            string PortValue = common.readini("basic", "port", "", ".\\config.ini");
            socketwatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress iP = IPAddress.Parse(IpValue);
            int port = int.Parse(PortValue);
            IPEndPoint iPEnd = new IPEndPoint(iP, port);
            /*绑定节点并开始监听*/
            socketwatch.Bind(iPEnd);
            socketwatch.Listen(30);
            Thread threadlisten = new Thread(ListenConnection);
            threadlisten.IsBackground = true;
            threadlisten.Start();
            myThread = threadlisten;
            Logs.Text += "监听开始!\r\n";
            Logs.Text += $"监听地址:{IpValue}:{PortValue}\r\n";
            this.statues.Text = "Listening...";
            this.statues.ForeColor = Color.LightGreen;
        }
/*监听*/
        private void ListenConnection()
        {
            Socket connection = null;
            while (true)
            {
                try
                {
                    connection = socketwatch.Accept();
                }
                catch (Exception ex)
                {
                    Logs.Text += ex.Message + "\r\n";
                    break;
                }
                IPAddress ClientiP = (connection.RemoteEndPoint as IPEndPoint).Address;
                int ClientPort = (connection.RemoteEndPoint as IPEndPoint).Port;
                string sendmsg = $"OK";
                byte[] arrSendMsg = Encoding.UTF8.GetBytes(sendmsg);
                connection.Send(arrSendMsg);
                string remoteEndPoint = connection.RemoteEndPoint.ToString();
                Logs.Text += $"成功与客户端 {remoteEndPoint} 建立连接!\r\n";
                clientConnectionItems.Add(remoteEndPoint, connection);
                Logs.Text += $"当前总连接数:{clientConnectionItems.Count()}\r\n";
                IPEndPoint netpoint = connection.RemoteEndPoint as IPEndPoint;
                ParameterizedThreadStart pts = new ParameterizedThreadStart(recv);
                Thread thread = new Thread(pts);
                thread.IsBackground = true;
                thread.Start(connection);
                RefreshList();
            }
        }
/*接受消息*/
        private void recv(object socketclientpara)
        {
            Socket socketServer = socketclientpara as Socket;
            while (true)
            {
                try
                {
                    byte[] arrServerRecMsg = new byte[1024 * 1024];
                    int length = socketServer.Receive(arrServerRecMsg);
                    string strSRecMsg = Encoding.UTF8.GetString(arrServerRecMsg, 0, length);
                    if (strSRecMsg == "1")
                    {
                        socketServer.Send(Encoding.UTF8.GetBytes("OK"));
                    }
                }
                catch (Exception ex)
                {
                    Logs.Text += $"客户端{socketServer.RemoteEndPoint.ToString()}连接中断" + ex.Message + "\r\n";
                    clientConnectionItems.Remove(socketServer.RemoteEndPoint.ToString());
                    Logs.Text += $"当前连接数:{clientConnectionItems.Count()}\r\n";
                    socketServer.Close();
                    RefreshList();
                    break;
                }
            }
        }

接下来就是服务端一些界面上的显示与操作代码,大致参考一下即可

        private string GetTime()//获取时间
        {
            DateTime currentTime = new DateTime();
            currentTime = DateTime.Now;
            return currentTime.ToString("yyyy-MM-dd HH:mm:ss");
        }

        private void LoadClientList()//装载list
        {
            int i = 0;
            foreach (var EndPoint in clientConnectionItems)
            {
                string[] ipPort = EndPoint.Key.ToString().Split(':');
                this.ClientList.Items.Add(ipPort[0]);
                this.ClientList.Items[i].SubItems.Add(ipPort[1]);
                this.ClientList.Items[i].SubItems.Add("YES");
                this.ClientList.Items[i].SubItems.Add(GetTime());
                this.ClientList.Items[i].SubItems.Add("None");
                i++;
            }
        }
        private void RefreshList()//刷新list
        {
            ClientList.Items.Clear();
            LoadClientList();
        }

        private void StartListenButton_Click(object sender, EventArgs e)//启动按钮
        {
            startlisten();
            RefreshList();
        }

        private void ClientList_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.SeletedIp.Text = this.ClientList.FocusedItem.SubItems[0].Text;
            this.SeletedPort.Text = this.ClientList.FocusedItem.SubItems[1].Text;
        }

        private void TurnOffButton_Click(object sender, EventArgs e)//关闭按钮
        {
            string ipvalue = this.SeletedIp.Text + ":" +this.SeletedPort.Text;
            try
            {
                clientConnectionItems[ipvalue].Send(Encoding.UTF8.GetBytes("0"));
            }
            catch
            {
                MessageBox.Show("找不到对应的客户端!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            
        }
  • 客户端
    由于客户端不需要显示出来,我们可以创建一个console程序,当然,也可以创建窗口程序

首先我们重写一下程序可见性

        protected override void SetVisibleCore(bool a)
        {
            base.SetVisibleCore(false);
        }

收发消息

        private void GetConnection()
        {
            this.Hide();
            string IpValue = common.readini("basic", "ip", "", ".\\config.ini");
            string PortValue = common.readini("basic", "port", "", ".\\config.ini");
            socketclient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress address = IPAddress.Parse(IpValue);
            int port = int.Parse(PortValue);
            IPEndPoint iPEnd = new IPEndPoint(address, port);
            try
            {
                socketclient.Connect(iPEnd);
            }
            catch (Exception)
            {

            }
            threadclient = new Thread(recv);
            threadclient.IsBackground = true;
            threadclient.Start();
        }

        private void recv()
        {
            int x = 0;
            while (true)
            {
                try
                {
                    byte[] arrRecvmsg = new byte[1024 * 1024];
                    int length = socketclient.Receive(arrRecvmsg);
                    string strRevMsg = Encoding.UTF8.GetString(arrRecvmsg, 0, length);
                    if (x == 1)
                    {
                        if (strRevMsg == "OK")
                        {
                            WaitForTime();
                        }
                        else
                        {
                            ShutdownWindows();
                        }
                    }
                    else
                    {
                        x = 1;
                    }
                }
                catch (Exception)
                {
                    //MessageBox.Show(ex.Message, "error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }
        private void sendmsg(string sendMsg)
        {
            byte[] arrClientSendMsg = Encoding.UTF8.GetBytes(sendMsg);
            socketclient.Send(arrClientSendMsg);
        }

关机指令

        private void ShutdownWindows()
        {
            string str = "shutdown -s -t 0";
            Process p = new Process();
            p.StartInfo.FileName = "cmd.exe";
            p.StartInfo.UseShellExecute = false;    //是否使用操作系统shell启动
            p.StartInfo.RedirectStandardInput = true;//接受来自调用程序的输入信息
            p.StartInfo.RedirectStandardOutput = true;//由调用程序获取输出信息
            p.StartInfo.RedirectStandardError = true;//重定向标准错误输出
            p.StartInfo.CreateNoWindow = true;//不显示程序窗口
            p.Start();//启动程序
            p.StandardInput.WriteLine(str + "&exit");
            p.StandardInput.AutoFlush = true;
            p.WaitForExit();
            p.Close();
        }

结语

  • 本程序主要难点在于客户机与服务器通讯保持和信息交换,处理好这两点,可以依次类推,以此为基础编写各种C/S模式程序

如果我的文章对你有帮助的话,不妨打赏我一下哦~