C# Socket 使用教程
我在学习Socket时,总是感觉文章看不懂,视频又好长,所以留下这篇学习笔记,权当做同学间学习参考,与个人回顾吧.
简介
Socket(译做:管道/套接字)是一个便捷的类
用于封装通信时所涉及到复杂底层逻辑
也正因此我们就可以使用十分简便且直观的Socket类,并调用其中方法,就可轻松地实现数据通信/计算机间数据传输.
正文
所以,在Socket中,抽象出了服务端和客户端的概念,并提供了其所需要的方法.
服务端
服务端的Socket实现流程是这样的:
1.创建socket对象
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//first is 寻址方式(此处是Ipv4)
//second is the socket's type(此处是流式传输)
//And the thrid is the 传输协议(或者说是信息传输方法)
2.绑定端口IP
我这里是从我写好的Winform窗体中获取的字符串.
其中txtIP是本机(服务端设备)IP地址;txtPort里填的是端口号,
我这里就先用的是31201,大家也可以在10000-6000之间随便填(好像是)
socket.Bind(new IPEndPoint(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text)));
//socket's Bind action need a IPEndPoint to bind the socket and the computer's ip and post
//(socket的Bing方法需要一个IPEndPoint来绑定socket和计算机的ip地址还有端口)
//so in the IPEndPoint we need a IPAddress and a int number(Post)
//所以在这个IPEndPoint中,我们需要一个IPAddress和一个数字(端口号)
3.开始侦听
socket.Listen(12233);//这个数字表示:同时排队的最大限制数量
//(这里是乱填的,一般改成100左右就可以了吧)
//是什么意思呢?:同时来了20000个链接请求,只能处理一个链接,
//队列里面放12233个等待链接的客户端,其他的返回错误消息。
4.开始接受客户端的链接
Socket proxSocket = serverSocket.Accept();//最好新开辟一个线程用来运行它
//this action is so especial(特殊) ,because the action will be 阻塞当前线程
//就是让它所在的线程停止工作,停在这个方法,
//直到侦测到客户端的请求才会返回一个代理Socket类型的变量
//(里面保存着客户端用来连接服务器端的socket的信息),然后继续往下执行
//So 这个方法每执行一次,往往就代表着,接收到了一个请求,并返回一个 代理Socket
//我们就可以读取这个代理socket的信息,并且调用它的Send方法,就可以实现信息的传递啦
5.接收数据
byte[] data = new byte[1024 * 1024];
proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
string str = Encoding.UTF8.GetString(data, 0, len);//把拿到的二进制数字转换成字符串
//这个是拿到了代理的Socket对象,然后就Receive就行了
//挺抽象的是:这个也会像Accept一样阻塞住当前线程
//最后这个叫SocketFalgs枚举我也不知道是干啥的
并提供了向客户端传输数据的方法
byte[] data = Encoding.UTF8.GetBytes(txtMsg.Text);
proxSocket.Send(data, 0, data.Length, SocketFlags.None);//发送消息
//其中Encoding是一个类调用其中的静态UTF8方法会实例化一个Encoding的对象,
//然后调用其中的GetBytes方法,就会按照UTF8编码,把()中的内容转换为Bytes
//然后才能用Stream也就是流的传输模式来传输数据
客户端
客户端传输数据的逻辑和服务端十分类似,或者说,现在所提出的所谓从客户端与服务端两个方面去理解Socket,本身就是一个类的两种不同使用方法,所以,读到这里的同志,也请一定注意:Socket 本质上只是封装了通信功能的一个C#类,不要偏颇的仅仅以服务端和客户端的角度,去看待Socket,这是不全面的,服务端和客户端的功能,只是比较常见的需求,并应用了Socket技术,如此而已.
接下来是客户端的应用过程:
1.创建Socket对象
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
2.连接服务器端Socket
try//如果服务器的Listen队列已满,那么就会返回一个异常
{
socket.Connect(new IPEndPoint(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text)));
//注意这里的txtIP.Text和txtPort.Text所填写的ip地址和端口都是服务端的
//逻辑上是:只要服务端的IP和端口固定了,那么客户端去找他就可以了,
//在连接之后,服务端就可以用,客户端连接后 自动生成的 代理Socket 与客户端进行消息的传递就可以了
}
catch
{
MessageBox.Show("当前服务器无法连接,请重试");
//如果愿意的话,可以把这段代码写成一个方法,然后在catch中调用自身,
//但是,要是一直连接不上的话,就怕一直递归下去会爆栈,所以还是有点危险的,
//有点影响程序鲁棒性,所以写个MessageBox得了
//暂停一秒,然后继续:
//Thread.Sleep(1000);
//调用自身方法体
3.接收服务器端消息
byte[] data = new byte[1024 * 1024];//一个可以存放1M数据的数组
int len = socket.Receive(data, 0, data.Length, SocketFlags.None);
//流式传输放到data里,len用来保存方法返回值,这个值是方法传递的字节数
string str = Encoding.UTF8.GetString(data, 0, len);
//服务和客户端双方一定要都用一套字符集,推荐UTF-8,
//如果是Defualt的话,他会按照系统默认字符集来传输/读取数据,
//字符集不同会出现乱码!
4.发送消息
byte[] data = Encoding.UTF8.GetBytes(txtMsg.Text);
socket.Send(data, 0, data.Length, SocketFlags.None);//发送消息
注意哦,我给出的代码例子,往往是可以直接在一个方法中使用的,但是,我不推荐这么做:
一是违反了面向对象的Single Responsibility Principle(单一责任原则);
二是,我给出的代码为了保证可读性(变量名可理解性)取名很直接.
尤其是在字符数组变量的data,存在变量名重复的问题,这时要么可以选择把他们放在不同的方法中,要么更改变量名,但是我更推荐把他们写在不同的方法中!
最后向大家说明一下,为什么客户端没有绑定IP地址和Post呢?
因为客户端在进行socket网络通信时,所携带的数据包就会包含自己的IP,而发送消息时所使用的端口则是由计算机自动分配的,
在第一次通信后,服务器就会保存相应的客户端ip和post到代理socket,以此来和客户端进行通信.
到了最最后,我想我应当给大家一些多线程的写法,以帮助大家写出更加丰满的程序,否则按照这个教程以上的知识,我们只能写出1对1的沟通程序,而这往往是具有一定局限性的.
Thread thread = new Thread(ParameterizedThrendStart((obj) = { }));//这里使用了有参委托
//ParameterizedThrendStart是官方提供的有参委托
//或者
//Thread thread = new Thread(ThrendStart(() = { }));
//ThrendStart是官方提供的无参委托
thread.IsBackground = true;
thread.Start(socket);
我想大家如果有一定C#基础的话可以看出,在这个线程的开辟中我们使用了,委托和Lambda表达式的相关知识,如果没学过可以去学,或者等我出学习日志,咱们一起学,咳咳,这些是后话
接下来更改了IsBackground的属性,将进程标志成了后台进程,然后开启了这个线程,线程将在执行完所有的ParmeterizedThrendStart委托中的方法后结束
C# IsBackground作用
这是前台进程和后台进程的区别
C#多线程系列(1):Thread
这是多线程系列课程
最最最后:
总结一下socket中常用的方法:
Bind
//绑定socket和ip地址与端口号
Listen
//表示的等待的链接上限,往往也与电脑配置有关,所以输一个超大的数,除了降低程序的安全性好像没啥大用,尽可能自己设置一个合理的上限吧
Accept
//等待连接,这个是服务端常用的一个方法,也是服务端和客户端通信最重要的一句话,创造连接,但是会阻塞线程,最好放在一个新的Thread里
Receive
//接收别的Thread给该程序(本机IP:程序占用端口)的数据,如果没有接收到,也会阻塞线程,最好也放在一个新的Thread里
Send
//Send用于发送某个东西,给这个socket,所以我们在客户端只需要服务端的IP地址,就是这个原因,我们得知道信息发送到哪去,而服务端,只需要调用Accept方法,等待客户端连接就可以了
Connect(new IPEndPoint(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text)));
//这个是只有客户端才用的,因为你得知道要发给谁才行,然后就是自动与这个地址的Accept联系就可以了
但是话又说回来,Accept,Receive,还有Connect往往都是很容易抛异常的,所以最好try一下
总结:
实质上,我们去观察Socket,往往就能发现,我在本机运行和调用Socket中的方法,实现通信,而我要一定要给予socket的的往往是我们要联系的目标的IP地址和端口,而我们本地,socket就可以与系统沟通,然后便宜行事了.
(类似于我知道对方的手机号,就可以通信,但是我自己的手机号,由电话卡帮我保管)
而在实现的过程中,我们往往需要抽象出信息的:发送方与接收方,而我认为这也就是Tcp模式下socket的主要逻辑是,接收方绑定好端口等待连接,发送方调用Connect方法,寻找服务器,实际上就是接收方在等待,而发送方发送了沟通请求罢了
f5d37138-357b-46d3-ab69-85009e38c363