.NET Socket通信
2019-04-27 本文已影响0人
Memoyu
认知尚浅,如有错误,愿闻其详!
关于Socket
Socket作为进程通信的机制,是处于网络层中的应用层,说白了就是两个程序间通信用的。
它的形式与电话插座类似,电话的通话双方相当于两个互相通信的程序,电话号相当于IP。
网络通信三要素
-
IP地址(网络上主机设备的唯一标识,识别一台唯一的主机)
-
端口号(定位程序,确定两个通信的程序)
有效端口:0~65535,其中0~1023由系统使用,称为公认端口,他们紧密绑定与一些服务。从1024~49151是一些松散的绑定于一些服务,需要注册的一些端口,称为注册端口,剩下的49152~65535为动态端口、私有端口,我们一般开发都是使用这一频段的端口 -
传输协议(用什么样的方式进行交互)
常见协议:TCP(面向连接,提供可靠的服务),UDP(无连接,传输速度快),一般使用TCP。
服务端于客户端Socket通信流程
Socket流程.png重点记忆两个端的步骤:
服务端: \客户端:
1、创建Socket对象(负责侦听) 1、创建Socket对象
2、绑定端口 2、连接服务器端
3、开启侦听 3、发送消息、接受消息
4、开始接受客户端连接(不断接收,涉及多线程) 4、停止连接
5、创建一个代理Socket对象(负责通信) 5、关闭Socket对象
6、发送、接收消息
7、关闭Socket对象
实现代码
服务端XAML代码(客户端类似):
1 <Window x:Class="SocketDemo.MainWindow"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6 xmlns:local="clr-namespace:SocketDemo"
7 mc:Ignorable="d"
8 Title="MainWindow" Height="472.5" Width="605">
9 <StackPanel>
10 <Canvas Margin="10,20" Height="30">
11 <Label Content="IP:" Height="30" Width="30" FontSize="18" HorizontalContentAlignment="Center" Canvas.Left="8"/>
12 <TextBox x:Name="txtIp" Text="192.168.0.4" Height="30" Width="150" FontSize="20" HorizontalContentAlignment="Center" Canvas.Left="41" />
13 <Label Content="Port:" Height="30" Width="50" FontSize="18" HorizontalContentAlignment="Center" Canvas.Left="210"/>
14 <TextBox x:Name="txtPort" Text="45000" Height="30" Width="150" FontSize="20" HorizontalContentAlignment="Center" Canvas.Left="263" />
15 <Button x:Name="btnStartServer" Content="开启服务" Height="30" Width="100" Canvas.Left="460"/>
16 </Canvas>
17 <TextBox Name="txtLog" Height="300" AcceptsReturn="True" TextWrapping="Wrap"></TextBox>
18 <Canvas Margin="0,20" Height="30">
19 <TextBox x:Name="txtMsg" Height="30" Width="450" FontSize="20" HorizontalContentAlignment="Center" Canvas.Left="0" />
20 <Button x:Name="btnSendMsg" Content="发送消息" Height="30" Width="100" Canvas.Left="470" />
21 </Canvas>
22 </StackPanel>
23 </Window>
具体服务端实现:
namespace SocketDemo
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
List<Socket> clientScoketLis = new List<Socket>();//存储连接服务器端的客户端的Socket
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
btnStartServer.Click += BtnStartServer_Click;//事件注册
btnSendMsg.Click += BtnSendMsg_Click;
Closing += MainWindow_Closing;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
ClientWindows clientWindows = new ClientWindows();
clientWindows.Show();
}
/// <summary>
/// 关闭事件
/// </summary>
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
//使用foreach出现 “集合已修改;可能无法执行枚举操作”,ClientExit源于方法中对list集合进行了Remove,所造成的异常。
//msdn的解释:foreach 语句是对枚举数的包装,它只允许从集合中读取,不允许写入集合。也就是,不能在foreach里遍历的时侯把它的元素进行删除或增加的操作的
//foreach (var socket in clientScoketLis)
//{
// ClientExit(null , socket);
//}
//改成for循环即可
for (int i = 0; i < clientScoketLis.Count; i++)//向每个客户端说我下线了
{
ClientExit(null, clientScoketLis[i]);
}
}
/// <summary>
/// 开启服务事件
/// </summary>
private void BtnStartServer_Click(object sender, RoutedEventArgs e)
{
//1、创建Socket对象
//参数:寻址方式,当前为Ivp4 指定套接字类型 指定传输协议Tcp;
Socket socket = new Socket(AddressFamily.InterNetwork , SocketType.Stream , ProtocolType.Tcp);
//2、绑定端口、IP
IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(this.txtIp.Text) , int.Parse(txtPort.Text));
socket.Bind(iPEndPoint);
//3、开启侦听 10为队列最多接收的数量
socket.Listen(10);//如果同时来了100个连接请求,只能处理一个,队列中10个在等待连接的客户端,其他的则返回错误消息。
//4、开始接受客户端的连接 ,连接会阻塞主线程,故使用线程池。
ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptClientConnect),socket);
}
/// <summary>
/// 线程池线程执行的接受客户端连接方法
/// </summary>
/// <param name="obj">传入的Socket</param>
private void AcceptClientConnect(object obj)
{
//转换Socket
var serverSocket = obj as Socket;
AppendTxtLogText("服务端开始接收客户端连接!");
//不断接受客户端的连接
while (true)
{
//5、创建一个负责通信的Socket
Socket proxSocket = serverSocket.Accept();
AppendTxtLogText(string.Format("客户端:{0}连接上了!", proxSocket.RemoteEndPoint.ToString()));
//将连接的Socket存入集合
clientScoketLis.Add(proxSocket);
//6、不断接收客户端发送来的消息
ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveClientMsg) , proxSocket);
}
}
/// <summary>
/// 不断接收客户端信息子线程方法
/// </summary>
/// <param name="obj">参数Socke对象</param>
private void ReceiveClientMsg(object obj)
{
var proxSocket = obj as Socket;
//创建缓存内存,存储接收的信息 ,不能放到while中,这块内存可以循环利用
byte[] data = new byte[1020*1024];
while (true)
{
int len;
try
{
//接收消息,返回字节长度
len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
}
catch (Exception ex)
{
//7、关闭Socket
//异常退出
try
{
ClientExit(string.Format("客户端:{0}非正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
}
catch (Exception)
{
}
return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
}
if (len <= 0)//判断接收的字节数
{
//7、关闭Socket
//小于0表示正常退出
try
{
ClientExit(string.Format("客户端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
}
catch (Exception)
{
}
return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
}
//将消息显示到TxtLog
string msgStr = Encoding.Default.GetString(data , 0 , len);
//拼接字符串
AppendTxtLogText(string.Format("接收到客户端:{0}的消息:{1}" , proxSocket.RemoteEndPoint.ToString() , msgStr));
}
}
/// <summary>
/// 消息发送事件
/// </summary>
private void BtnSendMsg_Click(object sender, RoutedEventArgs e)
{
foreach (Socket proxSocket in clientScoketLis)
{
if (proxSocket.Connected)//判断客户端是否还在连接
{
byte[] data = Encoding.Default.GetBytes(this.txtMsg.Text);
//6、发送消息
proxSocket.Send(data , 0 , data.Length , SocketFlags.None); //指定套接字的发送行为
this.txtMsg.Text = null;
}
}
}
/// <summary>
/// 向文本框中追加信息
/// </summary>
/// <param name="str"></param>
private void AppendTxtLogText( string str)
{
if (!(txtLog.Dispatcher.CheckAccess()))//判断跨线程访问
{
////同步方法
//this.Dispatcher.Invoke(new Action<string>( s =>
//{
// this.txtLog.Text = string.Format("{0}\r\n{1}" , s , txtLog.Text);
//}) ,str);
//异步方法
this.Dispatcher.BeginInvoke(new Action<string>(s =>
{
this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);
}), str);
}
else
{
this.txtLog.Text = string.Format("{0}\r\n{1}", str, txtLog.Text);
}
}
/// <summary>
/// 客户端退出调用
/// </summary>
/// <param name="msg"></param>
private void ClientExit(string msg , Socket proxSocket)
{
AppendTxtLogText(msg);
clientScoketLis.Remove(proxSocket);//移除集合中的连接Socket
try
{
if (proxSocket.Connected)//如果是连接状态
{
proxSocket.Shutdown(SocketShutdown.Both);//关闭连接
proxSocket.Close(100);//100秒超时间
}
}
catch (Exception ex)
{
}
}
}
}
具体客户端实现:
17
18 namespace SocketDemo
19 {
20 /// <summary>
21 /// ClientWindows.xaml 的交互逻辑
22 /// </summary>
23 public partial class ClientWindows : Window
24 {
25 private Socket _socket;
26 public ClientWindows()
27 {
28 InitializeComponent();
29 btnSendMsg.Click += BtnSendMsg_Click;//注册事件
30 btnConnect.Click += BtnConnect_Click;
31 Closing += ClientWindows_Closing;
32 }
33 /// <summary>
34 /// 窗口关闭事件
35 /// </summary>
36 private void ClientWindows_Closing(object sender, System.ComponentModel.CancelEventArgs e)
37 {
38 ServerExit(null,_socket);//向服务端说我下线了。
39 }
40
41 /// <summary>
42 /// 连接按钮事件
43 /// </summary>
44 private void BtnConnect_Click(object sender, RoutedEventArgs e)
45 {
46 //1、创建Socket对象
47 Socket socket = new Socket(AddressFamily.InterNetwork , SocketType.Stream , ProtocolType.Tcp);
48 _socket = socket;
49 //2、连接服务器,绑定IP 与 端口
50 IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(txtIp.Text) , int.Parse(txtPort.Text));
51 try
52 {
53 socket.Connect(iPEndPoint);
54 }
55 catch (Exception)
56 {
57 MessageBox.Show("连接失败,请重新连接!","提示");
58 return;
59 }
60 //3、接收消息
61 ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveServerMsg),socket);
62 }
63
64 /// <summary>
65 /// 不断接收客户端信息子线程方法
66 /// </summary>
67 /// <param name="obj">参数Socke对象</param>
68 private void ReceiveServerMsg(object obj)
69 {
70 var proxSocket = obj as Socket;
71 //创建缓存内存,存储接收的信息 ,不能放到while中,这块内存可以循环利用
72 byte[] data = new byte[1020 * 1024];
73 while (true)
74 {
75 int len;
76 try
77 {
78 //接收消息,返回字节长度
79 len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
80 }
81 catch (Exception ex)
82 {
83 //7、关闭Socket
84 //异常退出
85 try
86 {
87 ServerExit(string.Format("服务端:{0}非正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
88 }
89 catch (Exception)
90 {
91
92 }
93 return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
94 }
95
96 if (len <= 0)//判断接收的字节数
97 {
98 //7、关闭Socket
99 //小于0表示正常退出
100 try
101 {
102 ServerExit(string.Format("服务端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
103 }
104 catch (Exception)
105 {
106
107 }
108 return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
109 }
110 //将消息显示到TxtLog
111 string msgStr = Encoding.Default.GetString(data, 0, len);
112 //拼接字符串
113 AppendTxtLogText(string.Format("接收到服务端:{0}的消息:{1}", proxSocket.RemoteEndPoint.ToString(), msgStr));
114 }
115 }
116
117 /// <summary>
118 /// 客户端退出调用
119 /// </summary>
120 /// <param name="msg"></param>
121 private void ServerExit(string msg, Socket proxSocket)
122 {
123 AppendTxtLogText(msg);
124 try
125 {
126 if (proxSocket.Connected)//如果是连接状态
127 {
128 proxSocket.Shutdown(SocketShutdown.Both);//关闭连接
129 proxSocket.Close(100);//100秒超时间
130 }
131 }
132 catch (Exception ex)
133 {
134 }
135 }
136
137 /// <summary>
138 /// 发送信息按钮事件
139 /// </summary>
140 private void BtnSendMsg_Click(object sender, RoutedEventArgs e)
141 {
142 byte[] data = Encoding.Default.GetBytes(this.txtMsg.Text);
143 //6、发送消息
144 _socket.Send(data, 0, data.Length, SocketFlags.None); //指定套接字的发送行为
145 this.txtMsg.Text = null;
146 }
147
148 /// <summary>
149 /// 向文本框中追加信息
150 /// </summary>
151 /// <param name="str"></param>
152 private void AppendTxtLogText(string str)
153 {
154 if (!(txtLog.Dispatcher.CheckAccess()))//判断跨线程访问
155 {
156 ////同步方法
157 //this.Dispatcher.Invoke(new Action<string>( s =>
158 //{
159 // this.txtLog.Text = string.Format("{0}\r\n{1}" , s , txtLog.Text);
160 //}) ,str);
161 //异步方法
162 this.Dispatcher.BeginInvoke(new Action<string>(s =>
163 {
164 this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);
165 }), str);
166 }
167 else
168 {
169 this.txtLog.Text = string.Format("{0}\r\n{1}", str, txtLog.Text);
170 }
171 }
172 }
173 }