Socket通信
今天没事,自己做了一个非常非常简易的socket测试demo,我们就来最通俗的聊天室好了。由于是最简化版的,我放弃了传输协议,只是自己定义一个最简单的结构体,也放弃了其他好的东西,只有socket使用!
首先是server代码,我使用的是Winform,好歹有个界面不是:
网络,模块与辅助工具:
/**
* Author: Garsonil
* Des:
* Time:
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Timers;
namespace NetModule
{
/// <summary>
/// 服务器
/// </summary>
public class Server : IDisposable
{
private Socket socket;
private Dictionary<int, SocketUtil> clientSockets;
private int clientId;
public Server(string ip, int port)
{
Start(ip, port);
clientSockets = new Dictionary<int, SocketUtil>();
clientId = 1;
}
/// <summary>
/// 启动服务器
/// </summary>
/// <param name="ip"></param>
/// <param name="port"></param>
public void Start(string ip, int port)
{
IPAddress ipAddress = IPAddress.Parse(ip);
IPEndPoint point = new IPEndPoint(ipAddress, port);
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(point);//绑定ip下的一个固定端口作为接受端口
socket.Listen(100);//监听接受
socket.BeginAccept(AcceptCallback, null);//开启异步接收
}
/// <summary>
/// 客户端异步连接回调
/// </summary>
/// <param name="ar"></param>
private void AcceptCallback(IAsyncResult ar)
{
Socket client = socket.EndAccept(ar);
SocketUtil socketUtil = new SocketUtil(clientId, client, this);
socketUtil.StartReceive();
//发送消息,主要目的通知客户端ID
Message message = MessageUtil.Set(0, "Server", clientId, "", "Hi there, I accept you request at " + DateTime.Now.ToString());
socketUtil.Send(MessageUtil.MessageToByte(message));
clientSockets.Add(clientId++, socketUtil);
socket.BeginAccept(AcceptCallback, null);//再次开启异步接收
}
/// <summary>
/// 释放客户端
/// </summary>
/// <param name="id"></param>
public void DisposeSocket(int id)
{
if (clientSockets.ContainsKey(id))
{
clientSockets.Remove(id);
}
}
public void Dispose()
{
if(socket != null)
socket.Close();
}
}
/// <summary>
/// 消息缓存,简陋版
/// </summary>
public class MessageQueue
{
static List<Message> messageQueue;
private static int startIndex;
static MessageQueue()
{
messageQueue = new List<Message>();
startIndex = 0;
}
/// <summary>
/// 把消息放入缓存
/// </summary>
/// <param name="msg"></param>
public static void EnQueue(Message msg)
{
messageQueue.Add(msg);
}
/// <summary>
/// 获取未读的最上一条消息
/// </summary>
/// <returns></returns>
public static Message GetQueue()
{
if (messageQueue.Count >= startIndex +1)
return messageQueue[startIndex ++];
return new Message();
}
/// <summary>
/// 标志是否有新消息,在update中复用
/// </summary>
/// <returns></returns>
public static bool hasNew()
{
return messageQueue.Count > startIndex;
}
}
/// <summary>
/// 客户端连接socket管理
/// </summary>
public class SocketUtil
{
private int id;
private string name;
private Socket socket;
private Server server;
private byte[] buffer;
public SocketUtil(int id, Socket socket, Server server)
{
this.id = id;
this.socket = socket;
this.server = server;
buffer = new byte[1024];
ListenConnect();
}
/// <summary>
/// 开始接收消息
/// </summary>
public void StartReceive()
{
socket.BeginReceive(buffer, 0, 1024, SocketFlags.None, ReceiveCallback, null);
}
/// <summary>
/// 异步接收转换与缓存
/// </summary>
/// <param name="ar"></param>
private void ReceiveCallback(IAsyncResult ar)
{
int length = 0;
try
{
length = socket.EndReceive(ar);
}
catch (Exception e)
{
Console.WriteLine("Receive Error : " + e.ToString());
}
byte[] bytes = new byte[length];
Buffer.BlockCopy(buffer, 0, bytes, 0, length);
Message msg = MessageUtil.ByteToMessage(bytes);
if (msg.rcvId == 0)
name = msg.sendName.ToString();
MessageQueue.EnQueue(msg);
StartReceive();//复接
}
/// <summary>
/// 发送,用于服务器广播或 P2P连接
/// </summary>
/// <param name="bytes"></param>
public void Send(byte[] bytes)
{
socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallback, null);
}
private void SendCallback(IAsyncResult ar)
{
socket.EndSend(ar);
}
/// <summary>
/// 监听是否掉线,掉线删除,监听频率为0.1秒
/// </summary>
private void ListenConnect()
{
Timer timer = new Timer()
{
Interval = 0.1d,
AutoReset = true,
};
timer.Elapsed += (sender, args) =>
{
if (!socket.Connected)
{
server.DisposeSocket(id);
timer.Stop();
}
};
timer.Start();
}
}
/*消息结构体*/
[StructLayout(LayoutKind.Sequential)]
public struct Message
{
public int sendId;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] sendName;
public int rcvId;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] rcvName;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
public char[] msg;
public long time;
}
/// <summary>
/// 消息辅助类
/// </summary>
public class MessageUtil
{
/// <summary>
/// 快速生成结构体,可直接挪到结构体中
/// </summary>
/// <param name="sid">send id</param>
/// <param name="sname">send name</param>
/// <param name="rid">receive id</param>
/// <param name="rname">receive name</param>
/// <param name="msg"></param>
/// <returns></returns>
public static Message Set(int sid, string sname, int rid, string rname, string msg)
{
Message message;
message.sendId = sid;
message.sendName = new char[32];
CpyString(ref message.sendName, 32, sname);
message.rcvId = rid;
message.rcvName = new char[32];
CpyString(ref message.rcvName, 32, rname);
message.msg = new char[512];
CpyString(ref message.msg, 512, msg);
message.time = DateTime.Now.ToBinary();
return message;
}
/// <summary>
/// 快速将string转为char并储存
/// </summary>
/// <param name="chars"></param>
/// <param name="size">chars的长度</param>
/// <param name="str"></param>
static void CpyString(ref char[] chars, int size, string str)
{
if (string.IsNullOrEmpty(str)) return;
char[] cs = str.ToCharArray();
Array.Copy(cs, 0, chars, 0, (cs.Length > size) ? size : cs.Length);
}
/// <summary>
/// byte转结构体
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static Message ByteToMessage(byte[] bytes)
{
Message head;
int length = Marshal.SizeOf(typeof(Message));
IntPtr buffer = Marshal.AllocHGlobal(length);
try //struct_bytes转换
{
Marshal.Copy(bytes, 0, buffer, length);
head = (Message)Marshal.PtrToStructure(buffer, typeof(Message));
return head;
}
catch (Exception ex)
{
throw new Exception("Error in BytesToStruct ! " + ex.Message);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
/// <summary>
/// Message转byte
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public static byte[] MessageToByte(Message message)
{
int size = Marshal.SizeOf(typeof(Message));
byte[] bytes = new byte[size];
IntPtr structPtr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(message, structPtr, false);
Marshal.Copy(structPtr, bytes, 0, size);
Marshal.FreeHGlobal(structPtr);
return bytes;
}
}
}
在Winform中的点击事件中的代码是:
private void button1_Click(object sender, EventArgs e)
{
if (server == null || button1.Text.Equals("启动"))
{
server = new Server(textBox1.Text, int.Parse(textBox2.Text));
timer = new Timer()
{
AutoReset = true,
Interval = 0.1d,
};
timer.Elapsed += ShowReceiveMessage;
timer.Start();
button1.Text = "停止";
}
else
{
server.Dispose();
server = null;
timer.Stop();
timer = null;
button1.Text = "启动";
}
}
private string msg = "";
private void ShowReceiveMessage(object sender, System.Timers.ElapsedEventArgs e)
{
while (MessageQueue.hasNew())
{
NetModule.Message message = MessageQueue.GetQueue();
msg += new string(message.msg);//"\n\t" + new string(message.sendName) +"->" + new string(message.rcvName) + ":" + new string(message.msg);
Action<string> ac = new Action<string>(ShowText);
this.Invoke(ac, "");//线程限制原因
}
}
void ShowText(string aa)
{
richTextBox1.Text = msg;
}
具体作用已写注释。
客户端使用的是unity,如果要迁移其他的工具上,除了更改显示你不需做任何操作。客户端代码和服务器代码基本相同,只用主类有一点区别:
public class NetWork:IDisposable
{
private Socket socket;
private int id = -1;
private string selfName = "Garson";
const int BUFFER_SiZE = 2048;
byte[] buffer = new byte[BUFFER_SiZE];
public bool isConnect
{
get
{
if (socket != null)
return socket.Connected;
return false;
}
}
public void Connect(string ip, int port, Action callback )
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAddress = IPAddress.Parse(ip);
IPEndPoint point = new IPEndPoint(ipAddress, port);
socket.BeginConnect(point, ar =>
{
try
{
socket.EndConnect(ar);
}
catch (Exception e)
{
Console.WriteLine("Connect Error : " + e.ToString());
}
if (socket.Connected)
{
StartReceive();
if (callback != null)
callback();
}
} , null);
}
private void StartReceive()
{
IAsyncResult result = socket.BeginReceive(buffer, 0, BUFFER_SiZE, SocketFlags.None, ReceiveCallback, null);
if (result.IsCompleted)
{
}
}
private void ReceiveCallback(IAsyncResult ar)
{
int length = 0;
try
{
length = socket.EndReceive(ar);
}
catch (Exception e)
{
Console.WriteLine("Receive Error : " + e.ToString());
}
byte[] bytes = new byte[length];
Buffer.BlockCopy(buffer, 0, bytes, 0, length);
Message message = MessageUtil.ByteToMessage(bytes);
if (id == -1 && message.sendId == 0)
{
id = message.rcvId;
Message msg = MessageUtil.Set(id, selfName, 0, "Server", selfName + " is Comming!");
Send(MessageUtil.MessageToByte(msg));
}
MessageQueue.EnQueue(message);
StartReceive();
}
public void Send(byte[] bytes)
{
socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallback, null);
}
public void Send(int tid, string tname, string msg)
{
Message message = MessageUtil.Set(id, selfName, tid, tname, msg);
Send(MessageUtil.MessageToByte(message));
}
private void SendCallback(IAsyncResult ar)
{
socket.EndSend(ar);
}
public void Dispose()
{
if(socket != null)
socket.Close();
}
}
在MonoBehaviour中的调用同样简单:
private NetWork netWork;
void Start()
{
netWork = new NetWork();
netWork.Connect("127.0.0.1", 11479, delegate()
{
Debug.Log("Connect Succeed!");
});
}
private string msgcache = "";
private string sendMsg = "";
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
netWork.Send(0, "Server", sendMsg);
sendMsg = "";
}
if (MessageQueue.hasNew())
{
NetModule.Message message = MessageQueue.GetQueue();
msgcache += "\n" + new string(message.sendName) + "->" + new string(message.rcvName) + ":" + new string(message.msg);
}
}
void OnGUI()
{
GUILayout.Label("isConnect: " + netWork.isConnect);
sendMsg = GUILayout.TextField(sendMsg);
GUILayout.Label(msgcache);
}
最后来一发效果展示: