C#中网络通信技术

1. Socket技术

Socket通信介绍
Socket痛信介绍
同步通信与异步通信
通信技术
通信技术介绍
通信技术介绍
视频链接
异步通信

1.1 Socket技术本质

Socket被称为“套接字”,抽象化为类,程序设计者只需要创建Socket类对象,即可使用套接字。简单的说就像一个插座,连接应用层与运输层,并不需要理解是如何实现的,相当于在TCP/IP协议基础上封装起来,客户端与服务端通过插座作为中介用网线连接起来。在使用时我们只需要知道IP地址与端口号。
在这里插入图片描述

1.2 Socket技术如何使用

基本思想:基于文件类的操作,现在要往文件内写入或者读取文件中的内容,最主要的三个操作”打开-读写-关闭“。Socket就是该模式的一个实现,把它当作一种特殊的文件,而Socket类中的函数就可以对其进行操作(读、写IO、打开、关闭)

2.TCP协议

socket是一套用于不同主机之间通信的API,工作在TCP/IP 协议栈之上,通过socket与不同主机之间建立通信,我们只需要指定主机的IP地址和一个端口号,IP地址是唯一标识你的网络设备,如果没有端口,操作系统中则没有办法区分数据到底应该发送到哪一个应用上,因此端口主要用于区分主机上的不同应用,通过socket我们可以建立不同应用之间的虚拟通道,并且是点对点的
在这里插入图片描述
一个形象的比喻是将一条数据线连接在不同应用的插槽上,这也是socket这个名字的由来,我么经常用到的socket又两种类型:TCP、UDP,以下主要介绍TCP
在这里插入图片描述
TCP主要有2个特点:TCP协议是非常可靠的,它底层会自动检测并回传跌势的数据包,因此,对于调用者来说,你发送的数据对方一定会接收到,其次,发送和接收到的数据顺序是完全一致的,比如,你发送了一串字符,
对方就一定会元丰不懂的收到同样的字符串,这也是为什么大家说TCP是基于“数据流”的协议。
在这里插入图片描述
另外,需要注意的是,TCP需要收发数据的双方扮演不同的角色:服务器和客户端,服务器会被动等待客户端的连接,它自己不会主动发起请求,
在这里插入图片描述

3.同步通信与异步通信

同步通信:发送一个数据包以后,一直等到接收方响应,才可以发送下一个数据包(串行通信)
实例:客户端与服务端一对一连接,且在相邻时间内,单向通信。
异步通信:客户端请求之后,不必等到服务器回应之后就可以发送下一条请求。(并行运行 ),即在同步的基础上增加线程。
实例:一个服务端,多个客户端,多个客户端同一时间向服务端发送消息;一个客户端,一个服务端,客户端不同功能同一时间发送多条消息。如果此时用同步通信,服务端一下子收到多条数据,数据之间的字符串会乱码。

4.TCP程序设计

socket程序主要包括服务端、客户端,以下分别就服务端与客户端程序开发步骤做简要概述。

4.1 服务端程序开发步骤

服务端程序开发步骤主要分为以下几步:

  1. 创建Socket套接字
  2. IP地址和端口号与服务端绑定
  3. 创建监听序列
  4. Send与Recieve操作
  5. 关闭通信
    (1)创建Socket套接字:这一步几乎是固定模式
    AddressFamily.InterNetwork:IP地址寻址方式
    SocketType.Stream:套接字类型
    ProtocolType.Tcp:TCP协议
Socket server=new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

(2)IP地址和端口号与服务端绑定:这一步几乎是固定模式
引入参数IPEndPoint:网络节点表示为IP地址与端口号

IPEndPoint point = new IPEndPoint(IPAddress.Parse(“IP地址”,int.Parse("端口号"));
服务端.Bind(point);

IP地址作为服务端的唯一标识符,表明身份,端口号作为连接的通道,通过Bind方式绑定
(3)创建监听序列:listen() 或accept()

server.Listen(3); //最多只能与3个客户端连接
Socket Client =server.Accept(); //从缓存中一个一个抓取,抓取的连接请求是客户端的,故定义为Client

如果不用Accept,Listen(3)相当于连接最大个数,当为4个,则超过限制,如果用Accept,相当于所有连接请求放在动态数组中,从另一个地方拿出来,但是一次只拿一个,此时Listen(3)、Listen(1)、Listen(10)都没有关系
(4) Send与Recieve操作:程序的核心部分,要实现的具体操作
Recieve接收部分:

  byte[] b = new byte[1024 * 1024 * 2];      //数据接收缓存区,2M
  int length = 0;
  length = Client.Receive(b);         //使用try/catch防止连接过程中断线
  string msg=Encoding.Default.GetString(b,0,length);  //字节解码成字符串

Send接收部分:

客户端.Send(Encoding.Default.GetBytes(“消息字符串”);

(5) 关闭通信

服务端.Shutdown(SocketShutdown.Both);
服务端.Close();//关闭Socket连接

在socket关闭之前,要确保已经发送和接收完所有挂起的数据,因此在关闭socket之前,要先调用

服务端.Shutdown(SocketShutdown.Both);

如果设计到异步通信,同时应关闭异步通信中创建的线程。
注意事项:在服务端的程序设计中存在以下对应关系,避免程序设计出错

***服务端.listen();
Socket 客户端= 服务端.accept();
客户端.recieve();
客户端.send();***

4.2 客户端程序开发步骤

客户端程序编写步骤如下:
(1)创建套接字:与服务端相同
(2)Connect申请连接:这一步与服务端端口绑定类似

IPEndPoint endPoint = new IPEndPoint(服务端IP地址, 服务端开放的端口号); // 用指定的ip和端口号初始化IPEndPoint实例
客户端.Connect(endPoint);  //与远程主机建立连接

(3)Send()与Recieved()进行通信:与服务端相同
(4)关闭通信:与服务端相同

4.3 服务端案例

4.3.1 界面布局

在这里插入图片描述

4.3.2 服务端代码

完整程序代码:代码中有详细注释

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

namespace 通信实例
{
    public partial class Server : Form
    {
        public Server()
        {
            InitializeComponent();
        }
        /// <summary>
        /// 应用程序主入口点
        /// </summary>
        static void main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Server());
        }

        //创建套接字
        Socket server=new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        //创建键和值的集合,string类型的变量和Socket
        private Dictionary<string,Socket> ClientList = new Dictionary<string, Socket>();
        /// <summary>
        /// 启动服务器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            IPEndPoint point = new IPEndPoint(IPAddress.Parse(textBox1.Text),int.Parse(textBox2.Text));
            try
            {
                server.Bind(point);     //每个套接字协议(地址/端口号)只允许使用一次,多次使用会报错,
                                        //相当于一台电脑、一个串口,二者都想做服务器,
                                        //电脑启用做服务器后,串口就无法再启用充当服务器,
                                        //即一个文件无法打开2次,如果可以,后面的操作以哪一个文件为准呢?
                                        
            }
            catch(Exception ex)         //使用try/catch的理由,避免已经被占用
            {
                MessageBox.Show("无法启动服务器" + ex.Message);
            }
            server.Listen(3);  //最多只能与3个客户端连接,Listen与Accept并存,以Accept为准
            Task.Run(() =>      //如果不用Accept,Listen(3)相当于连接最大个数,当为4个,则超过限制
            {                   //如果用Accept,相当于所有连接请求放在动态数组中,从另一个地方拿出来,
                                //但是一次只拿一个,此时Listen(3)、Listen(1)、Listen(10)都没有关系
                ListenSocket();
            });
            MessageBox.Show("服务器启动成功");
            button1.Enabled = false;        //服务器只需要启动一次
        }

        /// <summary>
        /// 创建监听程序
        /// </summary>
        private void ListenSocket()
        {
            while(true)
            {
                Socket Client =server.Accept();     //从缓存中一个一个抓取,抓取的连接请求是客户端的,故定义为Client
                string client =Client.RemoteEndPoint.ToString();    //告诉服务器是谁连接了,只读,读客户端身份
                Log(client + "连接了服务器");
                ClientList.Add(client, Client);  //抓取的客户端地址和连接请求
                OnlineClient(client, true);     //需要知道哪些客户端连接了服务器,
                                                //即当服务器向不同客户端发送不同消息时,得明确是往那个客户端发送的
                Task.Run(() =>
                {
                    ReceiveMsg(Client);
                });
            }
        }

        /// <summary>
        /// 已连接客户端列表更新
        /// </summary>
        /// <param name="client"></param>
        /// <param name="p"></param>
        private void OnlineClient(string client,bool p)
        {
            if(comboBox1.InvokeRequired)        //跨线程操作,OnlineClient-ListenSocket--Task.Run(() =>中
            {
                Invoke(new Action(() =>          //线程中创建新的线程,跨线程使用关键字Invoke
                {
                        if (p)
                        {
                            comboBox1.Items.Add(client);
                        }
                        else 
                        {
                            foreach (string item in comboBox1.Items)
                                if (item == client)
                                {
                                    comboBox1.Items.Add(client);
                                    break;
                                }
                        }
                    }));
            }
            else      //非跨线程操作,线程后台执行时,2个线程名称相同
            {
                if (p)
                {
                    comboBox1.Items.Add(client);
                }
                else
                {
                    foreach (string item in comboBox1.Items)
                        if (item == client)
                        {
                            comboBox1.Items.Add(client);
                            break;
                        }
                }
            }
        }

        /// <summary>
        /// 接收消息
        /// </summary>
        /// <param name="Client"></param>
        private void ReceiveMsg(Socket Client )
        {
            while(true)
            {
                byte[] b = new byte[1024 * 1024 * 2];      //数据接收缓存区
                int length = 0;
                string client=Client.RemoteEndPoint.ToString();     //获取连接的客户端IP地址
                try
                {
                    length = Client.Receive(b);         //使用try/catch防止连接过程中断线   
                }
                catch
                {
                    OnlineClient(client, false);
                    Log(client + ":失去连接");
                    ClientList.Remove(client);
                    break;
                }
                if(length> 0)
                {
                    string msg=Encoding.Default.GetString(b,0,length);  //字节解码成字符串
                    Log(client + ":" +msg);
                }
                else
                {
                    OnlineClient(client, false);
                    Log(client + ":失去连接");
                    ClientList.Remove(client);
                }
            }
        }

        /// <summary>
        /// 创建一个Listview接收消息的方法
        /// </summary>
        /// <param name="info"></param>
        string time = DateTime.Now.ToString("yyyy-mm-dd HH:MM:SS");
        private void Log(string info)
        {
            if (!listView1.InvokeRequired)
            {
                ListViewItem lit=new ListViewItem(time);
                lit.SubItems.Add(info);
                listView1.Items.Insert(listView1.Items.Count , lit);    
            }
            else
            {
                Invoke(new Action(()=>
                {
                    ListViewItem lit = new ListViewItem(time);
                    lit.SubItems.Add(info);
                    listView1.Items.Insert(listView1.Items.Count, lit);
                }));
            }
        }

        /// <summary>
        /// 服务器发送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button3_Click(object sender, EventArgs e)
        {
            if(comboBox1.SelectedItem!=null)
            {
                Log("" + richTextBox1.Text.Trim());
                string client=comboBox1.SelectedItem.ToString();
                ClientList[client].Send(Encoding.Default.GetBytes(richTextBox1.Text));
            }
            else
            {
                MessageBox.Show("请选择客户端");
            }
        }
        /// <summary>
        /// 群发消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button4_Click(object sender, EventArgs e)
        {
             if (comboBox1.Items.Count>0 )
            {
                Log("" + richTextBox1.Text.Trim());
                //string client = comboBox1.SelectedItem.ToString();
                foreach(string item in comboBox1.Items)
                {
                    ClientList[item].Send(Encoding.Default.GetBytes(richTextBox1.Text));
                }
                
            }
            else
            {
                MessageBox.Show("没有客户端连接到服务器");
            }
        }
        /// <summary>
        /// 启动客户端
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            Form frm = new Client();
            frm.Show();
        }
    }
}

4.2.3 连接示范

以串口调试助手作为客户端,多次开启,如下:
在这里插入图片描述
连接后的情况如图所示:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值