C# Socket网络编程(附赠练手小项目)

在公司花了两天的时间来研究Socket编程,感觉也还是学到了一点点皮毛,不过也还是有一点点的小成就。

配合Winform窗体界面,实现了简单的窗体间互相通信的小项目(可以互相发消息,服务器可以给客户端发送文件和震动弹出)。

现在把这两天学习到的知识点记录下来分享。。。

首先要声明:本篇博客并不会详细的讲解Socket底层的实现逻辑,只是讲解他是怎么运用的,是怎么实现窗体间通信的。


Socket的简单原理

Socket的两个概念:端口协议

端口

我们是通过服务端去访问应用程序的,但是在同一个服务端中的应用程序,他们的IP地址都是一样的,所以单单通过IP地址去访问对应的应用程序是不可能的,所以,得再结合端口去实现具体的访问。
每个应用程序就会有一个端口,我们就可以根据IP地址和端口号去实现访问了。(每个应用程序的端口号都是唯一的)

在这里插入图片描述

通过端口和IP地址客户端就可以准确无误的去访问服务器里面的应用程序了。

在访问的过程中,又会涉及到协议的问题!

协议

Socket有两个协议:TCPUDP

TCP

其中我们要知道,TCP协议是网络上比较安全稳定的协议,一般不会发送数据丢失。使用TCP协议建立网络连接,需要经过“三次握手”才建立连接。如下图:
在这里插入图片描述
只有客户端和服务器完成了这三次握手的过程,服务器才会和客户端进行数据的传输。(只要是少了一次都不会进行互相通信)

TCP建立网络连接的优缺点:

  1. 优点:安全,稳定,防止数据丢失。
  2. 缺点:效率低,经过三次握手的过程,需要耗费一定的时间。

UDP

UDP协议是与TCP协议相反的。

UDP协议不管服务器有没有空,就是一直给服务器发送消息,直到发完为止。他不管服务器有没有接收到,他只是要完成任务就行。
在这里插入图片描述

如果服务器很忙的话,并没有时间去处理客户端发过来的消息,那么就会造成数据丢失。

UDP建立网络连接的优缺点:

  1. 优点:快速,效率高。
  2. 缺点:不稳定,容易造成数据丢失。

两个协议各有的优缺点,也不好说哪个比较好。
像UDP协议一般用于视频的传输等。

好了TCP/UDP协议了解到这里就好了!


Socket

需要包含命名空间:
using System.Net;
using System.Net.Sockets;

一个服务器至少有两个Socket用于通信,一个用来监听,一个用来连接客户端进行通信。

我们先看一张图:
在这里插入图片描述
这张图描述的就是客户端和服务其进行互相通信的过程。
下面的讲解也是根据这张图来说明。

  1. Socket
    他是一个类,用来定义对象进行通信。
    首先创建一个用于监听的Socket(监听有没有客户端连接服务器)
    如代码:
// 一个负责监听的Socket ************************************************************************
								// ip地址的类型(ipV4/ipv6)   选择以流的方式     流对应的是Tcp协议
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

参数一:填写IP地址的类型(ipv4:InterNetwork; ipv6:InterNetworkV6)

参数二:填写TCP/UDP连接对应的方式
补充知识点:TCP是以的方式(Stream);UDP是以数据报的方式(Dgram)。

参数三:填写TCP/UDP协议
这里就用了TCP的传输方式。

然后创建ip地址和端口号对象:

// 创建ip地址和端口号对象     
IPAddress iPAddress = IPAddress.Any; //IPAddress.Parse(this.txtSever.Text);
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, Convert.ToInt32(this.txtPort.Text));

Any是自动获取连接进行的客户端的IP地址。
端口号是获取我们textBox控件里面的。

  1. Bind
    绑定监听端口

    // 让负责监听的Socket绑定IP地址和端口号
    socketWatch.Bind(iPEndPoint);
    

    到了这里,就完成了监听的工作。

  2. Listen
    设置监听队列
    在同一个时间段允许最大的连接个数

    // 设置监听队列(比如在同一个时间段允许最大的连接个数)
    socketWatch.Listen(10);
    

    比如在一秒内最多允许10个客户端连接服务器,从第11个客户端往后都得排队等待连接。

  3. Accept
    该函数用于等待客户端与服务器进行连接,并返回一个新的Socket,用于与客户端进行通信。

     // 负责监听的Socket来接受客户端的连接(创建跟客户端通信的Socket)
     Socket  socket = socketWatch.Accept();   // Accept() : 等待客户端连接
    

    利用死循环可以一直与多个客户端进行连接
    为了不卡死主线程,可以使用多线程对其进行操作!

     // 该线程的作用是:服务器不停的监听,等待客户端连接,并且创建与之通信用的Socket
    Thread th = new Thread(Listen);
    th.IsBackground = true;
    th.Start(socketWatch);
    
    private void Listen(Object o) {
    	Socket socketWatch = o as Socket;
    
    	// 等待客户端连接,并且创建与之通信用的Socket
    	while (true) {
    		// 负责监听的Socket来接受客户端的连接(创建跟客户端通信的Socket)
    		Socket socket= socketWatch.Accept();   // Accept() : 等待客户端连接                
    	}
           
    }
    
  4. Receive
    获取客户端发过来的消息(单位:byte)

    // 客户端连接成功后,服务器接收客户端发来的消息
    byte[] buffer = new byte[1024 * 1024 * 2];
    // b:实际接收到的有效字节数    
    int b = socket.Receive(buffer); // 调用Receive函数返回客户端发来的消息(单位:字节)
    

    可以根据b来判断客户端发过来的消息的字节个数。

  5. Send
    服务器给客户端发送消息

    仅支持字节发送数组发送

    byte[] newByte = Encoding.UTF8.GetBytes("我是服务器");
    socket.Send(newByte);
    

好了,服务器的流程基本上就这样了。

客户端与服务端雷同,就不演示了。




练手小项目

可能讲的不是很好,但是没关系,我把我自己写好的代码上传到此给需要的朋友玩一下。(可以实现两个窗口间互相通信)

界面如下:

服务器:
在这里插入图片描述
客户端:
在这里插入图片描述

代码如下:
服务器

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 _Socket {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
            Control.CheckForIllegalCrossThreadCalls = false;
        }

        private void btnStart_Click(object sender, EventArgs e) {
            try {
                // 一个负责监听的Socket ************************************************************************
                // ip地址的类型(ipV4/ipv6)   选择以流的方式     流对应的是Tcp协议
                Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                // 创建ip地址和端口号对象     
                IPAddress iPAddress = IPAddress.Any; //IPAddress.Parse(this.txtSever.Text);
                IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, Convert.ToInt32(this.txtPort.Text));

                // 让负责监听的Socket绑定IP地址和端口号
                socketWatch.Bind(iPEndPoint);

                ShowMsg("监听成功!");

                // 设置监听队列(比如在同一个时间段允许最大的连接个数)
                socketWatch.Listen(10);


                // 该线程的作用是:服务器不停的监听,等待客户端连接,并且创建与之通信用的Socket
                Thread th = new Thread(Listen);
                th.IsBackground = true;
                th.Start(socketWatch);
            }
            catch (Exception) {  }
              
        }


        // 存储连接服务器的客户端的IP地址/端口号和负责通信的socket
        Dictionary<string, Socket> dictionary = new Dictionary<string, Socket>();


        Socket socket;  // 负责通信
        private void Listen(Object o) {
            Socket socketWatch = o as Socket;

            // 等待客户端连接,并且创建与之通信用的Socket
            while (true) {
                try {
                    // 负责监听的Socket来接受客户端的连接(创建跟客户端通信的Socket)
                    socket = socketWatch.Accept();   // Accept() : 等待客户端连接
                    // 将连接成功后的客户端IP地址和端口号显示出来
                    ShowMsg(socket.RemoteEndPoint.ToString() + "连接成功\r\n");

                    // 将远程连接的客户端的IP地址和socket存入集合中
                    dictionary.Add(socket.RemoteEndPoint.ToString(), socket);

                    // 将远程连接的客户端的IP地址和端口号存储到下拉框中
                    this.cobUsers.Items.Add(socket.RemoteEndPoint.ToString());

                    // 该线程的作用是:使服务器不停的接收客户端发过来的消息
                    Thread th = new Thread(Recive);
                    th.IsBackground = true;
                    th.Start(socket);
                }
                catch (Exception) {  }
                         
            }
           
        }


        // 服务器不停的接收客户端发过来的消息
        private void Recive(Object o) {
            Socket socket = o as Socket;

            while (true) {
                try {
                    // 客户端连接成功后,服务器接收客户端发来的消息
                    byte[] buffer = new byte[1024 * 1024 * 2];
                    // b:实际接收到的有效字节数    
                    int b = socket.Receive(buffer); // 调用Receive函数返回客户端发来的消息(单位:字节)

                    // 用户点击了叉×
                    if (b == 0) {
                        break;
                    }

                    // 将其转为人能看得懂的字符串类型
                    string str = Encoding.UTF8.GetString(buffer, 0, b);
                    ShowMsg(socket.RemoteEndPoint.ToString() + "(客户端):" + str);  // 显示出来
                }
                catch (Exception) { }
                
            }
        }

        private void ShowMsg(string str) {
            this.txtLog.AppendText(str + "\r\n");
        }


        private void btnSend_Click(object sender, EventArgs e) {

            try {
                // 获取文本的值并转换为byte类型数组
                byte[] by = Encoding.UTF8.GetBytes(this.txtMsg.Text.Trim());
                this.txtMsg.Text = "";

                // 定义一个泛型集合,将文本表示符0 和 byte数组存储入集合中
                List<byte> list = new List<byte>();
                list.Add(0);
                list.AddRange(by);

                // 将泛型集合转换为数组
                byte[] newByte = list.ToArray();
                

                // 服务器给客户端发送消息
                //socket.Send(by);
                string ip = this.cobUsers.SelectedItem.ToString();  // 获得下拉框中选中项的IP地址和端口号
                dictionary[ip].Send(newByte);    // 将其传入Dictionary中获得跟客户端通信的socket
            }
            catch (Exception) {  }
            
        }

        // 选择需要发送的文件
        private void btnSelect_Click(object sender, EventArgs e) {

            try {
                OpenFileDialog ofd = new OpenFileDialog();

                // 设置初始路径
                ofd.InitialDirectory = @"C:\Users\yangg\Desktop";
                ofd.Title = "请选择你需要发送的文件";
                // 设置文件筛选
                ofd.Filter = "所有文件|*.*";

                if (ofd.ShowDialog() == DialogResult.OK) {
                    this.txtPath.Text = ofd.FileName;
                }
            }
            catch (Exception) {  }
            
        }

        private void btnSentFile_Click(object sender, EventArgs e) {

            try {
                // 获取文件路径
                string path = this.txtPath.Text;

                // 使用文件流以读的方式打开文件
                using (FileStream fileRead = new FileStream(path, FileMode.Open, FileAccess.Read)) {
                    byte[] buffer = new byte[1024 * 1024 * 10];

                    // 将文件中的数据读取进字节数组中,返回读取到的个数
                    int r = fileRead.Read(buffer, 0, buffer.Length);

                    List<byte> list = new List<byte>();
                    list.Add(1);
                    list.AddRange(buffer);

                    byte[] newBuffer = list.ToArray();

                    // 发送
                    dictionary[this.cobUsers.SelectedItem.ToString()].Send(newBuffer, 0, r + 1, SocketFlags.None);
                }
            }
            catch (Exception) {  }
            
        }

        private void btnZD_Click(object sender, EventArgs e) {
            byte[] buffer = new byte[1];
            buffer[0] = 2;
            dictionary[this.cobUsers.SelectedItem.ToString()].Send(buffer);
        }

    }
}

客户端

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 _Client {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        Socket socket;
        private void btnStart_Click(object sender, EventArgs e) {

            try {
                // 创建一个发送消息的Socket
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                // 建立IP地址和端口号的对象
                IPAddress iPAddress = IPAddress.Parse(this.txtSever.Text);
                IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, Convert.ToInt32(this.txtPort.Text));

                // 客户端服务器建立连接
                socket.Connect(iPEndPoint);

                Shows("连接成功!");

                // 建立线程,无限的接收服务器发过来的消息
                Thread th = new Thread(Recive);
                th.IsBackground = true;
                th.Start();
            }
            catch (Exception) {  }
            
        }

        private void Shows(string str) {
            this.txtLog.AppendText(str + "\r\n");
        }

        // 客户端给服务器发送消息
        private void benSend_Click(object sender, EventArgs e) {

            try {
                // 获取待发送的文本,并转换为byte字节数组
                byte[] by = Encoding.UTF8.GetBytes(this.txtMsg.Text.Trim());
                this.txtMsg.Text = "";

                // 客户端给服务器发送消息
                socket.Send(by);
            }
            catch (Exception) {  }
            
        }


        // 不停的接收服务器发过来的消息
        private void Recive() {

            while (true) {

                try {
                    byte[] by = new byte[1024 * 1024 * 10];
                    // 获取服务器发过来的消息,返回接收到的个数
                    int r = socket.Receive(by);
                    if (r == 0) {
                        break;
                    }


                    // 表示发送文字消息
                    if (by[0] == 0) {
                        string str = Encoding.UTF8.GetString(by, 1, r - 1);
                        Shows(socket.RemoteEndPoint.ToString() + "(服务器):" + str);
                       
                    } else if (by[0] == 1) {    // 表示发送文件
                        SaveFileDialog sfd = new SaveFileDialog();
                        sfd.InitialDirectory = @"C:\Users\yangg\Desktop";
                        sfd.Title = "请选择报错位置";
                        sfd.Filter = "所有文件|*.*";

                        
                        if (sfd.ShowDialog(this) == DialogResult.OK) {
                            string path = sfd.FileName;

                            using (FileStream fileWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write)) {
                                fileWrite.Write(by, 1, r - 1);                               
                            }
                            MessageBox.Show("保存成功");
                        }
                        
                    } else if (by[0] == 2) {    // 表示发送震动
                        ZD();
                    }
                }
                catch (Exception) {  }
                
            }
            
        }


        // 震动
        private void ZD() {
            int x = this.Location.X;
            int y = this.Location.Y;

            for (int i = 0; i < 300; i++) {
                this.Location = new Point(x - 50, y);
                this.Location = new Point(x, y + 50);
                this.Location = new Point(x + 50, y);
                this.Location = new Point(x, y - 50);
                
            }

            this.Location = new Point(x, y);
        }

        private void Form1_Load(object sender, EventArgs e) {
            Control.CheckForIllegalCrossThreadCalls = false;
        }
    }
}

CSDN:
https://download.csdn.net/download/cpp_learner/12761444

百度连接:
链接:https://pan.baidu.com/s/16TE3Q51NhxMXNv2sp9gfcQ
提取码:fc4n


总结:
也就基本上是这样了,其他的也就不会了,希望对大家有帮助吧!

©️2020 CSDN 皮肤主题: 点我我会动 设计师:上身试试 返回首页