C# socket编程服务端部署

C# Socket网络编程之服务端部署

前前后后弄了差不多一个星期,终于把这玩意儿给搞出来了,其实谈不上多复杂,但是从0开始还是要走很多的弯路,现在也没有博客或者视频教程教弄这个东西,所以写下这篇博客方便以后要用的时来看看。


总的来说需要掌握的东西不多。

  • C# socket网络编程
  • Linux基本命令
  • TCP/IP协议

以下是我的服务端代码(由于我是用的winform框架写的,代码有两部分)

program.cs

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Drawing;

namespace ChatoServer
{
    static class Program
    {
        //1.创建套接字
        static Socket serverSocket = null;
        static IPAddress ip = null;
        static IPEndPoint point = null;

        static Dictionary allClientSockets = null;

        static MainForm form = null;
        /// 
        /// 应用程序的主入口点。
        /// 
        [STAThread]//开启单线程
        //表示这个Main程序被一个单线程套间包住,且Main的执行,一次只能被一个线程占用,这个线程未执行完,别的线程是没办法调用的。
        static void Main()
        {
            //在 C# 中,System.Threading.Thread 类用于线程的工作。它允许创建并访问多线程应用程序中的单个线程。进程中第一个被执行的线程称为主线程。
           // 当 C# 程序开始执行时,主线程自动创建。使用 Thread 类创建的线程被主线程的子线程调用。您可以使用 Thread 类的 CurrentThread 属性访问线程
            allClientSockets = new Dictionary();
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            form = new MainForm(bListenClick, bSendClick);
            Application.Run(form);

        }

        static EventHandler bListenClick = SetListen;//启动服务器相当于
        static EventHandler bSendClick = SendMsg;

        static void SetListen(object sender, EventArgs e)
        {
            
            ip = IPAddress.Parse(form.GetIPText());//获得IP
            point = new IPEndPoint(ip, form.GetPort());//获得端口
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//设置套接字
            try {
                serverSocket.Bind(point);//绑定
                serverSocket.Listen(20);//监听,最多20个
                form.Println($"服务器开始在 {point} 上监听。");

                Thread thread = new Thread(Listen);
                thread.SetApartmentState(ApartmentState.STA);//当前线程状态为单线程
                //当初始化一个线程,把Thread.IsBackground=true的时候,指示该线程为后台线程。后台线程将会随着主线程的退出而退出。
                thread.IsBackground = true;
                thread.Start(serverSocket);//开始线程的的执行
            }
            catch (Exception ex) {
                form.Println($"错误: {ex.Message}");
            }
        }

        static void Listen(object so)
        {
            Socket serverSocket = so as Socket;
            while (true)//不断接收来着客户端的请求
            {
                try {
                    //等待连接并且创建一个负责通讯的socket
                    Socket clientSocket = serverSocket.Accept();
                    //获取链接的IP地址
                    string clientPoint = clientSocket.RemoteEndPoint.ToString();
                    form.Println($"{clientPoint} 上的客户端请求连接。");
                    allClientSockets.Add(clientPoint, clientSocket);//加入字典--一个IP对应一个套接字。。。。
                    form.ComboBoxAddItem(clientPoint);//把连接的客户端的IP加入下拉列表方便通讯。。。
                    string msg = "ip=" + clientPoint;
                    //设置缓冲区,并把IP:具体IP的字符串设置成UTF-8编码的字节数组。。。
                    byte[] sendee = Encoding.UTF8.GetBytes(msg);
                    //相当于Python的range。。。。
                    foreach (string ip in allClientSockets.Keys)
                        if (ip != clientPoint)
                        {
                            //ip为当前遍历到的IP,clientPoint为新加进来的IP
                            allClientSockets[ip].Send(sendee);  //向所有客户端发送新客户端连接的ip信息
                            byte[] sendeeIP = Encoding.UTF8.GetBytes("ip=" + ip);
                            allClientSockets[clientPoint].Send(sendeeIP);  //向新客户端发送所有已有客户端的ip信息
                        }

                    //开启一个新线程不停接收消息
                    Thread thread = new Thread(Receive);
                    thread.IsBackground = true;
                    thread.SetApartmentState(ApartmentState.STA);
                    thread.Start(clientSocket);
                }
                catch(Exception e) {
                    form.Println($"错误: {e.Message}");
                    break;
                }
            }
        }

        static void Receive(object so)
        {
            //接收缓冲区
            byte[] buf = new byte[1024 * 1024 * 2];
            byte[] cacheBuf = new byte[1024 * 1024 * 2];
            Socket clientSocket = so as Socket;
            string clientPoint = clientSocket.RemoteEndPoint.ToString();
            while (true) {
                try {
                    //获取发送过来的消息容器
                    //Socket.Receive返回收到的字节数。
                    int len = clientSocket.Receive(buf);
                    //有效字节为0则跳过
                    if (len == 0) break;

                    if (buf[0] == 1) //如果接收的字节数组的第一个字节是1,说明接收的是文件
                    {
                        form.Println("来自" + clientSocket.RemoteEndPoint + ": 的文件buf正在接收");
                        Buffer.BlockCopy(buf, 0, cacheBuf, 0, 1024 * 1024 * 2);
                        Thread thread = new Thread(Receive);
                        thread.IsBackground = true;
                        thread.SetApartmentState(ApartmentState.STA);
                        thread.Start(clientSocket);
                    }
                    else if(buf[0] == 2)
                    {
                        form.Println("来自" + clientSocket.RemoteEndPoint + ": 的图片buf正在接收");
                        //发送并显示图片
                        MemoryStream ms = new MemoryStream();
                        ms.Write(buf, 1, len - 1);
                        Image img = Image.FromStream(ms);
                        form.SetPic(img);
                        form.Println("来自" + clientSocket.RemoteEndPoint + ": 的图片接收成功");
                    }
                    else
                    {
                        string s = Encoding.UTF8.GetString(buf, 0, len);
                        form.Println($"{clientPoint}: {s}");
                        if (s.StartsWith("message="))//判断字符串是否以message=开头
                            foreach (String t in allClientSockets.Keys)
                            {
                                if (clientPoint != t)
                                {
                                    byte[] sendee = Encoding.UTF8.GetBytes($"{clientPoint}: {s}");
                                    allClientSockets[t].Send(sendee);
                                }
                            }
                        else if (s.StartsWith("ip="))
                        {
                            string ip = Regex.Match(s, @"\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}:\d{1,5}").Value;
                            byte[] sendee = Encoding.UTF8.GetBytes($"{clientPoint}: {s.Substring(3+ ip.Length)}");
                            allClientSockets[ip].Send(sendee);
                        }
                        if (s.StartsWith("TO="))
                        {
                            string ip = Regex.Match(s, @"\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}:\d{1,5}").Value;
                            allClientSockets[ip].Send(cacheBuf);
                        }
                    }

                    //byte[] sendee = Encoding.UTF8.GetBytes("服务器返回信息");
                    //clientSocket.Send(sendee);
                }
                catch (SocketException e) {
                    allClientSockets.Remove(clientPoint);
                    form.ComboBoxRemoveItem(clientPoint);  //移除服务端相应用户列表项

                    string msg = "Removeip=" + clientPoint;  //给所有客户端发送移除消息
                    byte[] sendee = Encoding.UTF8.GetBytes(msg);
                    foreach (string ip in allClientSockets.Keys)
                            allClientSockets[ip].Send(sendee);
                    form.Println($"客户端 {clientSocket.RemoteEndPoint} 中断连接: {e.Message}");
                    clientSocket.Close();
                    break;
                }
                catch(Exception e) {
                    form.Println($"错误: {e.Message}");
                }
            }
        }

        static void SendMsg(object sender, EventArgs e)
        {
            if(form.GetComboBoxItem() == null)
            {
                string msg = form.GetMsgText();
                if (msg == "") return;
                byte[] sendee = Encoding.UTF8.GetBytes($"服务器:{msg}");
                foreach (Socket s in allClientSockets.Values)
                    s.Send(sendee);
                form.Println(msg);
                form.ClearMsgText();
            }
            else
            {
                string msg = form.GetMsgText();
                if (msg == "") return;
                byte[] sendee = Encoding.UTF8.GetBytes($"服务器:{msg}");
                Socket socketSend = allClientSockets[form.GetComboBoxItem()];
                socketSend.Send(sendee);
                form.Println(msg);
                form.ClearMsgText();
            }
        }

    }
}

form.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace ChatoServer
{
    public partial class MainForm : Form
    {
        public MainForm(EventHandler bListenClick, EventHandler bSendClick)
        {
            InitializeComponent();
            //这样设计方便代码分离。。。。
            
            this.buttonListen.Click += bListenClick;
            this.buttonSend.Click += bSendClick;
            
        }

        public string GetIPText()
        {
            //获取IP地址
            return this.textBoxIP.Text;
        }
        
        public int GetPort()
        {
            //获取端口地址
            return (int)this.numericUpDownPort.Value;
        }

        public string GetMsgText()
        {
            //获取输入框信息
            return this.textBoxSendee.Text.Trim();
        }

        public void SetPic(Image img)
        {
            //写入图片文件
            this.pictureBox1.Image = img;
        }

        public void ClearMsgText()
        {
            //清除文本框信息
            this.textBoxSendee.Clear();
        }
        //C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针
        delegate void VoidString(string s);
        //C#中禁止跨线程直接访问控件
        //InvokeRequired是为了解决这个问题而产生的,当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它。
        public void Println(string s)
        {
            if (this.textBoxMsg.InvokeRequired) {
                //如果InvokeRequired==true表示其它线程需要访问控件,那么调用invoke来转给控件owner处理。
                VoidString println = Println;//此时的println和Println等同
                //使用委托跨线程访问,这个事情必须自己接受其他委托做,不能其他线程直接做。
                this.textBoxMsg.Invoke(println, s);
            }
            else {
                this.textBoxMsg.AppendText(s + Environment.NewLine);
                //this.textBoxMsg1.
            }
        }

        public delegate DialogResult InvokeDelegate(Form parent);
        public DialogResult XShowDialog(Form parent)
        {
            if (parent.InvokeRequired)
            {
                InvokeDelegate xShow = new InvokeDelegate(XShowDialog);
                parent.Invoke(xShow, new object[] { parent });
                return DialogResult;
            }
            return this.ShowDialog(parent);
        }

        public void ComboBoxAddItem(string s)
        {
            //comboBoxAllClients下拉列表框的集合
            if (this.comboBoxAllClients.InvokeRequired) {
                VoidString cbAddItem = ComboBoxAddItem;
                this.textBoxMsg.Invoke(cbAddItem, s);
            }
            else {
                this.comboBoxAllClients.Items.Add(s);
            }
        }
        public void ComboBoxRemoveItem(string s)
        {
            if (this.comboBoxAllClients.InvokeRequired) {
                VoidString cbRmItem = ComboBoxRemoveItem;
                this.textBoxMsg.Invoke(cbRmItem, s);
            }
            else {
                this.comboBoxAllClients.Items.Remove(s);
            }
        }

        public string GetComboBoxItem()
        {
            if (this.comboBoxAllClients.SelectedItem == null)
                return null;
            else
                return this.comboBoxAllClients.SelectedItem.ToString();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            this.buttonListen.PerformClick();
        }
    }
}

其实一看Program.cs就能明白了。这里编码都不太难。
下面就是部署到服务器了。把写好的代码编译成.exe文件,放到服务器上。因为.exe只能在Windows上运行,所以为了在Linux环境下运行,需要在Linux上下载mono.下载好mono之后就直接 mono xxx.exe这样服务端就在服务器上运行起来了,再在本机打开客户端就可以通信啦。

你可能感兴趣的:(C# socket编程服务端部署)