TCP/UDP编程基础

一.Socket

1.TCP/IP

要想理解socket首先得熟悉一下TCP/IP协议族, TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何再它们之间传输的标准,

从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中

应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
传输层:TCP,UDP
网络层:IP,ICMP,OSPF,EIGRP,IGMP
数据链路层:SLIP,CSLIP,PPP,MTU

每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的
在这里插入图片描述
在这里插入图片描述
在TCP/IP协议中两个因特网主机通过两个路由器和对应的层连接。各主机上的应用通过一些数据通道相互执行读取操作
在这里插入图片描述
TCP编程服务器端的一般步骤:

①创建一个socket,用函数socket()
②绑定IP地址、端口等信息到socket上,用函数bind()
③开启监听,用函数listen()
④接收客户端上来的连接,用函数accept()
⑤收发数据,用函数send()和recv(),或者read()和write()
⑥关闭网络连接;
⑦关闭监听;

TCP编程客户端的一般步骤:

①创建一个socket,用函数socket()
②设置要连接的对方的IP地址和端口等属性
③连接服务器,用函数connect()
④收发数据,用函数send()和recv(),或者read()和write()
⑤关闭网络连接

2.socket

我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。

能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
在这里插入图片描述
socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

3.socket通信流程

socket是"打开—读/写—关闭"模式的实现,以使用TCP协议通讯的socket为例,其交互流程大概是这样子的
在这里插入图片描述

服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket

服务器为socket绑定ip地址和端口号

服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开

客户端创建socket

客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket

服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求

客户端连接成功,向服务器发送连接状态信息

服务器accept方法返回,连接成功

客户端向socket写入信息

服务器读取信息

客户端关闭

服务器端关闭

二.UDP通信

1.项目创建过程

vs创建项目
在这里插入图片描述
选择创建新项目,选择控制台应用
在这里插入图片描述
之后接下来选择路径完成创建

2.各部分代码

发送端

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace UDP
{
    class Program
    {
        static void Main(string[] args)
        {
            //提示信息
            Console.WriteLine("按下任意按键开始发送...");
            Console.ReadKey();

            //做好链接准备
            UdpClient client = new UdpClient();  //实例一个端口
            IPAddress remoteIP = IPAddress.Parse("169.254.184.236");  //假设发送给这个IP
            int remotePort = 11000;  //设置端口号
            IPEndPoint remotePoint = new IPEndPoint(remoteIP, remotePort);  //实例化一个远程端点 

            for (int i = 0; i < 50; i++)
            {
                //要发送的数据:第n行:hello cqjtu!重交物联2018级
                string sendString = null;
                sendString += "hello cqjtu!重交物联2019级";

                //定义发送的字节数组
                //将字符串转化为字节并存储到字节数组中
                byte[] sendData = null;
                sendData = Encoding.Default.GetBytes(sendString);

                client.Send(sendData, sendData.Length, remotePoint);//将数据发送到远程端点 
            }
            client.Close();//关闭连接

            //提示信息
            Console.WriteLine("");
            Console.WriteLine("数据发送成功,按任意键退出...");
            System.Console.ReadKey();
        }
    }
}

接受端

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace UDP
{
    class Program
    {
        static void Main(string[] args)
        {
            int result = 1;
            UdpClient client = new UdpClient(11000);
            string receiveString = null;
            byte[] receiveData = null;
            //实例化一个远程端点,IP和端口可以随意指定,等调用client.Receive(ref remotePoint)时会将该端点改成真正发送端端点 
            IPEndPoint remotePoint = new IPEndPoint(IPAddress.Any, 0);
            Console.WriteLine("正在准备接收数据...");
            while (true)
            {
                receiveData = client.Receive(ref remotePoint);//接收数据 
                receiveString = Encoding.Default.GetString(receiveData);
                Console.WriteLine(receiveString);
                if (result == 50)
                {
                    break;
                }

                result++;
            }
            client.Close();//关闭连接
            Console.WriteLine("");
            Console.WriteLine("数据接收完毕,按任意键退出...");
            System.Console.ReadKey();
        }
    }
 }

3.结果

在这里插入图片描述
在这里插入图片描述

三、Form窗口程序

1.项目创建

1.创建新项目
在这里插入图片描述
2.界面设计
从工具箱中选择button和textbox控件
在这里插入图片描述
3.textbox选择多行文本
在这里插入图片描述
4.添加垂直滚动条:找到 ScrollBars 属性,设置参数为 Vertical
在这里插入图片描述
5.设置边界样式:找到 BorderStyle ,参数设置为 FixedSingle
在这里插入图片描述
6.设置消息显示界面的 TextBox 不可编辑:找到 Enabled 属性,参数设为 False
在这里插入图片描述
7.设置按钮
在这里插入图片描述
8.设置窗体
窗体text设置为客户端
找到AcceptButton 属性,下拉框选中这个 button1 按钮
在这里插入图片描述

2.客户端代码

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

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            
        }

        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                string str = "The current time: ";
                str += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                textBox1.AppendText(str + Environment.NewLine);              
                int port = 2000;
                string host = "10.61.170.2";IP地址
                IPAddress ip = IPAddress.Parse(host);
                IPEndPoint ipe = new IPEndPoint(ip, port);
                Socket c = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);       
                str = "Connect to server...";
                textBox1.AppendText(str + Environment.NewLine);
                c.Connect(ipe);
                string sendStr = textBox2.Text;
                str = "The message content: " + sendStr;
                textBox1.AppendText(str + Environment.NewLine);
                byte[] bs = Encoding.UTF8.GetBytes(sendStr);
                str = "Send the message to the server...";
                textBox1.AppendText(str + Environment.NewLine);
                c.Send(bs, bs.Length, 0);
                string recvStr = "";
                byte[] recvBytes = new byte[1024];
                int bytes;
                bytes = c.Receive(recvBytes, recvBytes.Length, 0);
                recvStr += Encoding.UTF8.GetString(recvBytes, 0, bytes);
                str = "The server feedback: " + recvStr;
                textBox1.AppendText(str + Environment.NewLine);    
                c.Close();
            }
            catch (ArgumentNullException f)
            {
                string str = "ArgumentNullException: " + f.ToString();
                textBox1.AppendText(str + Environment.NewLine);
            }
            catch (SocketException f)
            {
                string str = "ArgumentNullException: " + f.ToString();
                textBox1.AppendText(str + Environment.NewLine);
            }
            textBox1.AppendText("" + Environment.NewLine);
            textBox2.Text = "";
        }
    }
}

服务器端代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            int i = 0;
            int port = 2000;
            string host = "10.61.170.2";
            IPAddress ip = IPAddress.Parse(host);
            IPEndPoint ipe = new IPEndPoint(ip, port);
            Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            s.Bind(ipe);
            while (true)
            {
                i++;
                try
                {
                    Console.WriteLine("\t-----------------------------------------------");
                    Console.Write("Perform operations {0} :", i);
                    s.Listen(0);
                    Console.WriteLine("1. Wait for connect...");
                    Socket temp = s.Accept();
                    Console.WriteLine("2. Get a connect"); 
                    string recvStr = "";
                    byte[] recvBytes = new byte[1024];
                    int bytes;
                    bytes = temp.Receive(recvBytes, recvBytes.Length, 0);
                    recvStr += Encoding.UTF8.GetString(recvBytes, 0, bytes);
                    Console.WriteLine("3. Server Get Message:{0}", recvStr);
                    string sendStr = "Ok!Client send message sucessful!";
                    byte[] bs = Encoding.UTF8.GetBytes(sendStr);
                    temp.Send(bs, bs.Length, 0);
                    temp.Close();
                    Console.WriteLine("4. Completed...");
                    Console.WriteLine("-----------------------------------------------------------------------");
                    Console.WriteLine("");
                }
                catch (ArgumentNullException e)
                {
                    Console.WriteLine("ArgumentNullException: {0}", e);
                }
                catch (SocketException e)
                {
                    Console.WriteLine("SocketException: {0}", e);
                }
            }
        }
    }
}

3.运行结果

在这里插入图片描述
在这里插入图片描述

4.端口扫描

单线程

using System;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
using System.Threading;
namespace MultithreadingScanningPort
{
    public partial class Form1 : Form
    {
        private bool[] ports = new bool[65536];
        public Form1()
        {
            InitializeComponent();
            panel5.Hide();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if(int.Parse(beginPortText.Text)<0 || int.Parse(beginPortText.Text) > int.Parse(endPortText.Text)  || int.Parse(endPortText.Text)>65565)
            {
                messages.Items.Add("端口错误!");
                return;
            }

            messages.Items.Clear();
            messages.Items.Add("开始扫描.......");
            ScanningPort();

        }

        public void ScanningPort() {
            int start = int.Parse(beginPortText.Text);
            int end = int.Parse(endPortText.Text);
            messages.Items.Add("起始端口"+start);
            messages.Items.Add("结束端口" + end);
            for (int i = start; i <= end; i++)
            {
                Scanning(i);
            }

            messages.Items.Add("端口扫描结束");
        }


        public void Scanning(int port) {
            this.ports[port] = true;
            try {
                TcpClient tmp = new TcpClient(ipAddressText.Text, port);
                messages.Items.Add("端口" + port + "开放");
            }

            catch(System.Exception ex)
            {

            }
        }

    }
}

多线程

using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace SingleThreadScanningPort
{
    public partial class Form1 : Form
    {
        
        private bool[] ports = new bool[65536];//所有端口号
        private static int port=0;//当前端口号
        private static int count = 0;//开放端口号数量

        public Form1()
        {
            InitializeComponent();
            //CheckForIllegalCrossThreadCalls设置为false;然后就能安全的访问窗体控件
            CheckForIllegalCrossThreadCalls = false;

            //初始化进度显示为空
            label2.Text = "";

            //停止扫描按钮为不可用
            stopScanning.Enabled = false;
        }

        private void beginScanning_Click(object sender, EventArgs e)
        {
            //检查端口号
            if (int.Parse(beginPortText.Text) < 0 || int.Parse(beginPortText.Text) > int.Parse(endPortText.Text) || int.Parse(endPortText.Text) > 65565)
            {
                messages.Items.Add("端口错误!");
                return;
            }

            //新建线程执行扫描端口函数
            Thread procss = new Thread(new ThreadStart(ScanningPort));
            procss.Start();

            //设置进度条最大值最小值分别为结束端口和起始端口
            progressBar1.Maximum = int.Parse(endPortText.Text) - int.Parse(beginPortText.Text);
            progressBar1.Minimum = 0;

            //判断是否为继续扫描
            if (port == 0)
            {
                messages.Items.Clear();
                messages.Items.Add("开始扫描.......");
            }

            else
                messages.Items.Add("继续扫描......");

            //开始扫描禁用,停止扫描启用
            beginScanning.Enabled = false;
            stopScanning.Enabled = true;
        }
        public void ScanningPort()
        {
            int start;
            int end = int.Parse(endPortText.Text);

            //判断是否为继续扫描,如果是则继续扫描,否则重新扫描
            if (port != 0)
                start = port;
            else
                start = int.Parse(beginPortText.Text);

            messages.Items.Add("起始端口" + start);
            messages.Items.Add("结束端口" + end);


            for (int i = start; i <= end; i++)
            {

                //按下停止扫描后开始扫描按钮启用,此时停止扫描
                if (beginScanning.Enabled)
                    break;
                port = i;

                //新建线程进行扫描
                Thread thread = new Thread(Scanning);
                thread.Start();

                //主线程休眠10ms
                System.Threading.Thread.Sleep(10);

                //修改进度条的值
                progressBar1.Value = i- int.Parse(beginPortText.Text);

                //显示端口号以及进度
                label2.Text = "正在扫描端口: " + i+"  进度: "+Math.Round(( (i - int.Parse(beginPortText.Text)) *100.0 / progressBar1.Maximum),2)+"%";
                progressBar1.PerformStep();
            }

            if (port != 0)
                beginScanning.Text = "继续扫描";
            else
            {
                messages.Items.Add("端口扫描结束");
                messages.Items.Add("共有 " + count + " 个端口开放");
            }

            beginScanning.Enabled = true;
            stopScanning.Enabled = false;
           

            //判断是否扫描完毕
            if (int.Parse(endPortText.Text) == port)
            {
                port = 0;
                beginScanning.Text = "开始扫描";
            }

        }


        public void Scanning()
        {
            this.ports[port] = true;
            try
            {
                TcpClient tmp = new TcpClient(ipAddressText.Text, port);
                messages.Items.Add("端口" + port + "开放");
                count++;
            }

            catch (System.Exception ex)
            {
               
            }
        }

        private void stopScanning_Click(object sender, EventArgs e)
        {
            //按下停止按钮后,开始按钮和停止按钮状态翻转
            beginScanning.Enabled = true;
            stopScanning.Enabled = false;
        }
    }
}

四、抓包分析

UDP过滤
在这里插入图片描述

Version(版本号):分为 IPv4 和 IPv6 现在普遍都用的 IPv4 ,所以值为 4 ,1 个字节;HLen(ip报头长度):32位字的报头长度(HLEN);TOS(级别):服务类型描述数据报将如何被处理,比如优先发送等,大多数都是默认为 0 ;Datagram Total Length(总长度):包括报头和数据的数据包长度
identifier(标识):唯一的 IP 数据包值;
Flags(标志):说明是否有数据被分段,我是一条一条的发送多个数据包,每个包的数据很小,没有被分段,所以这里数值为 0 。
Fragmentation Offset(分段偏移):如果数据包在装人帧时太大,则需要进行分段和重组,这里没有分段,所以偏移量为 0 ;
TTL(存活期):存活期是在数据包产生时建立在其内部的一个设置,如果这个数据包在这个TTL到期时仍没有到达它要去的目的地,那么它将被丢弃,这个设置将防止IP包在寻找目的地的时候在网络中不断循环,每经过一个路由器,它的值就减一,这里它的值为 128 ,也就是 128 个生存期;Protocol(协议):上层协议的端口( TCP 是端口 6;UDP 是端口 17) ,同样也支持网络层协议,如ARP和ICMP,这里值为 17 ,也就是 UDP 协议;Header Checksum(校验码):只针对报头的循环冗余校验(CRC);Source Address(源地址):消息发送者的 ip 地址,这里是10.60.191.19;Destination Address(目的地址):消息接收者的 ip 地址,这里是10.60.202.32;ip包头的第六行:用于网络检测、调试、安全以及更多的内容,不过大多数情况都是默认为 0 的;Data数据包:可以很显然看到,长度为 34 字节,对应的十六进制就是蓝色的区域了,这就是我们要发送的数据:第n行:hello cqjtu!重交物联2019级

在这里插入图片描述
Form窗口程序
UDP与TCP包的区别
UDP:
在这里插入图片描述
第三行:Internet Protocl Version 4:IPv4协议
第四行:User Datagram Protocol:UDP协议
TCP:
在这里插入图片描述
第三行:Internet Protocl Version 4:IPv4协议
第四行:Transmission Control Protocol:TCP协议

五、总结

通过本次实验,了解到UDP创建Socket时,SOCK_DGRAM指定了这个Socket的类型是UDP。绑定端口和TCP一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据。
在TCP编程里面,accept()方法返回客户端的socket 和 地址及端口, recv()方法用于接收对方发送过来的数据,send() 方法用于向对方发送数据。
在UDP编程里面,recvfrom()方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端。

六.参考文献

https://blog.csdn.net/qq_43279579/article/details/109325176.
https://blog.csdn.net/ssj925319/article/details/109336123.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值