C#のsocket通信
博主要做一个手机和电脑端(C#)通讯的程序,便览了网络上关乎socket的东西。但是接收文件的时候卡住了,怎么也接收不全。后来做了分片处理,如果分片,发送的时候就会有不同的socket(客户端开发不是我,故我不能控制人家怎么发),结果撞山了。
因为发送的时候for循环发,导致不是有重帧就是丢失,故进行了深入的研究。
1.Socket
Socket包括Ip地址和端口号两部分,程序通过Socket来通信,Socket相当于操作系统的一个组件。Socket作为进程之间通信机制,通常也称作”套接字”,用于描述IP地址和端口号,是一个通信链的句柄。说白了,就是两个程序通信用的。
更深刻理解引用一个同行的博文:http://blog.csdn.net/jiajia4336/article/details/8798421
生活案例对比:
Socket之间的通信可以类比生活中打电话的案例。任何用户在通话之前,首先要占有一部电话机,相当于申请一个Socket,同时要知道对方的号码,相当于对方有一个固定的Socket,然后向对方拨号呼叫,相当于发出连接请求。假如对方在场并空闲,拿起 电话话筒,双方就可以进行通话了。双方的通话过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向socket发送数据和从socket接收数据。通话结束后,一方挂起电话机,相当于关闭socket,撤销连接。
注意:Socket不仅可以在两台电脑之间通信,还可以在同一台电脑上的两个程序间通信。
2,端口进阶(深入)
端口号范围:0-65535,总共能表示65536个数。
按端口号可分为3大类
(1)公认端口(WellKnownPorts):从0到1023,它们紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如:80端口实际上总是HTTP通讯。
(2)注册端口(RegisteredPorts):从1024到49151。它们松散地绑定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同样用于许多其它目的。例如:许多系统处理动态端口从1024左右开始。
(3)动态和/或私有端口(Dynamicand/orPrivatePorts):从49152到65535。理论上,不应为服务分配这些端口。实际上,机器通常从1024起分配动态端口。
通过IP地址确定了网络中的一台电脑后,该电脑上可能提供很多提供服务的应用,每一个应用都对应一个端口。
在Internet上有很多这样的主机,这些主机一般运行了多个服务软件 ,同时提供几种服务,每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务(应用程序)
例如:http 使用80端口, ftp使用21端口 smtp使用25端口
3.Socket分类
Socket主要有两种类型:
- 流式Socket
是一种面向连接的Socket,针对于面向连接的TCP服务应用,安全,但是效率低
2,数据报式Socket
是一种无连接的Socket,对应于无连接的UDP服务应用,不安全,但效率高
4. Socket一般应用模式(服务器端和客户端)
服务器端的Socket(至少需要两个)
01.一个负责接收客户端连接请求(但不负责与客户端通信)
02.每成功接收到客户端的连接便在服务器端产生一个对应的复杂通信的Socket
021.在接收到客户端连接时创建
022. 为每个连接成功的客户端请求在服务器端都创建一个对应的Socket(负责和客户端通信)
客户端的Socket
- 必须指定要连接的服务器地址和端口
- 通过创建一个Socket对象来初始化一个到服务器端的TCP连接
通过上图,我们可以看出,首先服务器会创建一个负责监听的socket,然后客户端通过socket连接到服务器指定端口,最后服务器端负责监听的socket,监听到客户端有连接过来了,就创建一个负责和客户端通信的socket。
下面我们来看下Socket更具体的通信过程:
Socket的通讯过程
服务器端:
01,申请一个socket
02,绑定到一个IP地址和一个端口上
03,开启侦听,等待接收连接
客户端:
01,申请一个socket
02,连接服务器(指明IP地址和端口号)
服务器端接收到连接请求后,产生一个新的socket(端口大于1024)与客户端建立连接并进行通信,原监听socket继续监听。
注意:负责通信的Socket不能无限创建,创建的数量和操作系统有关。
5.Socket的构造函数
Public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolTYpe)
AddressFamily:指定Socket用来解析地址的寻址方案。例如:InterNetWork指示当Socket使用一个IP版本4地址连接
SocketType:定义要打开的Socket的类型
Socket类使用ProtocolType枚举向Windows Sockets API通知所请求的协议
注意:
1,端口号必须在 1 和 65535之间,最好在1024以后。
2,要连接的远程主机必须正在监听指定端口,也就是说你无法随意连接远程主机。
如:
IPAddress addr = IPAddress.Parse("127.0.0.1");
IPEndPoint endp = new IPEndPoint(addr,,9000);
服务端先绑定:serverWelcomeSocket.Bind(endp)
客户端再连接:clientSocket.Connect(endp)
3,一个Socket一次只能连接一台主机
4,Socket关闭后无法再次使用
5,每个Socket对象只能与一台远程主机连接。如果你想连接到多台远程主机,你必须创建多个Socket对象。
6.Socket常用类和方法
相关类:
IPAddress:包含了一个IP地址
IPEndPoint:包含了一对IP地址和端口号
方法:
Socket():创建一个Socket
Bind():绑定一个本地的IP和端口号(IPEndPoint)
Listen():让Socket侦听传入的连接吃那个病,并指定侦听队列容量
Connect():初始化与另一个Socket的连接
Accept():接收连接并返回一个新的Socket
Send():输出数据到Socket
Receive():从Socket中读取数据
Close():关闭Socket,销毁连接
7、socket接收
博主就是因为网上的一些不良代码和思路被坑了不少时间,故此特意说明。
socket的接收缓冲区
我们都知道socket的接收方法使receive,receive是从socket缓冲区中读取,socket缓冲区是一个动态的东西,你读取一个,就相当于处理一个,就减少一个。
此缓冲区的意义:当前接收到的数据,意思是截止到你读取的时候里面的数据,这里面的数据是可以随着时间增加的。
我们可以理解为一个队列,发送方往队列中添加数据,receive取数据。
socket接收数据
windows的此缓冲区限制了大小为8k,故最多我们可以接受8k的数据,如果我们处理的过慢,又超过了发送方的超时时间,发送方就会显示超时。故同步处理就需要我们及时读取。否则,请异步处理,或者另起线程处理。
当然,如果我们双方约定了消息的大小,比如1k,那么我们接受的时候就可以每次读取1024个byte。
重点是,如果我们要读取一个文件,发送方使用一个socket的一次send,那么我们只能一次读取一个字节(约定为偶数的可以读取2个,约定4个或者8个的倍数的,可以读取4个或者8个),缓冲区的数据是按顺序到达的。
读取后,我们在重新拼装成消息,然后解析文件字段,还原为文件。
另:如果文件过大,切片后,不停的new socket发送是不可取的,可以尝试同一个socket发送(博主未验证)
上一段我接收的代码:
private void ServerStart() { //创建IPEndPoint实例 IPEndPoint ipep = new IPEndPoint(IPAddress.Any, Port); //创建socket套接字 serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //将所创建的套接字与IPEndPoint绑定 serverSocket.Bind(ipep); //设置socket为监听Listen模式 serverSocket.Listen(9999); while (true) { try { //在套接字上接收接入的连接 Socket acceptSocket = serverSocket.Accept(); if (acceptSocket != null) { Thread socketConnectedThread = new Thread(ReceiveData); socketConnectedThread.IsBackground = true; socketConnectedThread.Start(acceptSocket); //ThreadPool.SetMinThreads(100, 100); //ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveData), acceptSocket); Dispatcher.BeginInvoke((Action)(()=> { richTextBox.AppendText("\n有客户端接入..."); })); } } catch (Exception ex) { MessageBox.Show("listening Error: " + ex.Message); } } } private void ReceiveData(object obj) { Socket s = (Socket)obj; //根据收听到的客户端套接字向客户端发送信息 IPEndPoint clientep = (IPEndPoint)s.RemoteEndPoint; try { List<byte> list = new List<byte>(); byte[] buffer = new byte[1]; int readBytes = 0; do { readBytes = s.Receive(buffer, 0, buffer.Length,SocketFlags.None); list.AddRange(buffer); } while (s.Available!=0); byte[] buffers = list.ToArray(); Dispatcher.BeginInvoke((Action)(() => { richTextBox.AppendText("\nIP地址:" + clientep.Address + " 端口号:" + clientep.Port); })); byte[] messageBuffer = new byte[list.Count-8]; messageBuffer = buffers.Skip(8).Take(list.Count - 8).ToArray(); Message receive = MessageHelp.DeSerialize(messageBuffer); if (receive != null) { switch (receive.messageType) { case "link": ClientIp = receive.sourceIp; ClientPort = Convert.ToInt32(receive.sourcePort); //接到后发送ok byte[] sendData = MessageHelp.Serialize(new Message { messageType = "link", sourceIp = LocalIpAddress, sourcePort = Port.ToString(), content = "ok" }); //s.Send(sendData, sendData.Length, SocketFlags.None); Socket toClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); toClient.Connect(new IPEndPoint(IPAddress.Parse(ClientIp), ClientPort));//与该ip地址进行连接 toClient.Send(sendData, sendData.Length, SocketFlags.None); break; case "command": byte[] responseData = MessageHelp.Serialize(new Message { messageType = "command", sourceIp = LocalIpAddress, sourcePort = Port.ToString(), content = receive.content}); Socket reClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); reClient.Connect(new IPEndPoint(IPAddress.Parse(ClientIp), ClientPort));//与该ip地址进行连接 reClient.Send(responseData, responseData.Length, SocketFlags.None); Dispatcher.BeginInvoke((Action)(() => { richTextBox.AppendText("\n" + receive.content); })); break; case "file": FileName = receive.fileName; byte[] csharpFileByte = new byte[receive.fileTotalLength]; for (int i = 0; i < receive.bytes.Length; i++) { csharpFileByte[i] = Convert.ToByte(receive.bytes[i] & 0xff); } System.IO.File.WriteAllBytes(FileName, csharpFileByte); MessageBox.Show("文件接收成功"); //接到后发送ok byte[] responseFile = MessageHelp.Serialize(new Message { messageType = "file", sourceIp = LocalIpAddress, sourcePort = Port.ToString(), content = receive.content }); //s.Send(sendData, sendData.Length, SocketFlags.None); Socket fClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); fClient.Connect(new IPEndPoint(IPAddress.Parse(ClientIp), ClientPort));//与该ip地址进行连接 fClient.Send(responseFile, responseFile.Length, SocketFlags.None); break; } } } catch (Exception ex) { s.Close(); } s.Close(); s.Dispose(); }