Sokect
在计算机通信领域,Socket被翻译为“套接字”,他是计算机之间进行通信的一种约定或者是一种方式。通过Socket这种约定,一台计算机可以接受到其他计算机的数据,也可以向其他计算机发送数据。
TCP和UDP
TCP是面向连接的,保证可靠的数据传输,每次建立连接都需要经历三次握手,数据传输完成都需要经历4次挥手断开连接,由于TCP是面向连接的所以只能用于端到端的通信。
UDP是面向无连接的通讯协议,每次发送数据不需要建立连接,因此可以用于广播发送并不局限于端到端。
TCP和UDP区别
UDP:
1.面向无连接,将数据及源封装在数据包中,不需要建立连接
2.每个数据报的大小限制在64K内
3.因为无连接 是不可靠协议
4.不需要建立连接,速度快
TCP:
1.建立连接,形成传输数据的通道
2.在连接中进行大数据量传输,以字节流的形式
3.通过三次握手(四次挥手)完成连接,是可靠协议
4.必须建立连接,效率会稍低
TCP三次握手四次挥手
一般需要了解一下几个字段:
序号:Seq序号,占32位,用来表示从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标识
确认序号:ACK序号,占32位,只有ACK标志位为1时,确认序号字段才有效,ACK=Seq+1
标志位共六个:URG、ACK、PSH、RST、SYN、FIN含义:
URG:紧急指针
ACK:确认序号有效
PSH:接收方应该尽快将这个报文交给应用层
RST:充值连接
SYN:发起一个新连接
FIN:释放一个连接
第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SEND状态,等待Server确认。
第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ACK=J+1,随机产生一个seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RECV状态。
第三次握手:Client收到确认后,检查ACK是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ACK=K+1,并将数据包发送给Server,Server检查ACK是否为K+1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间就可以开始传输数据了。
由于TCP连接是全双工的,因此每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传输,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传输,Server进入LAST_ACK状态
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手
服务端
using System;
using System.Net;
using System.Net.Sockets;
class namespace Server
{
static void Main(string[] args)
{
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 88);
//绑定ip和端口号
serverSocket.Bind(ipEndPoint);
serverSocket.Listen(0);//开始监听端口号
Console.WriteLine("Server Start");
Socket clientSocket = serverSocket.Accept();//接收客户端连接
//向客户端发送消息
string msg = " 你好 Client!";
byte[] data = System.Text.Encoding.UTF8.GetBytes(msg);
clientSocket.Send(data);
//接收客户端消息
byte[] dataBuffer = new byte[1024];
int count = clientSocket.Receive(dataBuffer);
string msgReceive = System.Text.Encoding.UTF8.GetString(dataBuffer, 0, count);
Console.Write(msgReceive);
Console.ReadLine();
clientSocket.Close();
serverSocket.Close();
}
}
客户端
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class Client
{
static void Main(string[] args)
{
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 88));
//接收消息
byte[] data = new byte[1024];
int count = clientSocket.Receive(data);//接收到数据才继续执行
string msg = Encoding.UTF8.GetString(data, 0, count);
Console.WriteLine(msg);
//发送消息
string s = Console.ReadLine();
Console.Write(s);
clientSocket.Send(Encoding.UTF8.GetBytes(s));
Console.ReadLine();
clientSocket.Close();
}
}
以上是同步接收数据,只能接收一个客户端的消息
异步接收
服务器端:
class Server
{
static void Main(string[] args)
{
StartServerAsync();
Console.ReadLine();
}
public static void StartServerAsync() {
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 88);
//绑定ip和端口号
serverSocket.Bind(ipEndPoint);
serverSocket.Listen(0);//开始监听端口号
Console.WriteLine("Server Start");
//Socket clientSocket = serverSocket.Accept();
//异步接收客户端连接
serverSocket.BeginAccept(AcceptCallBack, serverSocket);
}
public static void AcceptCallBack(IAsyncResult ar) {
Socket serverSocket = ar.AsyncState as Socket;
Socket clientSocket = serverSocket.EndAccept(ar);
//向客户端发送消息
string msg = " 你好 Client!";
byte[] data = System.Text.Encoding.UTF8.GetBytes(msg);
clientSocket.Send(data);
//接收客户端消息
clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceiveCallBack, clientSocket);//异步接收数据
serverSocket.BeginAccept(AcceptCallBack, serverSocket);
}
public static byte[] dataBuffer = new byte[1024];
public static void ReceiveCallBack(IAsyncResult ar) {
Socket clientSocket = null;
try
{
clientSocket = ar.AsyncState as Socket;
int count = clientSocket.EndReceive(ar);
if (count == 0) {
clientSocket.Close();
return;
}
string msg = Encoding.UTF8.GetString(dataBuffer, 0, count);
Console.WriteLine("从客户端接收数据:" + msg);
clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceiveCallBack, clientSocket);//异步接收数据
}
catch (Exception e)
{
Console.WriteLine(e);
if (clientSocket != null)
{
clientSocket.Close();
}
}
}
}
客户端:
class Client
{
static void Main(string[] args)
{
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 88));
//接收消息
byte[] data = new byte[1024];
int count = clientSocket.Receive(data);//接收到数据才继续执行
string msg = Encoding.UTF8.GetString(data, 0, count);
Console.WriteLine(msg);
//发送消息
while (true)
{
string s = Console.ReadLine();
if (s == "c") {
clientSocket.Close(); return;
}
clientSocket.Send(Encoding.UTF8.GetBytes(s));
}
}
}
粘包和分包
粘包产生原因:
(1)发送端原因:由于TCP本身的优化机制,如果发送的网络数据很多,数据包太小,那么会启动Nagle算法(可配置是否启用)对较小的数据包进行合并再发送。服务器在接收到消息(数据流)的时候就无法区分哪些数据包是客户端自己分开发送的,这样产生了粘包。
(2)接收端原因:服务器在接收到数据库后,放到缓冲区中,如果消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的情况,造成粘包现象。
分包产生的原因:在取数据的时候,只取到了一部分(与接收的缓冲区大小有关系),一个数据包被分成了多次接收。
解决办法:客户端发送数据时,将数据长度也告诉服务端,这样服务端就知道需要解析多长的数据,从而避免粘包。
客户端代码:
客户端处理比较简单,只需要在数据的头部加上该数据长度,以便服务端解析。新建一个类来处理。
public class Message
{
public static byte[] GetBytes(string data) {
byte[] dataBytes = Encoding.UTF8.GetBytes(data);//将字符串装换成字符数组
int datalength = dataBytes.Length;//获取长度
byte[] lengthBytes = BitConverter.GetBytes(datalength);//将长度转换成字节数组
byte[] newBytes = lengthBytes.Concat(dataBytes).ToArray();//连接字节数组
return newBytes;
}
}
将要发送的数据经过这个方法处理后,数据头部就有四个字节来表示数据长度。
服务端代码:
服务端接收到数据后,首先需要解析出头部四个字节,得到数据长度。然后根据长度解析出相应数据。也新建一个类来处理。
class Message
{
private byte[] data = new byte[1024];
private int startIndex = 0;//数据标志位
public void AddCount(int count)
{
startIndex += count;
}
public byte[] Data {
get {
return data; }
}
public int StartIndex {
get {
return startIndex; }
}
//剩余长度
public int RemainSize {
get {
return data.Length - startIndex; }
}
//读取数据
public void ReadMessage() {
while (true) {
if (startIndex <= 4) return;
//获取数据长度
int count = BitConverter.ToInt32(data, 0);//将前面四个字节转换成int
Console.WriteLine("count :" + count);
//如果有完整数据 则解析and重新给data赋值
if ((startIndex - 4) >= count)
{
string s = Encoding.UTF8.GetString(data, 4, count);
Console.WriteLine("解析出来一条数据:" + s);
Array.Copy(data, count + 4, data, 0, startIndex - 4 - count);
startIndex -= (count + 4);
}
else {
break;
}
}
}
}