转载引用 TCP/IP异步模型
Server
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace AsyncServer
{
public class AsyncTCPServer
{
public void Start()
{
//创建套接字
IPEndPoint ipe = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 6065);
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定端口和IP
socket.Bind(ipe);
//设置监听
socket.Listen(10);
//连接客户端
AsyncAccept(socket);
}
/// <summary>
/// 连接到客户端
/// </summary>
/// <param name="socket"></param>
private void AsyncAccept(Socket socket)
{
socket.BeginAccept(asyncResult =>
{
//获取客户端套接字
Socket client = socket.EndAccept(asyncResult);
Console.WriteLine(string.Format("客户端{0}请求连接...", client.RemoteEndPoint));
AsyncSend(client, "服务器收到连接请求");
AsyncSend(client, string.Format("欢迎你{0}",client.RemoteEndPoint));
AsyncReveive(client);
}, null);
}
/// <summary>
/// 接收消息
/// </summary>
/// <param name="client"></param>
private void AsyncReveive(Socket socket)
{
byte[] data = new byte[1024];
try
{
//开始接收消息
socket.BeginReceive(data, 0, data.Length, SocketFlags.None,
asyncResult =>
{
int length = socket.EndReceive(asyncResult);
Console.WriteLine(string.Format("客户端发送消息:{0}", Encoding.UTF8.GetString(data)));
}, null);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="client"></param>
/// <param name="p"></param>
private void AsyncSend(Socket client, string p)
{
if (client == null || p == string.Empty) return;
//数据转码
byte[] data = new byte[1024];
data = Encoding.UTF8.GetBytes(p);
try
{
//开始发送消息
client.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult =>
{
//完成消息发送
int length = client.EndSend(asyncResult);
//输出消息
Console.WriteLine(string.Format("服务器发出消息:{0}", p));
}, null);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
服务端
基本流程
创建套接字
绑定套接字的IP和端口号——Bind()/Connect()
以下引自个人的git库 Notes : share云笔记
bind的参数是自己的地址,而connect的参数是对方的地址
127.0.0.1和0.0.0.0的区别
一般我们用来做c/s测试通常是在本地的环回口(127.0.0.1),研究了四天,发现这个地址完全是个鸡肋,环回口的定义是,从本地数据接口发出的数据,由数据接口接受,这个接口的地址是固定不变的(收发地址都是127.0.0.1),打个比方,我们按照OSI七层网络模型举例,当一个数据包发出后,按理讲应该从物理层进入,达到最顶层的应用层再返回,但环回口仅到ip层就被接受了,也就是说我们发出的数据包不知道发给哪一个主机
拟定PC1地址为192.168.0.1 pc2为127.0.0.1
pc2按照环回地址发出数据包,但由于到ip层就被接受了,这意味着他并没有在应用层查看到其他主机的路由地址,那就不知道发给PC1,还是PC3,PC4
0.0.0.0(接受所有主机发来的数据包),这意味着服务端只要有一个程序调用listen方法监听一个特定端口,从其他网段发来的数据包就能找到这台主机
本人所用服务器主机版本为centos7,绑定0.0.0.0:9095(9095为socket异步发送数据端口),查询 netstat -ano 即可勘查绑定的所有端口
服务端TCP连接步骤:
使套接字处于监听状态等待客户端的连接请求——Listen()
当请求到来后,使用BeginAccept()和EndAccept()方法接受请求,返回新的套接字
使用BeginSend()/EndSend和BeginReceive()/EndReceive()两组方法与客户端进行收发通信
返回,再次等待新的连接请求
关闭套接字
socket异步编程之传输堵塞
从tcp的质询协议来看,建立连接需要三次握手,第一次由客户端发出begin请求,绑定套接字之后对服务器调用connect方法,将要传输的字节流发送至服务端,服务端经过bind方法在防火墙上打开响应的接受端口之后,listen等待来自客户端的请求,若套接字中的ip能够到达服务端但我防火墙 则调用accept方法进行接受
第二次握手,服务端监听到客户端调用send发来的字节数组,立即在本地查询路由地址,确认发来数据包的ip,从定义的文件路径回调文本数据,客户端accept到数据后,切换为receive方法,准备对下一次连接请求进行数据回调,
第三次握手,这次握手比较难,大二上学期想了足足两周才明白。第二次的传输过程中,由于我们尽可能排除数据包超时重传的情况,以防数据包因为双方主机的突发情况(即突然断电或者关机)而丢包出现网络延迟。
Discuss :
- 客户端对服务端建立连接
- 服务端是客户端建立连接请求,第二次需要经过第一次的成功连接才能开始本次连接
- 就是保证第二次的时候,我们能够确认数据包全部传递,且未发生丢包,否则重传。在这过程中,需要在服务端开一个线程监听本次传输内receive方法需要接受的数据包长度。
Tips
从路由基础中我们可以得知,在主机A到达主机B的过程中,需要经历几百跳以及几千跳的中继路由,有时候主机A可能会与同一网段内主机发来的数据包发生拥塞,造成网络数据包丢失的情况,在进程管理中这是无法避免的,称之为进程并发性。
不妨试想一下以下代码的执行结果
byte[] bytes = new Byte[1024];
IPAddress ipAddress = IPAddress.Parse("119.29.88.13");
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);
// 生成一个TCP的socket
Socket listener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
listener.Bind(localEndPoint);
listener.Listen(100);
while (true)
{
// Set the event to nonsignaled state.
allDone.Reset();
//开启异步监听socket
Console.WriteLine("Waiting for a connection");
listener.BeginAccept(
new AsyncCallback(AcceptCallback),
listener);
// 让程序等待,直到连接任务完成。在AcceptCallback里的适当位置放置allDone.Set()语句.
allDone.WaitOne();
}
Console.WriteLine("\nPress ENTER to continue");
Console.Read();
连接行为回调函数AcceptCallback:
public static void AcceptCallback(IAsyncResult ar)
{
//添加此命令,让主线程继续.
allDone.Set();
// 获取客户请求的socket
Socket listener = (Socket)ar.AsyncState;
Socket handler = listener.EndAccept(ar);
// 造一个容器,并用于接收命令.
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
}
注意到allDone方法了么,这是一个每当服务端调用accept方法的时候,就会更改连接的事务状态,将receive方法通过一个线程唤醒,与同步的不同就在于不用每时每刻监听端口立即回调方法,直到端口有来自其他主机发来数据的时候,才更改receive的事务状态,回调数据,大大避免了listen与receive在同一个时间内的事务并发。
以上是异步处理事务并发的方式,
客户端
基本流程
创建套接字并保证与服务器的端口一致
使用BeginConnect()和EndConnect()这组方法向服务端发送连接请求
使用BeginSend()/EndSend和BeginReceive()/EndReceive()两组方法与服务端进行收发通信
关闭套接字
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace AsyncClient
{
public class AsyncTCPClient
{
/// <summary>
/// 连接到服务器
/// </summary>
public void AsynConnect()
{
//端口及IP
IPEndPoint ipe = new IPEndPoint(IPAddress.Parse("119.29.88.13"), 9095);
//创建套接字
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//开始连接到服务器
client.BeginConnect(ipe, asyncResult =>
{
client.EndConnect(asyncResult);
//向服务器发送消息
AsynSend(client,"你好我是客户端");
AsynSend(client, "第一条消息");
AsynSend(client, "第二条消息");
//接受消息
AsynRecive(client);
}, null);
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="socket"></param>
/// <param name="message"></param>
public void AsynSend(Socket socket, string message)
{
if (socket == null || message == string.Empty) return;
//编码
byte[] data = Encoding.UTF8.GetBytes(message);
try
{
socket.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult =>
{
//完成发送消息
int length = socket.EndSend(asyncResult);
Console.WriteLine(string.Format("客户端发送消息:{0}", message));
}, null);
}
catch (Exception ex)
{
Console.WriteLine("异常信息:{0}", ex.Message);
}
}
/// <summary>
/// 接收消息
/// </summary>
/// <param name="socket"></param>
public void AsynRecive(Socket socket)
{
byte[] data = new byte[1024];
try
{
//开始接收数据
socket.BeginReceive(data, 0, data.Length, SocketFlags.None,
asyncResult =>
{
int length = socket.EndReceive(asyncResult);
Console.WriteLine(string.Format("收到服务器消息:{0}", Encoding.UTF8.GetString(data)));
AsynRecive(socket);
}, null);
}
catch (Exception ex)
{
Console.WriteLine("异常信息:", ex.Message);
}
}
}
}