现在网络的应用越来越普及,网络的构建也越来越简便,对于某些研究性项目自建网络服务端
也是可行的方案。本项目的网络服务,是用C#,基于Socket构建的,核心的工作是通过自定的BS60传输协议,实现与手机端,PC端的数据传输。服务端有两个基本数据表,一个是用户注册表,一个是用户在线数据表。数据管理系统,主要处理网络任务数据,网友分享数据。采用PC机,基于Socket的网络服务,用WIFI局域网就可以进行开发测试,外网要通过内网穿透技术。以下扼要介绍一下服务端的基本架构及邮箱验证和手机验证。
(一),用C#构建基于Socket网络服务
网络服务的用户监听,项目开始有两种模式,一个是ASYNC 模式,一个是Thread 模式,后来一直采用的是Thread 模式。除了基本的通用架构,服务端主要是处理(接收及发送)手机端及PC端的数据请求。
1),网络服务的基本架构
//获取本机IP地址
public List<string> getIP()
{
List<string> listIP = new List<string>();
try
{
string HostName = Dns.GetHostName(); //得到主机名
IPHostEntry IpEntry = Dns.GetHostEntry(HostName);
for (int i = 0; i < IpEntry.AddressList.Length; i++)
{
//从IP地址列表中筛选出IPv4类型的IP地址
//AddressFamily.InterNetwork表示此IP为IPv4,
//AddressFamily.InterNetworkV6表示此地址为IPv6类型
if (IpEntry.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
{
listIP.Add(IpEntry.AddressList[i].ToString());
}
}
return listIP;
}
catch (Exception ex)
{
MessageBox.Show("获取本机IP出错:" + ex.Message);
listIP.Clear();
return listIP;
}
}
// 启动网络服务监听
private Thread threadWatch = null;//负责监听客户端连接请求的线程
private Socket socketWatch = null;//负责监听的套接字
int READBUFFERSIZE = 1024 * 1024;
private void startSeverThread()
{
// 创建一个基于IPV4的TCP协议的Socket对象
if (socketWatch == null)
{
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
else
{
return;
}
//取得IP地址
string strIPaddress = "";
List<string> listIP = getIP();
if (listIP.Count == 0)
{
// 未能获取IP!
return;
}
else if (listIP.Count >= 1)
{
strIPaddress = listIP[0];
}
IPAddress ip = IPAddress.Parse(strIPaddress));
//创建一个网络端点
string strPort = "8899";
IPEndPoint ippoint = new IPEndPoint(ip, int.Parse(strPort));
//把负责监听的套接字绑定到唯一的IP和端口
socketWatch.Bind(ippoint);
//设置监听队列的长度
socketWatch.Listen(10);
threadWatch = new Thread(WatchConnection);
threadWatch.IsBackground = true;//设置为后台
threadWatch.Start();// 启动线程
}
// 用户SOCKET 结构
struct userSocket
{
public Socket curSocket;
public int iposj;// 在线用户表的位置
}
// 监听客户端连接请求的线程
void WatchConnection()
{
while (true)
{
//创建监听的连接套接字
Socket sockConnection = socketWatch.Accept();
// 创建连接成功添加到 Online User manager 对象里面
// 将 IP ADDRESS 转换成 Long 数据
long Ipvalue = GetIpLongVale(sockConnection.RemoteEndPoint.ToString());
// 将当前访问时间存到 int 数据中
int icTimes = GetLnkServerTime();
// 将访问信息添加到在线用户表中
int icposj = onLineUserManager.AppendOnlineUser(0, sockConnection, Ipvalue, icTimes);
// 创建用户线程 参数传递结构
userSocket cLnkuser = new userSocket();
//为客户端套接字连接开启新的线程用于接收客户端发送的数据
cLnkuser.curSocket = sockConnection;
cLnkuser.iposj = icposj;
// 创建接收数据的线程
Thread t = new Thread(RecMsg);
//设置为后台线程
t.IsBackground = true;
//为客户端连接开启线程
t.Start((object)cLnkuser);
}
2),接收处理客户端数据的线程
这一部分是每个项目的核心,不同的项目处理的方式方法也可能不同,本项目处理方式如下。
/// 接收客户端信息的线程部分代码
void RecMsg(object o)
{
userSocket curStruct = (userSocket)o;
Socket socketClient = curStruct.curSocket;
int icurposj = curStruct.iposj;
Boolean loopRecvblv = true;
//创建一个字节数组接收数据
byte[] arrMsgRec = new byte[READBUFFERSIZE];
byte[] recArrFastReply = new byte[256];
// 通过在线用户管理,协调控制数据的接收与发送
onLineUserManager.checkSetOnlineUser(icurposj, SET_RECEIVE_RUN);// 设置接收运行
onLineUserManager.TaskRecReplyCtrl(icurposj, REPLY_INIT_SET);// 任务接收初始设置
Boolean crecLockblv = false;// 接收数据锁保护
int iheartBeatTimenum = 0;// 心跳包数
onLineUserManager.checkResetThreadLoopWait();
while (loopRecvblv)
{
// 设置循环等待参数,可根据在线用户数自动调整
Thread.Sleep(onLineUserManager.mThreadLoopSpaceMisec);
// 检查用户是否在线
if (!onLineUserManager.checkUserIfOnlineblv(icurposj)) break;// 用户离线
if (crecLockblv)// 一项数据接收完前循环等待
{
Thread.Sleep(ConstStringIntDataClass.CYBERLOOPWAIT_RECEIVE);
continue;
}
// 等待快速回应
if (onLineUserManager.TaskRecReplyCtrl(icurposj, REPLYOPER_ITEM) != REPLY_EMPTY)
{// reply user task
if (onLineUserManager.TaskRecReplyCtrl(icurposj, REPLYOPER_STATUS) != REPLY_FINISH)
{
Thread.Sleep(20);
continue;
}
crecLockblv = true;// 接收控制锁
int length = socketClient.Receive(recArrFastReply);
if (length >= 10)// 接收回应数据
{
// 检查处理回应的数据
// 。。。。。。。
}
crecLockblv = false;// 数据接收完成开锁
continue;
}
// 正常等待接收数据
crecLockblv = true;// 数据接收锁
try
{
//接收数据
mReceiveSumn = 0;// 重置本次接收的字节数
int length = socketClient.Receive(arrMsgRec);
if (length > 0)// 接收到数据
{
int icgLen = length;
if (icgLen > 20) icgLen = 20;
// 检查是否是数据头
int iretcd = mglobal_checkDataHead(arrMsgRec, length);
if (iretcd == DATAKIND_ERROR){// 不是合法数据
crecLockblv = false;
continue;
}
if (iretcd == DATAKIND_HEARTBEAT)
{// 接收到心跳包
onLineUserManager.checkSetOnlineUser(icurposj, APPENDOPER_HEARTBEAT);
crecLockblv = false;
continue;
}
// 数据一次接收完成
if (iretcd == DATAKIND_FINISH)
{
// 处理完成的接收数据
// 。。。。
crecLockblv = false;
continue;
}
// 数据接收未完成
try
{
while (true)
{// 继续读入数据
length = socketClient.Receive(arrMsgRec);
// 添加接收的数据,并检查是否接收完成,接收完成后,退出
// 。。。
}
// 处理接收的数据
crecLockblv = false;
}
catch (Exception e)
{
// 用户掉线或离开!
loopRecvblv = false;
break;
}
}
}
}
catch (Exception e)
{
// 用户掉线或离开!
loopRecvblv = false;
break;
}
// 无数据或数据处理完成,继续循环等待
crecLockblv = false;
}
onLineUserManager.checkSetOnlineUser(icurposj, SET_RECEIVE_STOP);// 设置停止接收数据
onLineUserManager.removeOnlineUser(icurposj);
//关闭套接字,退出监听
socketClient.Close();
//结束当前线程
Thread.CurrentThread.Abort();
arrMsgRec = null;
}
3), 数据的发送
项目数据的发送,依据客户端的请求不同,具体的处理流程也是不同的,这里只给处最底层的部分代码。
public void bs60Send(Socket socket, byte[] buffer, int offset, int size, int timeout, int onLineposj)
{
int startTickCount = Environment.TickCount;
int sent = 0; // 已经发送的字节数
do
{
if (Environment.TickCount > startTickCount + timeout)
throw new Exception("发送超时");
try
{
sent += socket.Send(buffer, offset + sent, size - sent, SocketFlags.None);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.WouldBlock ||
ex.SocketErrorCode == SocketError.IOPending ||
ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
{
// 缓冲区可能已满,等待并重试
Thread.Sleep(30);
}
else
throw ex; // 发生任何严重错误
}
} while (sent < size);
if (sent >= size)
{
// 发送完成
}
else
{
// 发送失败!
}
}
(二),PC客户端的邮箱验证
网络服务中,对于PC端的用户通常采用邮箱验证方式,在此提供部分代码供参考。
进行邮箱验证,首先要选择一个已经开通了SMTP功能的电子邮箱,项目中使用的163的邮箱。
public static string Email163SmtpSend(string senderName, string toEmail, string copyEmail, string emailTitle, string emailBody)
{
String fromEmail = "xxxxxx@163.com";//
string smtpServer = "smtp.163.com";
string smtpasswd = "h163126";
MailMessage msg = new MailMessage();//邮件信息类
msg.From = new MailAddress(fromEmail, senderName, Encoding.UTF8);//邮箱,发件人,编码
msg.To.Add(toEmail);//收件人,可以循环添加收件人
if (copyEmail.Length > 6)
{
msg.CC.Add(copyEmail);//抄送人,同上
}
msg.Subject = emailTitle;//邮件主题
msg.BodyEncoding = Encoding.UTF8;//邮件内容编码
msg.SubjectEncoding = Encoding.UTF8;//邮件主题编码
msg.IsBodyHtml = false;//是否是html格式
msg.Body = emailBody;
msg.Priority = MailPriority.High;//邮件优先级
SmtpClient client = new SmtpClient();// 客户端smtp
client.DeliveryMethod = SmtpDeliveryMethod.Network;//指定邮件发送方式为网络
client.Host = smtpServer;// 指定服务器
client.EnableSsl = false;//服务器支持安全连接,安全为true
client.UseDefaultCredentials = false;//是否随着请求一起发
client.Credentials = new NetworkCredential(fromEmail, smtpasswd);//用户名密码
string strInforet = "";
try
{
client.Send(msg);
strInforet = "发送成功!";
}
catch (Exception)
{
strInforet = "发送失败!";
}
return strInforet;
}
(三),移动端的手机验证
网络服务中的手机验证码发送,是通过“验证服务手机”的专用APP,协同网络服务端手机验证程序完成的。架构是服务端设置一个专用网络数据接口,启动专用 监听程序,处理验证服务手机的登录。移动端开发了一个专用的连网登录及接收验证发送任务的APP程序。
1), C# 网络端手机验证码发送的部分代码
这部分就是向已经通过端口连接的验证服务手机,发送服务端生成的手机验证码相关数据(接收验证码的手机号及验证码)
public class VerifyPhone
{
// 保存验证用手机的相关信息:套接字与接收缓存
public Socket socket;
public byte[] Rcvbuffer;
//实例化方法
public VerifyPhone(Socket s)
{
socket = s;
}
//清空接受缓存,新的接收之前要调用该方法
public void ClearBuffer()
{
Rcvbuffer = new byte[1024];
}
//
public void Dispose()
{
try
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
finally
{
socket = null;
Rcvbuffer = null;
}
}
}
// 验证服务程序
class SocketServerVerifyClass
{
// 监听的套接字
TcpListener listener;
// 是否启动连接了发送验证码的手机
bool IsVerifyStart = false;
public List<VerifyPhone> phoneLst = new List<VerifyPhone>();
// 设置启动端口监听
public void Start(string strIPaddress, int intPort)
{
if (IsVerifyStart)
return;
//服务器启动监听
IPEndPoint localep = new IPEndPoint(IPAddress.Parse(strIPaddress), intPort);
listener = new TcpListener(localep);
listener.Start(10);
IsVerifyStart = true;
AsyncCallback callback = new AsyncCallback(AcceptCallBack);
listener.BeginAcceptSocket(callback, listener);
}
private void AcceptCallBack(IAsyncResult ar)
{
try
{
// 完成异步接收连接请求的异步调用
// 连接信息添加到手机列表
Socket handle = listener.EndAcceptSocket(ar);
VerifyPhone vphone = new VerifyPhone(handle);
phoneLst.Add(vphone);
AsyncCallback callback;
// 调用异步方法接收连接请求
if (IsVerifyStart)
{
callback = new AsyncCallback(AcceptCallBack);
listener.BeginAcceptSocket(callback, listener);
}
// 连接上进行异步的数据接收
vphone.ClearBuffer();
callback = new AsyncCallback(ReceiveCallback);
vphone.socket.BeginReceive(vphone.Rcvbuffer, 0, vphone.Rcvbuffer.Length, SocketFlags.None, callback, vphone);
}
catch
{
// 在调用引发异常,套接字Listener被关闭,设置为未启动侦听状态
IsVerifyStart = false;
}
}
private void ReceiveCallback(IAsyncResult ar)
{
VerifyPhone vphone = (VerifyPhone)ar.AsyncState;
try
{
int i = vphone.socket.EndReceive(ar);
if (i == 0)
{
RemoveClient(vphone);
return;
}
else
{
vphone.ClearBuffer();
AsyncCallback callback = new AsyncCallback(ReceiveCallback);
vphone.socket.BeginReceive(vphone.Rcvbuffer, 0, vphone.Rcvbuffer.Length, SocketFlags.None, callback, vphone);
}
}
catch
{
}
}
public void SendData(VerifyPhone vphone, string data)
{
byte[] byteAr = Encoding.UTF8.GetBytes(data);
try
{
AsyncCallback callback = new AsyncCallback(SendCallback);
frd.socket.BeginSend( byteAr, 0, byteAr.Length, SocketFlags.None, callback,vphone);
}
catch(SocketException ex) {
}
}
private void SendCallback(IAsyncResult ar)
{
VerifyPhone vphone = (VerifyPhone)ar.AsyncState;
vphone.socket.EndSend(ar);
}
private void RemoveClient(VerifyPhone vphone)
{
foreach (VerifyPhone curphone in phoneLst)
{
if (curphone == vphone)
{
phoneLst.Remove(vphone);
}
}
}
public void RemoveAllClient()
{
foreach (VerifyPhone curphone in phoneLst)
{
phoneLst.Remove(curphone);
}
}
public void Stop()
{
if (!IsVerifyStart)
return;
listener.Stop();
IsVerifyStart = false;
}
}
// 启动运行 验证码服务程序
SocketServerVerifyClass verifyServer = new SocketServerVerifyClass();
// 启动服务监听
string strIPaddress = getIP();// 得到IP地址
string verifyPort = "8805";// 专用端口
verifyServer.Start(strIPaddress, int.Parse(verifyPort));
// 验证码的发送处理过程
服务端收到手机验证申请后
A,自动生成一个随机手机验证码;
B,检查是否连接了验证码发送手机,没有则向申请注册手机发送现在不能注册的提示;
C,构建验证码发送数据:string strSendSMS = "bs60SMSPhone:" + 手机号 + "," + 验证码;
D,通过运行验证服务程序(verifyServer) 发送验证码到验证服务手机
verifyServer.SendData((VerifyPhone)phoneLst[0], strSendSMS);
E,同时也向申请验证的手机发送验证码(注,手机的验证是在移动端完成的)。
2),验证服务手机的验证码接收及发送APP
任何一部安装安装了Android“手机验证码发送服务APP”的手机,都可以成为“验证服务手机”。
在手机端同样通过Socket进行网络数据接收与发送。发送部分是保持活动在线的心跳包。下面是java语言的部分代码。
// 网络连接
private void doConnect() {
//子线程请求网络
new Thread(new Runnable() {
@Override
public void run() {
String strname = "服务器连接....";
try {
msocket = new Socket();
// 用服务端IP地址和端口号,建立Socket连接
String serverIP = "192.168.1.188";// 服务器端IP
String serverPort = "8805";// 服务器端口号
int icport = Integer.valueOf(serverPort);
msocket.connect(new InetSocketAddress(serverIP, icport), 5 * 1000);
bos = new BufferedOutputStream(msocket.getOutputStream());
// 启动监听,读取网络数据
readThread = new ReadThread();
readThread.start();
strname = "服务器连接成功!";
isconnectBoolean=true;
// 启动心跳包
new Thread(new KeepAliveHeartBeat()).start();
} catch (Exception e) {
e.printStackTrace();
strname = "服务器连接连接失败!";
}
Message msg = new Message();
byte[] data = strname.getBytes();
msg.obj = data;
msg.what = SHOW_OPERINFO;// 显示操作信息
mHandler.sendMessage(msg);
}
}).start();
}
//启动一个线程,一直读取从服务端发送过来的消息
private class ReadThread extends Thread {
@Override
public void run() {
while (true) {
try {
BufferedInputStream bufferedInput = new BufferedInputStream(msocket.getInputStream());
if (bufferedInput.available() > 0) {
int len = bufferedInput.available();
byte[] data = new byte[len];
int nowbyten =bufferedInput.read(data);
Message msg = new Message();
msg.obj = data;
msg.what = CHECK_DATA;// 检查接收到的数据,自动发送验证码
mHandler.sendMessage(msg);
}
}catch (IOException e){
}
}
}
}
// 维持在线的心跳包
class KeepAliveHeartBeat implements Runnable{
long checkDelay = 10;
long keepAliveDelay = 2000;
int iheratBreakErrNum = 0;
int iheratBreakNormalNum = 0;
public void run() {
cKeepAliveblv = true;
while(!isconnectBoolean){// wait connect,...
try {
Thread.sleep(checkDelay);
}catch(InterruptedException e){
}
}
while(isconnectBoolean){
if(System.currentTimeMillis()-lastSendTime>keepAliveDelay && sendLockblv == false){
sendLockblv = true;//
try {
bos.write("bs60HEARTBEAT".getBytes());
//要flush一下
bos.flush();
iheratBreakErrNum = 0;
iheratBreakNormalNum++;
} catch (IOException e) {
e.printStackTrace();
iheratBreakErrNum++;
}
lastSendTime = System.currentTimeMillis();
sendLockblv = false;
if(iheratBreakErrNum > 3){
break;
}
}else{
try {
Thread.sleep(checkDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(iheratBreakNormalNum >= 4){
iheratBreakNormalNum = 0;
Message msg = new Message();
msg.obj = "...";
msg.what = SERVER_NORMAL;// 显示网络连接正常的信息
mHandler.sendMessage(msg);
}
}
if(iheratBreakErrNum > 3){
Message msg = new Message();
msg.obj = "Server Break";
msg.what = SERVER_BREAK;// 处理服务器中断
mHandler.sendMessage(msg);
}
cKeepAliveblv = false;
}
}
// 发送SMS
要从验证手机向其它手机发送手机验证码,需要得到发送SMS权限。此类代码网上比较多,这里就不提供了。