自制即时通信系统:socket网络编程(2)
剖析:服务器程序(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 intMYSQL 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");elseprintf("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; }SOCKETListenSock = 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("接受客户链接失败");}elsesbuf_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;elsepsbuf->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;elsepsbuf->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--;}elsesbuf_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);}elseif(head->magic==0x54 && head->flags==0x01)//登陆包{if(logging(buf, clntSock, servAddr)){offline_msg_put(buf,clntSock, servAddr);getonlineback(buf);}elsereturn 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;}}elseif(head->magic==0x54 && head->flags==0x04)//修改个人信息包{set_self(buf, clntSock, servAddr);}elseif(head->flags==0x08)//查询包{query_friend(buf, clntSock, servAddr);}elseif(head->flags==0x10)//添加删除好友{add_dec_friend(buf, clntSock, servAddr);}elseif(head->magic==0x54 && head->flags==0x40)//申请新陌生人信息{getstrangermsg(buf,clntSock,servAddr);}elseif(head->magic==0x54 && head->flags==0x20)//下线通知{offline(a, buf);closesocket(clntSock);return 1;}elseif(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;}
以上代码仅作参考。
注意!笔者所写的服务器端尚未良好解决对数据库访问冲突的问题。