Socket通信的基本流程具体步骤如下所示
1.开启一个链接之前,需要先完成Socket和Bind两个步骤。Socket是新建一个套接字,Bind指定套接字的IP和端口(客户端在调用Connect时会由系统分配端口,因此可以省去Bind)。
2.服务端通过Listen开启监听,等待客户端接入。
3.客户端通过Connect连接服务器, 服务端通过Accept接收客户端连接。 在connect-accept过程中, 操作系统将会进行三次握手
4.客户端和服务端通过write和read发送和接收数据, 操作系统将会完成TCP数据的确认、 重发等步骤。
5.通过close关闭连接, 操作系统会进行四次挥手。
异步原理
套接字编程原理:延续文件作用思想,打开-读写-关闭的模式。
C/S编程模式如下:
Ø 服务器端:
打开通信通道,告诉本地机器,愿意在该通道上接受客户请求——监听,等待客户请求——接受请求,创建专用链接进行读写——处理完毕,关闭专用链接——关闭通信通道(当然其中监听到关闭专用链接可以重复循环)
Ø 客户端:打开通信通道,连接服务器——数据交互——关闭信道。
Socket通信方式:
Ø 同步:客户端在发送请求之后必须等到服务器回应之后才可以发送下一条请求。串行运行
Ø 异步:客户端请求之后,不必等到服务器回应之后就可以发送下一条请求。并行运行
套接字模式:
Ø 阻塞:执行此套接字调用时,所有调用函数只有在得到返回结果之后才会返回。在调用结果返回之前,当前进程会被挂起。即此套接字一直被阻塞在网络调用上。
Ø 非阻塞:执行此套接字调用时,调用函数即使得不到得到返回结果也会返回。
套接字工作步骤:
Ø 服务器监听:监听时服务器端套接字并不定位具体客户端套接字,而是处于等待链接的状态,实时监控网络状态
Ø 客户端链接:客户端发出链接请求,要连接的目标是服务器端的套接字。为此客户端套接字必须描述服务器端套接字的服务器地址与端口号。
Ø 链接确认:是指服务器端套接字监听到客户端套接字的链接请求时,它响应客户端链接请求,建立一个新的线程,把服务器端套接字的描述发送给客户端,一旦客户端确认此描述,则链接建立好。而服务器端的套接字继续处于监听状态,继续接受其他客户端套接字请求。
异步通信例子(部分代码)
static void Main(string[] args)
{
//创建多个链接池,表示创建maxConn最大客户端
conns = new Conn[maxConn];
for (int i = 0; i < maxConn; i++)
{
conns[i] = new Conn();
}
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000);
serverSocket.Bind(ipEndPoint);//绑定IP和端口号
serverSocket.Listen(maxConn);//开始监听端口,0为监听无限个客户端
Console.WriteLine("[服务器]启动成功");
//开始调用异步连接
serverSocket.BeginAccept(AcceptCb, null);
//按下quit退出程序
while (true)
{
if (Console.ReadLine() == "quit") return;
}
}
/// <summary>
/// Accept回调
/// </summary>
/// <param name="ar"></param>
static void AcceptCb(IAsyncResult ar)
{
try
{
Socket socket = serverSocket.EndAccept(ar);//尝试进行异步连接
int index = NewIndex();
if (index < 0)
{
socket.Close();
Console.Write("[警告]链接已满");
}
else
{
Conn conn = conns[index];
conn.Init(socket);
string adr = conn.GetAdress();
Console.WriteLine("客户端连接 [" + adr + "] conn池ID:" + index);
conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReceiveCb, conn);
}
serverSocket.BeginAccept(AcceptCb, null);
}
catch (Exception e)
{
Console.WriteLine("AcceptCb失败:" + e.Message);
}
}
static void ReceiveCb(IAsyncResult ar)
{
Conn conn = (Conn)ar.AsyncState;
try
{
int count = conn.socket.EndReceive(ar);
//关闭信号
if (count <= 0)
{
Console.WriteLine("收到 [" + conn.GetAdress() + "] 断开链接");
conn.Close();
return;
}
//数据处理
string str = Encoding.UTF8.GetString(conn.readBuff, 0, count);
Console.WriteLine("收到 [" + conn.GetAdress() + "] 数据:" + str);
str = conn.GetAdress() + "发送的:" + str;
byte[] bytes = System.Text.Encoding.UTF8.GetBytes("接收到" + str);
//广播
/*
for (int i = 0; i < conns.Length; i++)
{
if (conns[i] == null)
continue;
if (!conns[i].isUse)
continue;
Console.WriteLine("将消息转播给 " + conns[i].GetAdress());
conns[i].socket.Send(bytes);
}*/
//点播
for (int i = 0; i<=0;i++)
{
if (conns[i] == null)
continue;
if (!conns[i].isUse)
continue;
Console.WriteLine("将消息转播给 " + conns[i].GetAdress());
conns[i].socket.Send(bytes);
}
//继续接收
conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(),SocketFlags.None, ReceiveCb, conn);
}
catch (Exception e)
{
Console.WriteLine("收到 [" + conn.GetAdress() + "] 断开链接");
conn.Close();
}
}