一、TCP/UDP
Socket 套接字是支持TCP/IP协议的网络通信的基本操作单元。可以将套接字看作不同主机间的进程进行双向通信的端点,它构成了单个主机内及整个网络间的编程界面。 套接字的工作原理: 通过互联网进行通信,至少需要一对套接字,其中一个运行于客户机端,称之为ClientSocket,另一个运行于服务器端,称之为ServerSocket。 套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
1.1 TCP
TCP协议提供的是端到端服务。TCP协议所提供的端到端的服务是保证信息一定能够到达目的地址。它是一种面向连接的协议。
TCP编程的服务器端一般步骤:
- 创建一个socket,用函数socket()
- 绑定IP地址、端口等信息到socket上,用函数bind()
- 开启监听,用函数listen()
- 接收客户端上来的连接,用函数accept()
- 收发数据,用函数send()和recv(),或者read()和write()
- 关闭网络连接
- 关闭监听
客户端一般步骤:
- 创建一个socket,用函数socket()
- 设置要连接的对方的IP地址和端口等属性
- 连接服务器,用函数connect()
- 收发数据,用函数send()和recv(),或者read()和write()
- 关闭网络连接
1.2 UDP
UDP协议提供了一种不同于TCP协议的端到端服务。UDP协议所提供的端到端传输服务是尽力而为(best-effort)的,即UDP套接字将尽可能地传送信息,但并不保证信息一定能成功到达目的地址,而且信息到达的顺序与其发送顺序不一定一致。
服务器端:
- 创建一个socket,用函数socket()
- 绑定IP地址、端口等信息到socket上,用函数bind()
- 循环接收数据,用函数recvfrom()
- 关闭网络连接
客户端:
- 创建一个socket,用函数socket()
- 设置对方的IP地址和端口等属性
- 发送数据,用函数sendto()
- 关闭网络连接
二、控制台程序使用 UDP 通信
编译工具:visual studio 2019
2.1 创建控制台程序
在屏幕上连续输出50行“hello cqjtu!重交物联2019级”
1.创建新项目
创建成功,如下图所示:
2.代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace test
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 50; i++)
{
Console.WriteLine("hello cqjtu!重交物联2019级", (i + 1));
}
System.Console.ReadKey();
}
}
}
3.编译输出结果
2.2 UDP通信
打开一个网络UDP 套接字,向另一台室友电脑发送这50行消息
我的电脑运行客户端程序,室友电脑运行服务器端程序,从而实现简单的通信功能
使用网络协议需要引入头文件.Net和.Net.Sockets,要实现两台电脑需在同一个网络下
服务端:
步骤: 1.实例化并设置socket实例对象 创建IP地址和端口
IPEndPoint ip = new IPEndPoint(IPAddress.Any,8001);
绑定监听地址:
socket.Bind(ip);
2.连接
IPEndPoint send = new IPEndPoint(IPAddress.Any, 0);
EndPoint Remote = (EndPoint)(send);
3.接收客户端发送过来的消息
receive = socket.ReceiveFrom(data, ref Remote);
4.完整代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace test
{
class Program
{
static void Main(string[] args)
{
int receive;
byte[] data = new byte[1024];
IPEndPoint ip = new IPEndPoint(IPAddress.Any,8080);
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,ProtocolType.Udp);
socket.Bind(ip);
Console.WriteLine("This is a Server, host name is {0}", Dns.GetHostName());
Console.WriteLine("正在连接客户机...");
IPEndPoint send = new IPEndPoint(IPAddress.Any, 0);
EndPoint Remote = (EndPoint)(send);
receive = socket.ReceiveFrom(data, ref Remote);
Console.WriteLine("Message received from {0}: ", Remote.ToString());
Console.WriteLine(Encoding.UTF8.GetString(data, 0, receive));
string message = "你好!客户端";
data = Encoding.UTF8.GetBytes(message);
socket.SendTo(data, data.Length, SocketFlags.None, Remote);
while(true)
{
data = new byte[1024];
receive = socket.ReceiveFrom(data, ref Remote);
Console.WriteLine(Encoding.UTF8.GetString(data, 0, receive));
}
}
}
}
客户端:
客户端程序与服务器程序非常类似。由于客户机不需要在指定的UDP端口等待流入的数据,所以不使用Bind()方法来绑定监听地址。而是在数据发送时使用系统随机指定的一个UDP端口。
完整代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace test1
{
class Program
{
static void Main(string[] args)
{
byte[] data = new byte[1024];
Console.WriteLine("This is a Client, host name is {0}", Dns.GetHostName());
IPEndPoint ip = new IPEndPoint(IPAddress.Parse("192.168.0.102"), 8080);
Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
string welcome = "你好! 服务器端";
data = Encoding.UTF8.GetBytes(welcome);
server.SendTo(data, data.Length, SocketFlags.None, ip);
IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
EndPoint Remote = (EndPoint)sender;
data = new byte[1024];
int recv = server.ReceiveFrom(data, ref Remote);
Console.WriteLine("Message received from {0}: ", Remote.ToString());
Console.WriteLine(Encoding.UTF8.GetString(data, 0, recv));
Console.WriteLine("按下任意按键开始发送...");
Console.ReadKey();
int i = 0;
while (i<=50)
{
string s = "hello cqjtu!重交物联2019级" + i;
Console.WriteLine(s);
server.SendTo(Encoding.UTF8.GetBytes(s), Remote);
i++;
}
Console.WriteLine("发送完毕!");
server.Close();
Console.WriteLine("按下任意按键退出发送...");
Console.ReadKey();
}
}
}
注意:要先运行服务器端代码,在运行客户端代码
2.3 结果
服务器端: 客户端:
3.4 抓包分析数据
在方框内输入 “ UDP ” 过滤包,然后就可以看到下面的信息,这些就是室友电脑发给我的 50 条数据,下面开始分析这些数据,只选择其中一条分析。 当室友电脑给我发送消息的时候,就能抓取包,貌似自己电脑给自己发消息抓不到包,建议两台电脑进行发送数据抓包
客户端抓包:
源地址是192.168.43.112,目的地址是192.168.43.236,使用协议为UDP,端口号为8080,长度为33字节
服务器端抓包:
三、Form窗口程序使用 TCP 通信
3.1 创建项目
创建成功如下图所示:
3.2 设计界面
我们这里需要3个控件,2 个 TextBox 控件和 1 个 Button 控件
1.设计消息显示界面
注意:所有控件的name属性不能重复,也不能有中文
- 设置消息显示界面的 TextBox 不可编辑:找到 Enabled 属性,参数设为 False 。
2.设计消息发送框
3.设计Button按钮
-
name为button1 -
修改Text属性
4.设计窗体
3.3 代码编写
红色方框后就是我们双击button控件后,自动生成的函数体
注意头文件.Net和.Net.Sockets
客户端:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
try
{
string str;
IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("192.168.0.102"), 8080);
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
str = "连接服务器中...";
textBox1.AppendText(str + Environment.NewLine);
socket.Connect(iPEndPoint);
string sendmessage = textBox2.Text;
str = "消息内容: " + sendmessage;
textBox1.AppendText(str + Environment.NewLine);
byte[] bs = Encoding.UTF8.GetBytes(sendmessage);
str = "正在发送给服务器...";
textBox1.AppendText(str + Environment.NewLine);
socket.Send(bs, bs.Length, 0);
string recvStr = "";
byte[] recvBytes = new byte[1024];
int bytes;
bytes = socket.Receive(recvBytes, recvBytes.Length, 0);
recvStr += Encoding.UTF8.GetString(recvBytes, 0, bytes);
str = "服务器返回信息:" + recvStr;
textBox1.AppendText(str + Environment.NewLine);
socket.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 = "";
}
}
}
服务器端:
创建一个控制台应用,像前面UDP通信
代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
int i = 0;
IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Any, 8080);
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(iPEndPoint);
while (true)
{
i++;
try
{
Console.WriteLine("Perform operations {0} :", i);
Console.WriteLine("************************************");
socket.Listen(0);
Console.WriteLine("等待连接...");
Socket temp = socket.Accept();
Console.WriteLine("连接成功!");
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("消息内容:{0}", recvStr);
string sendmessage = "成功发送消息,客户端!";
byte[] bs = Encoding.UTF8.GetBytes(sendmessage);
temp.Send(bs, bs.Length, 0);
temp.Close();
Console.WriteLine("Completed...");
Console.WriteLine("-----------------------------------");
}
catch (ArgumentNullException e)
{
Console.WriteLine("ArgumentNullException: {0}", e);
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
}
}
}
}
3.4 结果展示
客户端:
服务器端:
3.5 抓包分析数据
看一下 UDP 包和 TCP 包的区别: UDP: Internet Protocl Version 4:IPv4协议 User Datagram Protocol:UDP协议 TCP: Internet Protocl Version 4:IPv4协议 Transmission Control Protocol:TCP协议 可以看到都是IPv4协议,区别在于第四行,其余都差不多
3.6 端口扫描程序
单线程
单线程方式去进行会非常慢,会非常卡,只能通过停止调试按钮去关闭
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Threading;
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
private string hostAddress;
private int start;
private int end;
private int port;
public Form1()
{
InitializeComponent();
}
private void label2_Click(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
try
{
textBox4.Clear();
label5.Text = "0%";
hostAddress = textBox1.Text;
start = Int32.Parse(textBox2.Text);
end = Int32.Parse(textBox3.Text);
if (decideAddress())
{
textBox1.ReadOnly = true;
textBox2.ReadOnly = true;
textBox3.ReadOnly = true;
progressBar1.Minimum = start;
progressBar1.Maximum = end;
textBox4.AppendText("端口扫描器 v1.0.0" + Environment.NewLine + Environment.NewLine);
PortScan();
}
else
{
MessageBox.Show("输入错误,端口范围为[0-65536]!");
}
}
catch
{
MessageBox.Show("输入错误,端口范围为[0-65536]!");
}
}
private bool decideAddress()
{
if ((start >= 0 && start <= 65536) && (end >= 0 && end <= 65536) && (start <= end))
return true;
else
return false;
}
private void PortScan()
{
double x;
string xian;
textBox4.AppendText("开始扫描...(可能需要请您等待几分钟)" + Environment.NewLine + Environment.NewLine);
for (int i = start; i <= end; i++)
{
x = (double)(i - start + 1) / (end - start + 1);
xian = x.ToString("0%");
port = i;
Scan();
label5.Text = xian;
label5.Refresh();
progressBar1.Value = i;
}
textBox4.AppendText(Environment.NewLine + "扫描结束!" + Environment.NewLine);
textBox1.ReadOnly = false;
textBox2.ReadOnly = false;
textBox3.ReadOnly = false;
}
private void Scan()
{
int portnow = port;
TcpClient objTCP = null;
try
{
objTCP = new TcpClient(hostAddress, portnow);
textBox4.AppendText("端口 " + port + " 开放!" + Environment.NewLine);
}
catch
{
}
}
}
}
结果:
多线程
相比于单线程,多线程快很多 注意: 构造函数里的: CheckForIllegalCrossThreadCalls = false; 可以直接跳过跨线程检查,如果程序不当,会造成死循环,在本程序中不直接跳过会出现问题;建议使用委托 delegate
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Threading;
namespace WindowsFormsApp3
{
public partial class Form1 : Form
{
private string hostAddress;
private int start;
private int end;
private int port;
private Thread scanThread;
private bool[] done = new bool[65526];
private bool OK;
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
}
private void button1_Click(object sender, EventArgs e)
{
try
{
textBox4.Clear();
label5.Text = "0%";
hostAddress = textBox1.Text;
start = Int32.Parse(textBox2.Text);
end = Int32.Parse(textBox3.Text);
if (decideAddress())
{
textBox1.ReadOnly = true;
textBox2.ReadOnly = true;
textBox3.ReadOnly = true;
Thread process = new Thread(new ThreadStart(PortScan));
process.Start();
progressBar1.Minimum = start;
progressBar1.Maximum = end;
textBox4.AppendText("端口扫描器 v1.0.0" + Environment.NewLine + Environment.NewLine);
}
else
{
MessageBox.Show("输入错误,端口范围为[0-65536]!");
}
}
catch
{
MessageBox.Show("输入错误,端口范围为[0-65536]!");
}
}
private bool decideAddress()
{
if ((start >= 0 && start <= 65536) && (end >= 0 && end <= 65536) && (start <= end))
return true;
else
return false;
}
private void PortScan()
{
double x;
string xian;
textBox4.AppendText("开始扫描...(可能需要请您等待几分钟)" + Environment.NewLine + Environment.NewLine);
for (int i = start; i <= end; i++)
{
x = (double)(i - start + 1) / (end - start + 1);
xian = x.ToString("0%");
port = i;
scanThread = new Thread(new ThreadStart(Scan));
scanThread.Start();
System.Threading.Thread.Sleep(100);
label5.Text = xian;
progressBar1.Value = i;
}
while (!OK)
{
OK = true;
for (int i = start; i <= end; i++)
{
if (!done[i])
{
OK = false;
break;
}
}
System.Threading.Thread.Sleep(1000);
}
textBox4.AppendText(Environment.NewLine + "扫描结束!" + Environment.NewLine);
textBox1.ReadOnly = false;
textBox2.ReadOnly = false;
textBox3.ReadOnly = false;
}
private void Scan()
{
int portnow = port;
Thread Threadnow = scanThread;
done[portnow] = true;
TcpClient objTCP = null;
try
{
objTCP = new TcpClient(hostAddress, portnow);
textBox4.AppendText("端口 " + port + " 开放!" + Environment.NewLine);
}
catch
{
}
}
}
}
结果:
四、参考文献
C#使用TCP/UDP协议通信并用Wireshark抓包分析数据 C#利用套接字实现数据发送 编写端口扫描器程序
|