Socket通信
关于Socket
Socket作为进程通信的机制,是处于网络层中的应用层,说白了就是两个程序间通信用的。
它的形式与电话插座类似,电话的通话双方相当于两个互相通信的程序,电话号相当于IP。
网络通信三要素
IP地址(网络上主机设备的唯一标识,识别一台唯一的主机)
端口号(定位程序,确定两个通信的程序)
有效端口:0~65535,其中0~1023由系统使用,称为公认端口,他们紧密绑定与一些服务。从1024~49151是一些松散的绑定于一些服务,需要注册的一些端口,称为注册端口,剩下的49152~65535为动态端口、私有端口,我们一般开发都是使用这一频段的端口.
传输协议(用什么样的方式进行交互)
常见协议:TCP(面向连接,提供可靠的服务),UDP(无连接,传输速度快),一般使用TCP。
服务端于客户端Socket通信流程
![](https://img2018.cnblogs.com/blog/1667295/201904/1667295-20190424203016101-1072133202.png)
重点记忆两个端的步骤:
服务端: 客户端:
1、创建Socket对象(负责侦听) 1、创建Socket对象
2、绑定端口 2、连接服务器端
3、开启侦听 3、发送消息、接受消息
4、开始接受客户端连接(不断接收,涉及多线程) 4、停止连接
5、创建一个代理Socket对象(负责通信) 5、关闭Socket对象
6、发送、接收消息
7、关闭Socket对象
实现代码
服务端XAML代码(客户端类似):
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
服务端XAML(客户端与服务端相似)
具体服务端实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Net;
5 using System.Net.Sockets;
6 using System.Text;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using System.Windows;
10 using System.Windows.Controls;
11 using System.Windows.Data;
12 using System.Windows.Documents;
13 using System.Windows.Input;
14 using System.Windows.Media;
15 using System.Windows.Media.Imaging;
16 using System.Windows.Navigation;
17 using System.Windows.Shapes;
18
19 namespace SocketDemo
20 {
21 ///
22 /// MainWindow.xaml 的交互逻辑
23 ///
24 public partial class MainWindow : Window
25 {
26 List clientScoketLis = new List();//存储连接服务器端的客户端的Socket
27 public MainWindow()
28 {
29 InitializeComponent();
30 Loaded += MainWindow_Loaded;
31 btnStartServer.Click += BtnStartServer_Click;//事件注册
32 btnSendMsg.Click += BtnSendMsg_Click;
33 Closing += MainWindow_Closing;
34 }
35
36
37 private void MainWindow_Loaded(object sender, RoutedEventArgs e)
38 {
39 ClientWindows clientWindows = new ClientWindows();
40 clientWindows.Show();
41 }
42 ///
43 /// 关闭事件
44 ///
45 private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
46 {
47 //使用foreach出现 “集合已修改;可能无法执行枚举操作”,ClientExit源于方法中对list集合进行了Remove,所造成的异常。
48 //msdn的解释:foreach 语句是对枚举数的包装,它只允许从集合中读取,不允许写入集合。也就是,不能在foreach里遍历的时侯把它的元素进行删除或增加的操作的
49 //foreach (var socket in clientScoketLis)
50 //{
51 // ClientExit(null , socket);
52 //}
53 //改成for循环即可
54 for (int i = 0; i < clientScoketLis.Count; i++)//向每个客户端说我下线了
55 {
56 ClientExit(null, clientScoketLis[i]);
57 }
58 }
59
60 ///
61 /// 开启服务事件
62 ///
63 private void BtnStartServer_Click(object sender, RoutedEventArgs e)
64 {
65 //1、创建Socket对象
66 //参数:寻址方式,当前为Ivp4 指定套接字类型 指定传输协议Tcp;
67 Socket socket = new Socket(AddressFamily.InterNetwork , SocketType.Stream , ProtocolType.Tcp);
68 //2、绑定端口、IP
69 IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(this.txtIp.Text) , int.Parse(txtPort.Text));
70 socket.Bind(iPEndPoint);
71 //3、开启侦听 10为队列最多接收的数量
72 socket.Listen(10);//如果同时来了100个连接请求,只能处理一个,队列中10个在等待连接的客户端,其他的则返回错误消息。
73
74 //4、开始接受客户端的连接 ,连接会阻塞主线程,故使用线程池。
75 ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptClientConnect),socket);
76
77
78 }
79 ///
80 /// 线程池线程执行的接受客户端连接方法
81 ///
82 /// 传入的Socket
83 private void AcceptClientConnect(object obj)
84 {
85 //转换Socket
86 var serverSocket = obj as Socket;
87
88 AppendTxtLogText("服务端开始接收客户端连接!");
89
90 //不断接受客户端的连接
91 while (true)
92 {
93 //5、创建一个负责通信的Socket
94 Socket proxSocket = serverSocket.Accept();
95 AppendTxtLogText(string.Format("客户端:{0}连接上了!", proxSocket.RemoteEndPoint.ToString()));
96 //将连接的Socket存入集合
97 clientScoketLis.Add(proxSocket);
98 //6、不断接收客户端发送来的消息
99 ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveClientMsg) , proxSocket);
100 }
101
102 }
103 ///
104 /// 不断接收客户端信息子线程方法
105 ///
106 /// 参数Socke对象
107 private void ReceiveClientMsg(object obj)
108 {
109 var proxSocket = obj as Socket;
110 //创建缓存内存,存储接收的信息 ,不能放到while中,这块内存可以循环利用
111 byte[] data = new byte[1020*1024];
112 while (true)
113 {
114 int len;
115 try
116 {
117 //接收消息,返回字节长度
118 len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
119 }
120 catch (Exception ex)
121 {
122 //7、关闭Socket
123 //异常退出
124 try
125 {
126 ClientExit(string.Format("客户端:{0}非正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
127 }
128 catch (Exception)
129 {
130 }
131 return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
132 }
133
134 if (len
180 //{
181 // this.txtLog.Text = string.Format("{0}\r\n{1}" , s , txtLog.Text);
182 //}) ,str);
183 //异步方法
184 this.Dispatcher.BeginInvoke(new Action(s =>
185 {
186 this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);
187 }), str);
188 }
189 else
190 {
191 this.txtLog.Text = string.Format("{0}\r\n{1}", str, txtLog.Text);
192 }
193 }
194 ///
195 /// 客户端退出调用
196 ///
197 ///
198 private void ClientExit(string msg , Socket proxSocket)
199 {
200 AppendTxtLogText(msg);
201 clientScoketLis.Remove(proxSocket);//移除集合中的连接Socket
202
203 try
204 {
205 if (proxSocket.Connected)//如果是连接状态
206 {
207 proxSocket.Shutdown(SocketShutdown.Both);//关闭连接
208 proxSocket.Close(100);//100秒超时间
209 }
210 }
211 catch (Exception ex)
212 {
213 }
214 }
215 }
216 }
服务器实现代码
具体客户端实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Net;
5 using System.Net.Sockets;
6 using System.Text;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using System.Windows;
10 using System.Windows.Controls;
11 using System.Windows.Data;
12 using System.Windows.Documents;
13 using System.Windows.Input;
14 using System.Windows.Media;
15 using System.Windows.Media.Imaging;
16 using System.Windows.Shapes;
17
18 namespace SocketDemo
19 {
20 ///
21 /// ClientWindows.xaml 的交互逻辑
22 ///
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 ///
34 /// 窗口关闭事件
35 ///
36 private void ClientWindows_Closing(object sender, System.ComponentModel.CancelEventArgs e)
37 {
38 ServerExit(null,_socket);//向服务端说我下线了。
39 }
40
41 ///
42 /// 连接按钮事件
43 ///
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 ///
65 /// 不断接收客户端信息子线程方法
66 ///
67 /// 参数Socke对象
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
158 //{
159 // this.txtLog.Text = string.Format("{0}\r\n{1}" , s , txtLog.Text);
160 //}) ,str);
161 //异步方法
162 this.Dispatcher.BeginInvoke(new Action(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 }
客户端实现代码
运行展示:
|