1.结构体和字节数组的互转
我们在C++中进行socket网络编程时,需要自定义一些协议包来完成功能所需的数据请求和回复,在C++中协议包我们通常使用结构体来定义,如果我们在C#中也使用结构体来定义协议包内容,因为C#的send和recv函数发送数据是以字节的形式进行发送的,所以我们就需要将结构体协议包先转换为字节数组,然后通过字节数组发送数据包。下面将结构体和字节数组进行互转的方法封装在transfer类中,需要使用时直接调用类的静态方法即可。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace service
{
public static class transfer
{
<summary>
/// 结构体转byte数组
/// </summary>
/// <param name="structObj">要转换的结构体</param>
/// <returns>转换后的byte数组</returns>
public static byte[] StructToBytes(object structObj)
{
//得到结构体的大小
int size = Marshal.SizeOf(structObj);
//创建byte数组
byte[] bytes = new byte[size];
//分配结构体大小的内存空间
IntPtr structPtr = Marshal.AllocHGlobal(size);
//将结构体拷到分配好的内存空间
Marshal.StructureToPtr(structObj, structPtr, false);
//从内存空间拷到byte数组
Marshal.Copy(structPtr, bytes, 0, size);
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回byte数组
return bytes;
}
/// <summary>
/// byte数组转结构体
/// </summary>
/// <param name="bytes">byte数组</param>
/// <param name="type">结构体类型</param>
/// <returns>转换后的结构体</returns>
public static object BytesToStuct(byte[] bytes, Type type)
{
//得到结构体的大小
int size = Marshal.SizeOf(type);
//byte数组长度小于结构体的大小
if (size > bytes.Length)
{
//返回空
return null;
}
//分配结构体大小的内存空间
IntPtr structPtr = Marshal.AllocHGlobal(size);
//将byte数组拷到分配好的内存空间
Marshal.Copy(bytes, 0, structPtr, size);
//将内存空间转换为目标结构体
object obj = Marshal.PtrToStructure(structPtr, type);
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回结构体
return obj;
}
}
}
2.区分每个协议包类型的枚举变量
每个协议包的包头的变量我使用枚举来区分各个协议包,因为给结构体中的变量进行赋值时必须定义有参构造给所有的成员变量赋值(C#的要求),所以我们定义结构体协议包时只写包需要的public的数据成员,在结构体内暂不赋值,具体使用时再进行具体成员数据的赋值。就像下面这样。
(1)定义枚举类型
(2)定义协议包
注意包中的string类型需要显示的指定字节数,不然后面在进行transfer类中结构体和字节数组的互转时会因为无法确定string类型的大小而无法转换。
(3)使用协议包发送具体的请求
3.服务端的网络框架
服务端网络需要先建立自己的套接字,绑定ip和端口后进行监听,开启线程等待客户端连接,接收到客户端连接之后需要为之开启线程接收并处理它的请求,判断和处理的逻辑放在dealPackage函数中,后续所有服务端功能的添加都将在该函数中注册后实现。服务端发送数据和客户端相同,都是先发包大小,再发数据包。服务端的代码如下。
class server
{
ConnectionPool m_connPool;
Socket m_sock;
//存连接用户的socket
List<Socket> m_clientSocks;
bool isOpen;
//存登录用户的uid和Socket的map
Dictionary<int, Socket> m_LoginUsers=new Dictionary<int, Socket>();
//关闭服务器
private void closeServer()
{
if(m_sock!=null)
{
m_sock.Close();
}
foreach(var item in m_clientSocks)
{
item.Close();
}
}
//发送数据
public void SendData<T>(Socket client,T structObj)
{
if (isOpen)
{
int len = Marshal.SizeOf(structObj);
byte[] size = BitConverter.GetBytes(len);
byte[] data = transfer.StructToBytes(structObj);
client.Send(size);
client.Send(data);
}
}
//处理注册请求
private void dealRegisterRq(Socket client,byte[] bytes)
{
Trace.WriteLine("服务端收到注册请求,开始处理注册请求");
RegisterRQ rq=(RegisterRQ)transfer.BytesToStuct(bytes,typeof(RegisterRQ));
//拆包查询数据库,判断该人是否是管理员,如果是管理员,不用注册,必须登录
MySqlConnection con = m_connPool.getPool().getConnection();
string query = "select uid,identity,status from user where tel='"+rq.tel+"' and identity="+rq.identity+" and name='"+rq.name+"'";
MySqlCommand cmd = new MySqlCommand(query,con);
MySqlDataReader reader=cmd.ExecuteReader();
RegisterRS rs = new RegisterRS();
rs.type=TYPE.reg_rs;
if(reader.Read())
{//查到了该人的信息,学生或企业已经存在
int uid = reader.GetInt32("uid");
int identity = reader.GetInt32("identity");
rs.uid=uid;
rs.identity=identity;
//如果status=0那么就是已注册,等待审核
//如果status=1 就是已经注册,审核完成,提醒登录
int status = reader.GetInt32("status");
if(status==0)
{
rs.rs=RegisterResult.ready_check;
}else
{
rs.rs=RegisterResult.user_is_exist;
}
}else
{
con.Close();
con.Open();
//没查到该人的信息,可以进行注册,状态为待审核状态
//进行数据库插入,状态为待审核状态
string insert = "insert into user(tel,pwd,identity,status,name,email) values('"+rq.tel+"','"+rq.pwd+"',"+rq.identity+",0,'"+rq.name+"','"+rq.email+"')";
cmd=new MySqlCommand(insert, con);
cmd.ExecuteNonQuery();
Trace.WriteLine("服务器注册用户成功,待审核该用户");
rs.rs=RegisterResult.reg_sucess_but_readyCheck;
}
m_connPool.getPool().closeConnection(con);
//发送注册回复包
SendData<RegisterRS>(client,rs);
}
//处理客户端发来的各种类型的请求包
private void dealPackage(Socket client,byte[] bytes)
{
//获取协议头
byte[] typeByte = new byte[4];
for (int i = 0; i<4; i++)
{
typeByte[i]=bytes[i];
}
Trace.WriteLine("");
int type = BitConverter.ToInt32(typeByte);
if (type>10000 && type<10000+200)
{
switch (type)
{
//处理注册请求
case (int)TYPE.reg_rq:
dealRegisterRq(client,bytes);
break;
}
}
}
//接收数据线程的任务函数
private void receive(Object o)
{
Socket client = (Socket)o;
while(true)
{
try
{
byte[] packsize = new byte[4];
int size = client.Receive(packsize);
if (size>0)
{
//包大小
int pack = BitConverter.ToInt32(packsize, 0);
//Trace.WriteLine(pack);
byte[] buf = new byte[pack];
int offset = 0;
List<byte> data = new List<byte>();
while (offset<pack)
{
Array.Clear(buf, 0, buf.Length);
int recvLen = client.Receive(buf);
byte[] temp = new byte[recvLen];
Array.Copy(buf, temp, recvLen);
data.AddRange(temp);
offset+=recvLen;
}
byte[] bytes = data.ToArray();
dealPackage(client,bytes);
}
else
{
break;
}
}
catch (SocketException ex)
{
if (ex.ErrorCode==10053)
{ //服务器关闭
closeServer();
isOpen=false;
break;
}
if (ex.ErrorCode==10054)
{
//客户端关闭
foreach (var item in m_clientSocks)
{
if (item==client)
{
m_clientSocks.Remove(item);
break;
}
}
break;
}
}
}
}
//接收连接线程的任务函数
private void accept(Object o)
{
Socket sock = (Socket)o;
while (true)
{
try
{
Socket client = sock.Accept();
m_clientSocks.Add(client);
ThreadPool.QueueUserWorkItem(receive, client);
}
catch (SocketException ex)
{
isOpen=false;
break;
}
}
}
//创建服务端网络
public server()
{
m_clientSocks=new List<Socket>();
//创建连接池对象
m_connPool=new ConnectionPool();
//创建socket
m_sock=new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint point = new IPEndPoint(IPAddress.Any, 8088);
m_sock.Bind(point);
m_sock.Listen(100);
Thread acceptThread = new Thread(accept);
acceptThread.IsBackground=true;
acceptThread.Start(m_sock);
isOpen=true;
}
//开启服务器
public static void Main(string[] args)
{
server s = new server();
while (true) ;
}
};
};
4.客户端的网络框架
每个客户端可以创建网络和服务端进行连接,连接成功后,客户端网络需要开启一个线程来接收服务端发送的数据包,客户端发送数据包采用先发包大小,再发数据包的形式。客户端网络类的代码如下。
//因为客户端这边我用了界面,所以继承了Form
public partial class Client : Form
{
public Socket m_sock;
public bool isConnected;
public string m_ip;
public int m_port;
public Login m_login;
//发送数据
public void SendData<T>(T structObj)
{
if(isConnected)
{
int len = Marshal.SizeOf(structObj);
byte[] size = BitConverter.GetBytes(len);
byte[] data = transfer.StructToBytes(structObj);
m_sock.Send(size);
m_sock.Send(data);
}
}
//客户端处理注册回复包
private void dealRegisterRs(byte[] bytes)
{
//拆包
RegisterRS rs = (RegisterRS)transfer.BytesToStuct(bytes, typeof(RegisterRS));
switch(rs.rs)
{
case RegisterResult.reg_sucess_but_readyCheck:
MessageBox.Show(m_login,"注册成功,等待管理员审核");
break;
case RegisterResult.ready_check:
MessageBox.Show(m_login,"已经注册过了,请等待管理员审核");
break;
case RegisterResult.user_is_exist:
MessageBox.Show(m_login,"已经审核过了,可以直接登录");
break;
}
}
//客户端接收服务端发来的各种回复包
private void dealPack(byte[] bytes)
{
byte[] typeByte = new byte[4];
for (int i = 0; i<4; i++)
{
typeByte[i]=bytes[i];
}
Trace.WriteLine("");
int type = BitConverter.ToInt32(typeByte);
if (type>10000 && type<10000+200)
{
switch (type)
{
//处理注册回复包
case (int)TYPE.reg_rs:
dealRegisterRs(bytes);
break;
}
}
}
//接收数据线程的任务函数
private void receive(object o)
{
Socket sock = (Socket)o;
while (true)
{
try
{
byte[] packsize = new byte[4];
int size = sock.Receive(packsize);
if (size>0)
{
//包大小
int pack = BitConverter.ToInt32(packsize, 0);
Trace.WriteLine(pack);
byte[] buf = new byte[pack];
int offset = 0;
List<byte> data = new List<byte>();
while (offset<pack)
{
Array.Clear(buf, 0, buf.Length);
int recvLen = sock.Receive(buf);
byte[] temp = new byte[recvLen];
Array.Copy(buf, temp, recvLen);
data.AddRange(temp);
offset+=recvLen;
}
byte[] bytes = data.ToArray();
dealPack(bytes);
}
else
{
sock.Close();
isConnected=false;
break;
}
}
catch (SocketException e)
{
sock.Close();
isConnected=false;
break;
}
}
}
public Client(string ip, int port)
{
this.Hide();
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
m_ip=ip;
m_port=port;
m_sock=new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint point = new IPEndPoint(IPAddress.Parse(ip), port);
m_sock.Connect(point);
isConnected=true;
Thread receiveThread = new Thread(receive);
receiveThread.Start(m_sock);
m_login = new Login(this);
m_login.Show();
}
[STAThread]
public static void Main(string[] args)
{
ApplicationConfiguration.Initialize();
Application.Run(new Client("127.0.0.1", 8088));
}
}
后面就是客户端发送请求包,服务端处理后发送回复包返回给客户端进行响应的过程了。懂得都懂。