C#使用套接字进行数据传输
一、Socket、TCP、UDP
1. Socket
所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。
套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
2. TCP
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。
TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。
TCP编程的服务器端一般步骤:
- 创建一个socket,用函数socket()
- 绑定IP地址、端口等信息到socket上,用函数bind()
- 开启监听,用函数listen()
- 接收客户端上来的连接,用函数accept()
- 收发数据,用函数send()和recv(),或者read()和write()
- 关闭网络连接;
- 关闭监听;
TCP编程的客户端一般步骤:
- 创建一个socket,用函数socket()
- 设置要连接的对方的IP地址和端口等属性
- 连接服务器,用函数connect()
- 收发数据,用函数send()和recv(),或者read()和write()
- 关闭网络连接
3. UDP
UDP 是User Datagram Protocol的简称, 中文名是用户数据包协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768 是UDP的正式规范。UDP在IP报文的协议号是17。
UDP协议与TCP协议一样用于处理数据包,在OSI模型中,两者都位于传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但即使在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。
UDP编程的服务器端一般步骤:
- 创建一个socket,用函数socket()
- 绑定IP地址、端口等信息到socket上,用函数bind()
- 循环接收数据,用函数recvfrom()
- 关闭网络连接
UDP编程的客户端一般步骤:
- 创建一个socket,用函数socket()
- 设置对方的IP地址和端口等属性
- 发送数据,用函数sendto()
- 关闭网络连接
二、C#实现UDP套接字发送信息
在VS2019
中新建项目:
选择C#的控制台应用:
设置项目位置和项目名:
1. 控制台显示信息
编写代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Send1
{
class Program
{
static void Main(string[] args)
{
for(int i = 0; i < 50; i++)
{
Console.WriteLine("hello cqjtu!重交物联2019级");
}
System.Console.ReadLine();
}
}
}
运行结果:
2. 实现UDP套接字发送信息
- 服务端代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Send1
{
class MyServer
{
static void Main()
{
int result;
string str = "第50行:hello cqjtu!重交物联2019级";
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);
result = String.Compare(receiveString, str);
if (result == 0)
{
break;
}
}
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 Send1_C
{
class Program
{
static void Main()
{
//提示信息
Console.WriteLine("按下任意按键开始发送...");
Console.ReadKey();
int m;
//做好链接准备
UdpClient client = new UdpClient(); //实例一个端口
IPAddress remoteIP = IPAddress.Parse("127.0.0.1"); //假设发送给这个IP
int remotePort = 11000; //设置端口号
IPEndPoint remotePoint = new IPEndPoint(remoteIP, remotePort); //实例化一个远程端点
for (int i = 0; i < 50; i++)
{
//要发送的数据:第n行:hello cqjtu!重交物联2019级
string sendString = null;
sendString += "第";
m = i + 1;
sendString += m.ToString();
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();
}
}
}
运行结果:
客户端:
服务端:
3. Wireshark抓包
可发现抓取了50条数据
可查到该信息为第8行:hello cqjtu!重交物联2019级
三、C#使用窗口程序发送信息(TCP)
1. 新建项目并设计界面
创建新项目:
选择Windows 窗体应用(.Net Framework)
:
设置项目名和路径:
初始界面如下:
在视图
中打开工具箱
:
拖拽出一个Button
,两个TextBox
:
- 配置显示消息的
TextBox
的属性
点击上面的TextBox
的箭头,设置为多行:
添加其垂直滚动条:
设置其边界样式为FixedSingle
:
设置只能读:
设置name:
- 配置输入消息的
TextBox
的属性
设置其name:
设置字体和大小:
- 配置
Button
属性
设置其文本:
设置其name:
- 配置窗体
Form
属性
点击选择该窗体:
设置其标题:
设置窗体的接受按钮,即按下 Enter 键触发该按钮的点击事件:
设置自动适应大小:
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;
点击事件:
private void button1_Click(object sender, EventArgs e)
{
try
{
/*
* 显示当前时间
*/
string str = "The current time: ";
str += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
tbShowMsg.AppendText(str + Environment.NewLine);
/*
* 做好连接准备
*/
int port = 2000;
string host = "127.0.0.1";//目的地IP地址
IPAddress ip = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ip, port);//把ip和端口转化为IPEndPoint实例
Socket c = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建一个Socket
/*
* 开始连接
*/
str = "Connect to server...";
tbShowMsg.AppendText(str + Environment.NewLine);
c.Connect(ipe);//连接到服务器
/*
*发送消息
*/
string sendStr = tbInput.Text;
str = "The message content: " + sendStr;
tbShowMsg.AppendText(str + Environment.NewLine);
byte[] bs = Encoding.UTF8.GetBytes(sendStr);
str = "Send the message to the server...";
tbShowMsg.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;//显示服务器返回信息
tbShowMsg.AppendText(str + Environment.NewLine);
/*
* 关闭socket
*/
c.Close();
}
catch (ArgumentNullException f)
{
string str = "ArgumentNullException: " + f.ToString();
tbShowMsg.AppendText(str + Environment.NewLine);
}
catch (SocketException f)
{
string str = "ArgumentNullException: " + f.ToString();
tbShowMsg.AppendText(str + Environment.NewLine);
}
tbShowMsg.AppendText("" + Environment.NewLine);
tbInput.Text = "";
}
3. 服务端编写
新建一个控制台应用程序,在主函数中编写如下代码:
static void Main(string[] args)
{
/*
* 做好连接准备
*/
int i = 0;
int port = 2000;
string host = "127.0.0.1";
IPAddress ip = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ip, port);
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建一个Socket类
s.Bind(ipe);//绑定2000端口
/*
* 循环监听并处理消息
*/
while (true)
{
i++;
try
{
Console.Write("Perform operations {0} :", i);
Console.WriteLine("\t-----------------------------------------------");
s.Listen(0);//开始监听
Console.WriteLine("1. Wait for connect...");
/*
* 实例一个新的socket端口
*/
Socket temp = s.Accept();//为新建连接创建新的Socket。
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("");
//s.Close();//关闭socket(由于再死循环中,所以不用写,但如果是单个接收,实例socket并完成任务后需关闭)
}
catch (ArgumentNullException e)
{
Console.WriteLine("ArgumentNullException: {0}", e);
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
}
}
4. 运行结果
客户端与服务端都运行后,在客户端发送信息:
客户端
:
服务端
:
5. Wireshark抓包
四、总结
使用套接字发送信息十分方便。