Socket Server And Client:
1、socket 架构图:
2、端口的分类:
1)公认端口(well known ports):从0到1023,它们紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如:80 端口实际上总是HTTP通讯。
2)注册端口(registered ports):从1024到49151。它们松散的邦定于一些服务。也就是说有许多服务邦定于这些端口,这些端口同样用于许多其他目的。例如:许多系统处理动态端口从1024左右开始。
3)动态和/或者私有端口(Dynamic and/or private ports):从49152到65535。理论上,不应该为服务分配这些端口。实际上,机器通常从1024起分配动态端口。但也有例外的情况。
3、Socket Server:
public List<Socket> proxSockets = new List<Socket>();
public SocketServer()
{
InitializeComponent();
}
private void btn_Listen_Click(object sender, RoutedEventArgs e)
{
//1、创建服务器的socket
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//Log 创建了服务器socket对象
this.txt_Log.Text = "创建了服务器Socket\r\n" + txt_Log.Text;
//2、绑定IP和端口
IPAddress ip = IPAddress.Parse(txt_IP.Text);
IPEndPoint endPoint = new IPEndPoint(ip, Convert.ToInt32(txt_port.Text));
serverSocket.Bind(endPoint);
//3、开始侦听,这个10表示等待连接的队列大小,例如:同一时间来了100个连接,处理一个,另外99个有10个放入队列,剩下的报错
serverSocket.Listen(10);
//4、接受客户端的connect,Accept 方法会阻塞当前线程,为防止阻塞主线程,因此放到线程池执行
this.txt_Log.Text = "开始接受客户端连接\r\n" + txt_Log.Text;
ThreadPool.QueueUserWorkItem(new WaitCallback(StartAcceptClient), serverSocket);
}
private void StartAcceptClient(object state)
{
Socket serverSocket = state as Socket;
//可能有多个client 连接
while (true)
{
Socket proxSocket = serverSocket.Accept();
proxSockets.Add(proxSocket);
//并且代理socket 对象需要接受client 的Message
ThreadPool.QueueUserWorkItem(new WaitCallback(RecevieData), proxSocket);
关闭代理socket (看实际应用场景决定)
//proxSocket.Shutdown(SocketShutdown.Both);
//proxSocket.Close();
}
}
private void RecevieData(object state)
{
Socket proxSocket = state as Socket;
byte[] data = new byte[1024 * 1024];
//每一个代理都需要接受
while (true)
{
//try catch 防止客户端异常退出
try
{
int realLen = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
if (realLen == 0)
{
//对方正常退出的case,对方有shutdown
StopConnect(proxSocket);
proxSockets.Remove(proxSocket);
return;
}
}
catch (Exception ex)
{
//异常退出也要关闭
StopConnect(proxSocket);
throw ex;
}
}
}
private void StopConnect(Socket proxSocket)
{
if (proxSocket.Connected)
{
proxSocket.Shutdown(SocketShutdown.Both);
//超过100s 强制关闭
proxSocket.Close(100);
}
}
//群发到每一个client
private void btn_Send_Click(object sender, RoutedEventArgs e)
{
foreach (var item in proxSockets)
{
if (item.Connected)
{
string str = this.txt_port.Text.Trim().ToUpper();
byte[] data = Encoding.Default.GetBytes(str);
item.Send(data, 0, data.Length, SocketFlags.None);
}
}
}
4、socket Client:
public Socket ClientSocket { get; set; }
public SocketClient()
{
InitializeComponent();
}
private void btn_Connect_Click(object sender, RoutedEventArgs e)
{
//1、创建服务器的socket
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ClientSocket = clientSocket;
//2、连接到服务器
while (true)
{
try
{
clientSocket.Connect(IPAddress.Parse(txt_IP.Text), Convert.ToInt32(txt_port.Text));
break;
}
catch (Exception)
{
//Log 连接失败 并重新连接
}
}
//连接OK 那个socket 开始receive
Thread th = new Thread(new ParameterizedThreadStart(ReceiveData));
th.IsBackground = true;
th.Start(ClientSocket);
}
private void ReceiveData(object obj)
{
Socket socket = obj as Socket;
byte[] data = new byte[1024 * 1024];
//每一个代理都需要接受
while (true)
{
//try catch 防止客户端异常退出
try
{
int realLen = socket.Receive(data, 0, data.Length, SocketFlags.None);
if (realLen == 0)
{
//对方正常退出的case,对方有shutdown
StopConnect(socket);
return;
}
}
catch (Exception ex)
{
throw ex;
}
}
}
private void StopConnect(Socket socket)
{
if (socket.Connected)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close(100);
}
}
private void btn_Send_Click(object sender, RoutedEventArgs e)
{
if (ClientSocket.Connected)
{
string str = this.txt_port.Text.Trim().ToUpper();
byte[] data = Encoding.Default.GetBytes(str);
ClientSocket.Send(data, 0, data.Length, SocketFlags.None);
}
}
}
5、Socket的通信的本质&阻塞:
send方法把数据复制到发送缓冲区中,它做的事情只是这个,然后TCP协议底层就根据在TCP三步握手时确定的MSS大小(比如:1024字节)从发送缓冲区中取1024字节放到TCP数据包的数据部分,然后从发送缓冲区中删除1024个字节。并且TCP不是真正意义上的停止等待协议。也就是并不是发送一个TCP数据包就停止发送TCP数据包,直到收到接收方发送得ACK数据包才发送下一个TCP数据包。而Receive 则是从接收缓冲区中取数据。那么根据这个原理
阻塞原因
产生阻塞的原因可能是下面几个:
第一:你的发送缓冲区太小了。比如:只有1MB或者小于1MB,那么你的发送缓冲区会被迅速填满。导致阻塞
第二: 对方的网络环境很差,接收方的接收缓冲区太小了.你的发送缓冲区发送速度就很慢。发送缓冲区的数据清空速度就很慢。
第三:整个网路环境很差.数据要经过很多路由器。路由器很有自己的缓冲区,路由器的缓冲区如果被填满,那么你的发送数据就降低。你的发送缓冲区清空数据的速度就降低。连续三次发1MB的速度就迅速填满发送缓冲区。
6、Socket的可能问题:
由于第五点我们知道,socket本质是通过基础系统的空间来交换数据的,那么主要可能引发以下两点问题
1,Socket receive的实际字节数不等于预期接收的字节数
receive时,虽然我们给Buffer定义了一个长度和要接收到的size,但是Socket接收到的字节数(即返回值)并不一定等于Size
通常我们会用一个较大的size来接收,另外需要采用循环接受的办法,上面的代码
2,服务器发送多次,客户端一次接收的问题。(理论上设置的receive buffer 足够大,可以一次拿到基础系统空间的数据即所有的森send)
存在一个问题:假如客户端想一次接收服务器多次发送过来的数据,客户端定义缓存body的长度等于服务端多次发送的数据长度和,客户端在服务器发送完成后开始一次接收,客户端肯定能接收到服务器发送过来的所有数据,不会只接收服务器发送过来的部分数据。
打个比方,假如服务器分两次分别发送“Hello”,“World”。这样客户端一定能接受到“HelloWorld”。
这样有可能会导致如果需要一条send 一条receive的情况(实时聊天工具或者工业上的设备间通信),就很难处理。