自制即时通信系统: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 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;
}

 

以上代码仅作参考。

注意!笔者所写的服务器端尚未良好解决对数据库访问冲突的问题。

你可能感兴趣的:(socket,服务器,VC,32位,即时通信)