剖析:服务器程序(32位控制台程序)
1.主线程
2.请求连接的套接字缓冲区(每一个客户端的连接请求都会先插入到套接字缓冲区中)
3.侦查线程(循环从套接字缓冲区中检查是否有未处理的套接字,如果有,则创建新的客户端用例线程进行处理,并在缓冲区中删除该套接字)
4.客户端用例线程(用来处理连接状态中的客户端用例)
5.在线用户检测线程(每隔一段时间执行一次,检验用户是否意外离线)
主线程:连接数据库,初始化socket库,初始化请求连接的套接字缓冲区,创建在线用户检测,创建线程侦查线程,循环accept()客户端的连接请求。
#include "stdafx.h" #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include <winsock2.h> #include <process.h> #include "MsgStruct.h" #include "mysql.h" #define ssize_t int MYSQL mysql; unsigned int WINAPI Thread_Online_Detect(void *vargp); void sbuf_insert(sbuf_t *sbuf, SOCKET *clntSock, sockaddr_in *servAddr); unsigned int WINAPI Thread_Detect_Func(void *vargp); void rio_readinitb(rio_t *rp, int fd); int rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); int rio_writeline(int fd, void *usrbuf, size_t n, int flags); int rio_read(rio_t *rp, char *usrbuf, size_t n); int main(){ //初始化数据库信息 MYSQL * conn=mysql_init(&mysql); mysql_options(conn, MYSQL_SET_CHARSET_NAME, "gb2312"); //链接已有数据库 if(!mysql_real_connect(&mysql, "localhost", "root", "", "im", 3306, 0, 0)) printf("connect database 'im' failed!\n"); else printf("connect database 'im' ok!\n"); //初始化socket库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return -1; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return -1; } SOCKET ListenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(ListenSock<0) { closesocket(ListenSock); printf("socket failed\n"); } struct sockaddr_in servAddr; memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr=htonl(INADDR_ANY); servAddr.sin_port=htons(5556); if(bind(ListenSock, (struct sockaddr *)&servAddr, sizeof(servAddr)) <0) { closesocket(ListenSock); printf("bind failed\n"); } if(listen(ListenSock, 5)<0) { closesocket(ListenSock); printf("listen failed\n"); } //初始化套接字缓冲区 sbuf_t sbuf; sbuf.num=0; sbuf.first=0; sbuf.last=-1; //创建在线用户检测线程、侦查线程 unsigned int thread_online; _beginthreadex(NULL, 0, Thread_Online_Detect,NULL, 0, &thread_online); unsigned int thread_detect; _beginthreadex(NULL, 0, Thread_Detect_Func,(void *)&sbuf, 0, &thread_detect); for(;;) { sockaddr_in *servAddr= new sockaddr_in; SOCKET *clntSock= new SOCKET; int addrlen = sizeof(*servAddr); *clntSock=accept(ListenSock, (struct sockaddr *)servAddr, &addrlen); if((*clntSock)<0) { delete servAddr; delete clntSock; printf("接受客户链接失败"); } else sbuf_insert(&sbuf, clntSock, servAddr); } }
请求连接的套接字缓冲区:
管理此缓冲区的有如下两个:sbuf_insert(),sbuf_remove()//分别向缓冲区中添加或者删除套接字请求。
void sbuf_insert(sbuf_t *psbuf, SOCKET *clntSock, sockaddr_in *servAddr) { if(psbuf->num<MAX_CLIENTS) { if(psbuf->last>=(MAX_CLIENTS-1)) psbuf->last=0; else psbuf->last++;: psbuf->buf[psbuf->last]=clntSock; psbuf->servAddr[psbuf->last]=servAddr; psbuf->num++;//必须放在最后 } } void sbuf_remove(sbuf_t *psbuf) { psbuf->num--; if(psbuf->first>=(MAX_CLIENTS-1)) psbuf->first=0; else psbuf->first++; }
侦查线程:
unsigned int WINAPI Thread_Detect_Func(void *vargp) { sbuf_t *psbuf=(sbuf_t *)vargp; while(1) { if(psbuf->num>0) { if(max_num_release>0) { Sock_Addr a; a.clntSock=psbuf->buf[psbuf->first]; a.servAddr=psbuf->servAddr[psbuf->first]; unsigned int threadId; _beginthreadex(NULL, 0, ThreadFunc,(void *)&a, 0, &threadId); sbuf_remove(psbuf); max_num_release--; } else sbuf_remove(psbuf); } } }
客户端用例线程:处理用户的登录,注册,修改信息,添加好友等各种请求。
关于用例的流程图,因为比较多,就专门贴在下一帖中:
unsigned int WINAPI ThreadFunc(void *vargp)
{
Sock_Addr a=*(Sock_Addr *)vargp;
SOCKET clntSock=*(a.clntSock);
sockaddr_in servAddr=*(a.servAddr);
rio_t rio;
rio_readinitb(&rio, clntSock);
//创建TCPClient以便链接客户端发送数据
DWORD dwIP = servAddr.sin_addr.s_addr;
unsigned short port=htons(servAddr.sin_port);
char buf[120];
memset(buf,'\0',sizeof(buf));
for(;;)
{
//下线用户的线程如何关闭?
int num=rio_readlineb(&rio, buf, sizeof(buf));
if(num>0)
{
printf("received %d Bytes\n",num);
// printf("the content is:\n%s\n",buf);
header *head=(header *)buf;
if(head->magic==0x54 && head->flags==0x11)
{
getonlineback(buf);
}
else
if(head->magic==0x54 && head->flags==0x01)//登陆包
{
if(logging(buf, clntSock, servAddr))
{
offline_msg_put(buf,clntSock, servAddr);
getonlineback(buf);
}
else
return 0;
}else//注册包
if(head->magic==0x54 && head->flags==0x02)
{ //模拟注册成功
if(registe(buf, clntSock, servAddr))
{
closesocket(clntSock);
delete a.clntSock;
delete a.servAddr;
max_num_release++;
return 0;
}
}
else
if(head->magic==0x54 && head->flags==0x04)//修改个人信息包
{
set_self(buf, clntSock, servAddr);
}
else
if(head->flags==0x08)//查询包
{
query_friend(buf, clntSock, servAddr);
}
else
if(head->flags==0x10)//添加删除好友
{
add_dec_friend(buf, clntSock, servAddr);
}
else
if(head->magic==0x54 && head->flags==0x40)//申请新陌生人信息
{
getstrangermsg(buf,clntSock,servAddr);
}
else
if(head->magic==0x54 && head->flags==0x20)//下线通知
{
offline(a, buf);
closesocket(clntSock);
return 1;
}
else
if(head->magic==0x54 && head->flags==-128)//udp离线消息入表
{
udpmsg(buf);
}
else
{
printf("信息包不在服务范围内.....");
}
}
else
{
printf("接受数据失败");
closesocket(clntSock);
printf("用户意外下线结束线程成功!\n");
return 1;
}
}
return 1;
}
在线用户检测线程:
unsigned int WINAPI Thread_Online_Detect(void *vargp)
{
Sock_Addr servAddr_;
char sql[255];
printf("监测线程启动...\n");
while(1)
{
Sleep(MAXONLINETIME*1000);
printf("监测线程本次检查开始...\n");
sprintf(sql,"select * from user where state='在线'");
if(mysql_query(&mysql, sql))
{
printf("检查失败");
}
else
{
_res_ = mysql_store_result(&mysql);
if (NULL == _res_)
{
printf("mysql_store_result failure!");
}
else
{
while(_row_ = mysql_fetch_row(_res_))
{
int time_ = GetTickCount();
if((time_ - atoi(_row_[10]))>(100*1000))
{
cln_offline line;
line.magic=0x54;
line.flags=0x20;
sprintf(line.userid,"%s",_row_[0]);
offline(servAddr_, (char *)&line, 1);
}
}
}
}
printf("监测线程本次检查结束...\n");
}
return 1;
}
以上代码仅作参考。
注意!笔者所写的服务器端尚未良好解决对数据库访问冲突的问题。