目录
期末考试考到了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仅支持一对一通信;
- UDP与TCP的报头比是8:20,所以相对TCP,UDP消耗更少的网络带宽;
- 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