- select 服务端
服 务 端 调 用 S e l e c t , 等 待 可 读 取 的 S o c k e t , 流 程 如 下。
初始化 listenfd
初 始化 clients列 表
while (true) {
checktist = 待 检 测 s o c k e t 列 表
Select (checkList ...)
f o r (遍 历可 读 c h e c k L i s t 列 表 ){
i f ( l i s t e n f d 可 读 ) Ac c e p t ;
i f ( 这个客户端可读 ) 消息处理;
}
}
服务端使用主循环结构while(true){…}子,不断地调用Select 检测Socket状态,其步骤 如 下:
- 将监听Socket( listenfd)和客户端Socket(遍历clients 列表) 添加到 待检测Socket 可读状态的列表checkList 中。
- 调用Select,程序中设置超时时间为1秒,若1秒内没有任何可读信息,Select 方法 将checkList 列表变成空列表,然后返回。
- 对Select处理后的每个Socket做处理,如果监听Socket( listenfd)可读,说明有客 户 端 连 接 , 需 调 用 A c c e p t 。 如 果 客 户 端 Socket
可 读, 说明客户端发送了消息 (或 关 闭 ), 将消息广播 给所 有客 户 端。
上述过 程的 示 例代码 如 下:
using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Net.WebSockets;
namespace EchoServer
{
class ClientState
{
public Socket socket;
public byte[] readBuff = new byte[1024];
}
class MainClass
{
// 监听Socket
static Socket listenfd;
// 客户端Socket及状态信息
static Dictionary<Socket, ClientState> clients =
new Dictionary<Socket, ClientState>();
public static void Main (string[] args)
{
Console.WriteLine ("Hello World!");
//Socket
listenfd = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
// Bind
IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
listenfd.Bind(ipEp);
//Listen
listenfd.Listen(0);
Console.WriteLine("[服务器]启动成功");
// 方案四: select方案
List<Socket> checkRead = new List<Socket>();
//主循环
while(true) {
// 填充checkRead列表
checkRead.Clear();
checkRead.Add(listenfd);
foreach(ClientState s in clients.Values) {
checkRead.Add(s.socket);
}
//select
Socket.Select(checkRead, null, null, 1000);
//检查可读对象
foreach(Socket s in checkRead) {
if (s == listenfd) {
ReadListendfd(s);
} else {
ReadClientfd(s);
}
}
}
}
//读取Clientfd
public static bool ReadClientfd(Socket clientfd) {
ClientState state = clients[clientfd];
int count = clientfd.Receive(state.readBuff);
//客户端关闭
if(count == 0){
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Socket Close");
return false;
}
//广播
string recvStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);
Console.WriteLine("Receive " + recvStr);
string sendStr = clientfd.RemoteEndPoint.ToString() + ":" + recvStr;
byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
foreach (ClientState cs in clients.Values){
cs.socket.Send(sendBytes);
}
return true;
}
//读取Listenfd
public static void ReadListendfd(Socket listenfd) {
Console.WriteLine("Accept");
Socket clientfd = listenfd.Accept();
ClientState state = new ClientState();
state.socket = clientfd;
clients.Add(clientfd, state);
}
}
}
- select 客户端
使用Select 方法的客户端和使用Poll 方法的客户端极其相似,因为只需检测一个 Socket 的状态,将连接服务端的socket输人到checkRead列表即可。为了不卡住客户端, Select的超时时间设置为 0 , 永不阻塞。 示例代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using UnityEngine.UI;
using System.Threading;
using System;
public class Echo : MonoBehaviour
{
//定义套接字
Socket socket;
//UGUI
public InputField InputField;
public Text text;
//接收缓冲区
byte[] readBuff = new byte[1024];
string recvStr = "";
//checkRead列表
List<Socket> checkRead = new List<Socket>();
//点击连接按钮
public void Connection()
{
//Socket
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//Connect 这个是同步方法
socket.Connect("127.0.0.1", 8888);
//socket.BeginConnect("127.0.0.1", 8888, ConnectionCallback, socket);
}
//点击发送按钮
public void Send()
{
//Send
string sendStr = InputField.text;
byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
//改用异步发送
socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
}
//Send回调
public void SendCallback(IAsyncResult ar) {
try {
Debug.Log("异步发送测试");
Socket socket = (Socket) ar.AsyncState;
int count = socket.EndSend(ar);
Debug.Log("Socket Send succ" + count);
} catch (SocketException ex) {
Debug.Log("Socket Send fail " + ex.ToString());
}
}
public void Update() {
if(socket == null) {
return;
}
//填充checkRead列表
checkRead.Clear();
checkRead.Add(socket);
//select
Socket.Select(checkRead, null, null, 0);
//check
foreach(Socket s in checkRead) {
byte[] readBuff = new byte[1024];
int count = socket.Receive(readBuff);
string recvStr = System.Text.Encoding.Default.GetString(readBuff,0, count);
text.text = recvStr;
}
}
}
由于程 序 在Update 中 不停 地 检 测 数 据, 性 能 较 差。 商 业 上 为 了做 到 性 能 上的 极 致, 大 多使用异步( 或使用多线程模拟异步程序)。 本书将 会使用异步客户端、Select 服务端演示 程 序。