Socket网络编程笔记(C#代码)

目录

一、Socket概述

二、UDP收发 

原理:

特点:

UDP发:

UDP收 :

运行结果:

三、UDP综合实例

运行结果:​

四、TCP Socket

原理:

客户端:

服务端:

实验结果:

五、TCP Socket多线程应用

六、Socket与HTTP

七、总结


 

期末考试考到了Socket概念的简答题。之前做项目自己也用到过。刚好现在空闲整理一下心得笔记。我会从网络编程的概念,优缺点和实际应用来总结。

这篇博客也参考了我们学校的教材和其他的博客。

一、Socket概述

百度百科:

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

Socket就是套接字,它是引用网络连接的特殊的文件描述符,由3个基本要素组成:AddressFamily(网络类型)、 SocketType(数据传输类型)及ProtocolType(采用的网络协议)。

二、UDP收发 

原理:

UDP是无连接协议,只需要知道对方的IP和Port就能进行数据的传输,其中IP负责定位主机Port负责定位应用。在C#中UDP网络通信中并未使用socket类,但是他使用的UDPClient类和Socket原理类似。

特点:

  • 虽然UDP协议无法保证数据可靠性,但因为它是基于无连接的协议,能够消除生成连接的系统延迟,所以速度比TCP更快;
  • UDP既支持一对一连接,也支持一对多连接,所以,可以使用广播的方式多地址发送,而TCP仅支持一对一通信;
  • UDPTCP的报头比是820,所以相对TCPUDP消耗更少的网络带宽;
  • UDP传输的数据有消息边界,而TCP传输的数据没有消息边界。

UDP发:

namespace UDPSend
{
    public partial class Form1 : Form
    {
        //定义一个UDPClient类型的字段
        UdpClient udpClient;
        public Form1()
        {
            //创建一个未与指定地址或端口绑定的UDPClient实例
            udpClient = new UdpClient();
            InitializeComponent();
        }

        //发送数据
        private void button1_Click(object sender, EventArgs e)
        {
            //临时存储textBox1中的数据
            string temp = this.textBox1.Text;
            //将textBox1中的数据(文本)转化为字节编码以便发送
            byte[] bData = System.Text.Encoding.UTF8.GetBytes(temp);
            //向本机的13579端口发送数据(方法1)
            udpClient.Send(bData, bData.Length, Dns.GetHostName(), 13579);
            //向本机的13579端口发送数据(方法2)
            //利用方法2,可向其他计算机端口方式数据
            //udpClient.Connect(IPAddress.Parse("127.0.0.1"), 13579);
            //udpClient.Send(bData, bData.Length);
        }
    }
}

UDP收 :

namespace UDPReceive
{
    public partial class Form1 : Form
    {
        //定义一个UDPClient类型的字段
        UdpClient udpClient;
        //定义一个线程 
        Thread thread;

        public Form1()
        {
            //屏蔽异常以便跨线程访问控件 
            CheckForIllegalCrossThreadCalls = false;
            InitializeComponent();
            //创建一个与指定端口绑定的UDPClient
            //实例,此端口须与发送方端口相同 
            udpClient = new UdpClient(13579);
        }

        //监听并接收数据
        private void listen()
        {
            //定义一个终结点,因为此前创建的UDPClient实例已与指定端口绑定,
            //所以,此处的IP地址和端口可任意设置或不设置
            IPEndPoint iep = null;
            while (true)
            {
                //获得发送方的数据包并转换为指定字符类型。
                //ref关键字使参数按引用传递,当控制权传给回调用方法时,
                //在方法中对参数所做的任何更改都将反映在该变量中
                string sData = System.Text.Encoding.UTF8.GetString(udpClient.Receive(ref iep));
                //将接收到的数据添加到listBox1的条目中
                this.listBox1.Items.Add(sData);
            }
        }

        //启动数据接收
        private void button1_Click(object sender, EventArgs e)
        {
            //创建一个线程以监听并接收数据 
            thread = new Thread(new ThreadStart(listen));
            //设置为后台线程,以便关闭窗体时终止线程 
            thread.IsBackground = true;
            thread.Start();
        }

        //关闭窗体时终止线程
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            //终止线程
            if (thread != null) thread.Abort();
        }
    }
}

运行结果:

 

三、UDP综合实例

把收发程序结合在一起就是一个基于UDP协议的聊天程序了。(基本功能,若有需求自行完善)

namespace UDPApp
{
    public partial class Form1 : Form
    {
        #region var
        //定义一个UDPClient_send类型的字段
        UdpClient udpClient;
        //定义一个UDPClient_receive类型的字段
        UdpClient udpClient_receive;
        //定义一个线程 
        Thread thread;
        #endregion
        public Form1()
        {
            //创建一个未与指定地址或端口绑定的UDPClient实例
            udpClient = new UdpClient();
            InitializeComponent();
            //屏蔽异常以便跨线程访问控件 
            CheckForIllegalCrossThreadCalls = false;
            
        }
        //发送数据send
        private void button1_Click(object sender, EventArgs e)
        {
            //临时存储textBox1中的数据
            string temp = this.textBox1.Text;
            //将textBox1中的数据(文本)转化为字节编码以便发送
            byte[] bData = System.Text.Encoding.UTF8.GetBytes(temp);
            //向本机的13579端口发送数据(方法1)
            udpClient.Send(bData, bData.Length, Dns.GetHostName(), Convert.ToUInt16(this.textsend.Text));
            //向本机的13579端口发送数据(方法2)
            //利用方法2,可向其他计算机端口方式数据
            //udpClient.Connect(IPAddress.Parse("127.0.0.1"), 13579);
            //udpClient.Send(bData, bData.Length);
            this.textBox1.Text = null;
        }

        #region receive
        private void btnStart_Click(object sender, EventArgs e)
        {
            //创建一个与指定端口绑定的UDPClient
            //实例,此端口须与发送方端口相同 
            udpClient_receive = new UdpClient(Convert.ToUInt16(this.textreceive.Text));
            //创建一个线程以监听并接收数据 
            thread = new Thread(new ThreadStart(listen));
            //设置为后台线程,以便关闭窗体时终止线程 
            thread.IsBackground = true;
            thread.Start();
        }
        //监听并接收数据
        private void listen()
        {
           // DateTime currentTime = new DateTime();
            //定义一个终结点,因为此前创建的UDPClient实例已与指定端口绑定,
            //所以,此处的IP地址和端口可任意设置或不设置
            IPEndPoint iep = null;
            while (true)
            {
                //获得发送方的数据包并转换为指定字符类型。
                //ref关键字使参数按引用传递,当控制权传给回调用方法时,
                //在方法中对参数所做的任何更改都将反映在该变量中
                string sData = System.Text.Encoding.UTF8.GetString(udpClient_receive.Receive(ref iep));
                //将接收到的数据添加到listBox1的条目中--回车问题
                string[] ssData = sData.Split('\n');
                for (int i = 0; i < ssData.Length; i++)
                {
                    this.listBox1.Items.Add(DateTime.Now.ToString() + " 消息: " + ssData[i]);
                }
               // this.listBox1.Items.Add(DateTime.Now.ToString()+" 消息: "+sData);
            }
        }
        #endregion

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            //终止线程
            if (thread != null) thread.Abort();
        }

       
    }
}

UDP的接收方要一直等待监听是否有数据发送到监听的端口上。所以开一个线程去循环执行listen()方法。

这里需要注意一下端口的问题,程序中我把端口固定了,再启动一个程序是会出现端口占用的问题,所以我们要选择不同的端口来监听UDP。

运行结果:

四、TCP Socket

原理:

TCP基于C/S是面向连接的可靠传输,TCP是一种面向连接的协议,即利用TCP传输数据的时候,首先必须先用低级通信协议IP在计算机之间建立连接(也就是所谓的握手,然后才可以传输数据),不同于UDP,TCP Socket分为Socket和ServerSocket对应着client和server,下面我来用代码实现一个简单的TCP通讯功能:


客户端:

namespace ChatClient
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;//禁用此异常
        }

        #region 变量
        //客户机与服务器之间的连接状态
        public bool bConnected = false;
        //侦听线程
        public Thread tAcceptMsg = null;
        //用于Socket通信的IP地址和端口
        public IPEndPoint IPP = null;
        //Socket通信
        public Socket socket = null;
        //网络访问的基础数据流
        public NetworkStream nStream = null;
        //创建读取器
        public TextReader tReader = null;
        //创建编写器
        public TextWriter wReader = null;
        #endregion

        //显示信息
        public void AcceptMessage()
        {
            string sTemp; //临时存储读取的字符串
            while (bConnected)
            {
                try
                {
                    //连续从当前流中读取字符串直至结束
                    sTemp = tReader.ReadLine();
                    if (sTemp.Length != 0)
                    {
                        //richTextBox2_KeyPress()和AcceptMessage()
                        //都将向richTextBox1写字符,可能访问有冲突,
                        //所以,需要多线程互斥
                        lock (this)
                        {
                            richTextBox1.Text = "服务器:" + sTemp + "\n" + richTextBox1.Text;
                        }
                    }
                }
                catch
                {
                    MessageBox.Show("无法与服务器通信。");
                }
            }
            //禁止当前Socket上的发送与接收
            socket.Shutdown(SocketShutdown.Both);
            //关闭Socket,并释放所有关联的资源
            socket.Close();
        }

        //创建与服务器的连接,侦听并显示聊天信息
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                IPP = new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket.Connect(IPP);
                if (socket.Connected)
                {
                    nStream = new NetworkStream(socket);
                    tReader = new StreamReader(nStream);
                    wReader = new StreamWriter(nStream);
                    tAcceptMsg = new Thread(new ThreadStart(this.AcceptMessage));
                    tAcceptMsg.Start();
                    bConnected = true;
                    button1.Enabled = false;
                    MessageBox.Show("与服务器成功建立连接,可以通信。");
                }
            }
            catch
            {
                MessageBox.Show("无法与服务器通信。");
            }
        }

        //发送信息
        private void richTextBox2_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)13)//按下的是回车键
            {
                if (bConnected)
                {
                    try
                    {
                        //richTextBox2_KeyPress()和AcceptMessage()
                        //都将向richTextBox1写字符,可能访问有冲突,
                        //所以,需要多线程互斥
                        lock (this)
                        {
                            richTextBox1.Text = "客户机:" + richTextBox2.Text + richTextBox1.Text;
                            //客户机聊天信息写入网络流,以便服务器接收
                            wReader.WriteLine(richTextBox2.Text);
                            //清理当前缓冲区数据,使所有缓冲数据写入基础设备
                            wReader.Flush();
                            //发送成功后,清空输入框并聚集之
                            richTextBox2.Text = "";
                            richTextBox2.Focus();
                        }
                    }
                    catch
                    {
                        MessageBox.Show("与服务器连接断开。");
                    }
                }
                else
                {
                    MessageBox.Show("未与服务器建立连接,不能通信。");
                }
            }
        }

        //关闭窗体时断开socket连接,并终止线程(否则,VS调试程序将仍处于运行状态)
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                socket.Close();
                tAcceptMsg.Abort();
            }
            catch
            { }
        }
    }
}

首先创建一个Socket和InetSocketAddress ,然后通过Socket的connect()方法进行连接,连接成功后可以获取到输出流,通过该输出流就可以向服务端传输数据。

服务端:

namespace ChatServer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;//禁用此异常
        }

        #region 变量
        //客户机与服务器之间的连接状态
        private bool bConnected = false;
        //侦听线程
        private Thread tAcceptMsg = null;
        //用于Socket通信的IP地址和端口
        private IPEndPoint IPP = null;
        //Socket通信
        private Socket socket = null;
        private Socket clientSocket = null;   //此处可参考,使用之前看是否分配内存
        //网络访问的基础数据流
        private NetworkStream nStream = null;
        //创建读取器
        private TextReader tReader = null;
        //创建编写器
        private TextWriter wReader = null;
        #endregion

        //显示信息----无连接关闭 .Accept()方法报错?
        public void AcceptMessage()
        {
            //接受客户机的连接请求
            clientSocket = socket.Accept();  //此处等待

            if (clientSocket != null)
            {
                bConnected = true;
                this.label1.Text = "与客户 " + clientSocket.RemoteEndPoint.ToString() + " 成功建立连接。";
            }

            nStream = new NetworkStream(clientSocket);
            //读字节流
            tReader = new StreamReader(nStream);
            //写字节流
            wReader = new StreamWriter(nStream);

            string sTemp; //临时存储读取的字符串
            while (bConnected)
            {
                try
                {
                    //连续从当前流中读取字符串直至结束
                    sTemp = tReader.ReadLine();
                    if (sTemp.Length != 0)
                    {
                        //richTextBox2_KeyPress()和AcceptMessage()
                        //都将向richTextBox1写字符,可能访问有冲突,
                        //所以,需要多线程互斥
                        lock (this)
                        {
                            richTextBox1.Text = "客户机:" + sTemp + "\n" + richTextBox1.Text;
                        }
                    }
                }
                catch
                {
                    tAcceptMsg.Abort();
                    MessageBox.Show("无法与客户机通信。");
                }
            }
            //禁止当前Socket上的发送与接收
            clientSocket.Shutdown(SocketShutdown.Both);
            //关闭Socket,并释放所有关联的资源
            clientSocket.Close();
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
        }

        //启动侦听并显示聊天信息--New Socket()
        private void button1_Click(object sender, EventArgs e)
        {
            //服务器侦听端口可预先指定(此处使用了最大端口值)
            //Any表示服务器应侦听所有网络接口上的客户活动
            IPP = new IPEndPoint(IPAddress.Any, 45678);
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Bind(IPP);//关联(绑定)节点
            socket.Listen(0);//0表示连接数量不限

            //创建侦听线程
            tAcceptMsg = new Thread(new ThreadStart(this.AcceptMessage));
            tAcceptMsg.Start();
            button1.Enabled = false;
        }

        //发送信息
        private void richTextBox2_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)13)//按下的是回车键
            {
                if (bConnected)
                {
                    try
                    {
                        //richTextBox2_KeyPress()和AcceptMessage()
                        //都将向richTextBox1写字符,可能访问有冲突,
                        //所以,需要多线程互斥
                        lock (this)
                        {
                            richTextBox1.Text = "服务器:" + richTextBox2.Text + richTextBox1.Text;
                            //客户机聊天信息写入网络流,以便服务器接收
                            wReader.WriteLine(richTextBox2.Text);
                            //清理当前缓冲区数据,使所有缓冲数据写入基础设备
                            wReader.Flush();
                            //发送成功后,清空输入框并聚集之
                            richTextBox2.Text = "";
                            richTextBox2.Focus();
                        }
                    }
                    catch
                    {
                        MessageBox.Show("无法与客户机通信!");
                    }
                }
                else
                {
                    MessageBox.Show("未与客户机建立连接,不能通信。");
                }
            }
        }

        //关闭窗体时断开socket连接,并终止线程(否则,VS调试程序将仍处于运行状态)
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                socket.Close();
                tAcceptMsg.Abort();
            }
            catch
            { }
        }

    }
}

首先创建一个服务端Socket并明确端口号,通过accept()方法获取到链接过来的客户端Socket,从客户端Socket中获取输入流,最后由输入流读取客户端传输来的数据。

实验结果:

五、TCP Socket多线程应用

TCP相比于UDP有一个缺点就是仅支持一对一连接,不可以一对多通信。但是通过多线程的技术手段,我们还是可以实现一对多通信的效果。

//仅需要修改AcceptMessage()方法即可
        public void AcceptMessage()
        {
            //接受客户机的连接请求
            clientSocket = socket.Accept();  //此处等待
            new Thread(() => {
            if (clientSocket != null)
            {
                bConnected = true;
                this.label1.Text = "与客户 " + clientSocket.RemoteEndPoint.ToString() + " 成功建立连接。";
            }

            nStream = new NetworkStream(clientSocket);
            //读字节流
            tReader = new StreamReader(nStream);
            //写字节流
            wReader = new StreamWriter(nStream);

            string sTemp; //临时存储读取的字符串
            while (bConnected)
            {
                try
                {
                    //连续从当前流中读取字符串直至结束
                    sTemp = tReader.ReadLine();
                    if (sTemp.Length != 0)
                    {
                        //richTextBox2_KeyPress()和AcceptMessage()
                        //都将向richTextBox1写字符,可能访问有冲突,
                        //所以,需要多线程互斥
                        lock (this)
                        {
                            richTextBox1.Text = "客户机:" + sTemp + "\n" + richTextBox1.Text;
                        }
                    }
                }
                catch
                {
                    tAcceptMsg.Abort();
                    MessageBox.Show("无法与客户机通信。");
                }
            }
            //禁止当前Socket上的发送与接收
            clientSocket.Shutdown(SocketShutdown.Both);
            //关闭Socket,并释放所有关联的资源
            clientSocket.Close();
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
            }
        }

我这个程序还存在很多的BUG,需要学习的可自行完善。大概的思路就是这样的。是应该新建一个Socket的而不是处理数据的线程。Server回复给指定Socket的问题。。。。。。

https://www.cnblogs.com/yuanshuang-club/p/11407789.html

六、Socket与HTTP

 在计算机网络中这两者根本不是一个概念。Socket是一个封装的API,基于TCP协议进行数据通信。而HTTP是超文本传输协议,是基于TCP的应用层协议。所以我们还可以通过Socket编程来实现以下HTTP协议的GET请求做一个测试。

这里用ONENET服务器做测试,向ONENET服务器发送一下内容:

POST /devices/25900768/datapoints HTTP/1.1
api-key: WXs6QMIDeT1FSX1JwfVIFTRAh=o=
Host:api.heclouds.com
Connection:close
Content-Length:59

{"datastreams":[{"id":"111","datapoints":[{"value":50}]}]}

就可以在ONENET平台上增加一个数据点了

七、总结

网络编程也许大家可以做出来。但是如果大家不了解这些网络协议还是希望大家学习一下。虽然对我们做网络编程没有多大影响,但是对于整个程序的深入理解会有很好的效果。

最后把所有程序打包给大家:https://mp.csdn.net/postedit?not_checkout=1

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值