socket 编程时我想在服务器端最多允许200个人同时连接,用没有什么好的方法使用close掉的编号?我的设想是accept初始编号为从1-200,用户close掉的编号放在另一个数组里面(int dest[200]),如果初始编号用完后就在dest数组里面找到编号进行accept,并从数组里面将此数删除,如果遇到用户close的或超时的连接则加入到此数组里面,不知道这中方法对不对?如果这种方法正确,但是我感到它它麻烦了,是否还有更好的方法?请多指教,谢谢!!
解决方案 »
- 地震了,吓死我了,感谢以前帮助过我的人
- 关于listbox中添加水平滚动条
- 请教高手:如何在winxp 中编程实现创建多个remote desktop session
- about listbox 右键消息
- 新手之问->2
- vsprintf 怎么用
- getExitCodeThread应该在什么地方用?
- 如何开发网站负载测试软件?
- c++ 怎么实现SQL Server 2005缓存依赖
- CInternetSession::OpenURL 的问题
- 急急急!在VC7环境下利用Socket编写程序,为什么Debug版本的程序传送的数据不是乱码,而Release版本的程序传送的是乱码?
- vc中定义的unsigned char型字符变量应当是一个字节,输出怎么是两个呢?
可是在OnAccept里面必须有连接号啊?我必须给它一个确定的数字,我感觉你的方法不能解决问题,还是我对CAsyncSocket理解的不够?请多指教!
作者:华东船舶工业学院机械系 袁 渊下载本文示例源代码摘要:基于TCP/IP的网络通信技术实现了面向连接的用户与服务器间点对点异步通信,本文在该基础上应用了多线程以及共享数据结构技术,使网络服务器具有了多用户间数据转发的功能,进而解决了局域网多用户间的通信问题。关键词:TCP/IP;多线程;共享;通信;网络
引言由于因特网的迅速流行,越来越多的应用程序具备了在网上与其它程序通信的能力。从WIN95开始微软把网络功能融进了它的操作系统,使得应用程序网络通信能力更为普及。因此,微软的TCP/IP协议也就成为网络应用程序基于的首选协议。一般采用TCP/IP协议的应用程序只实现了单用户与服务器间点对点的连接,而本文在VC6.0的环境下,运用了了多线程以及共享数据结构技术,不仅实现了多用户与服务器间的连接,而且解决了多用户间信息互发问题----依靠服务器的转发功能。通过本文的阐述,希望能对那些需要编写多用户网络通信程序的读者以启发。 一、技术概述
1.1 基于TCP/IP的通信技术基于TCP/IP的通信基本上都是利用SOCKET套接字进行数据通讯,程序一般分为服务器端和用户端两部分。下面简要地讲一下设计思路(VC6.0下):第一部分 服务器端
一、创建服务器套接字(create)。
二、服务器套接字进行信息绑定(bind),并开始监听连接(listen)。
三、接受来自用户端的连接请求(accept)。
四、开始数据传输(send/receive)。
五、关闭套接字(closesocket)。第二部分 用户端
一、创建用户套接字(create)。
二、与远程服务器进行连接(connect),如被接受则创建接收进程。
三、开始数据传输(send/receive)。
四、关闭套接字(closesocket)。通过以上设计思路,我们可以创建一个简单的面向连接的单用户程序。下面,将介绍多线程技术,以使程序支持多用户。1.2 多线程技术我们可以把线程看成是一个进程(执行程序)中的一个执行点,每个进程在任何给定时刻可能有若干个线程在运行。一个进程中的所有线程共享该进程中同样的地址空间,同样的数据和代码,以及同样的资源。进程中每个线程都有自己独立的栈空间,和其它线程分离,并且不可互相访问。每个线程在本进程所占的CPU时间内,要么以时间片轮换方式,要么以优先级方式运行。如果以时间片轮换方式运行,则每个线程获得同样的时间量;如果以优先级方式运行,则优先级高的线程将得到较多的时间量,而优先级低的线程只能得到较少的时间量。方式的选择主要取决于系统时间调度器的机制以及程序的实时性要求。现在,运用多线程技术就可以实现对多用户的支持。即在服务器端,使接收来自用户端的连接请求(accept)这步无限循环,每接收一个用户请求,产生两个线程(send和receive线程),用来管理服务器与该用户的通信任务。下面,运用共享数据结构技术,就可以实现本问所要解决的关键技术---服务器转发技术。1.3 共享数据结构技术同一进程中的多个线程共存于单一的线性地址空间,因此,在多线程间共享数据结构是非常容易且方便的。但必须注意的是,对数据结构的访问必须是多线程互斥的,否则数据任意更改将导致不可预料的结果。本文所阐述的服务器转发技术也就是通过共享数据结构实现线程间的互相通信。 二、实现方案整体方案的构思图如下:
通过上图,我们可以看到整个系统分为三个相关的程序,即注册/登陆服务器、通信服务器以及用户程序。其中,注册/登陆服务器负责用户的注册、登陆以及数据库管理;通信服务器负责完成数据转发以及共享数据结构的管理;用户端则完成注册、登陆和通信功能。为什么要把服务器分为两部分呢?主要是考虑到服务器的用户容量问题,以及对通信服务器的保护,只有在通过验证后,用户在能与通信服务器连接。由此可见,整个系统通信任务的实现还是很复杂的。用户端首先必须注册自己,等待注册成功;然后根据自己的注册信息进行服务器登陆,登陆成功后才能与通信服务器连接,进行用户间通信。注册/登陆服务器接收到用户端的信息后,首先判断是注册信息还是登陆信息。如果是注册信息,则将该数据按预定的格式写入数据库,然后返回注册成功的消息,期间有任何异常产生,服务器都会返回注册失败消息,提示用户重新注册;如果是登陆信息,则从数据中提取用户名和ID与数据库中的内容进行比较,如果该用户存在,则返回登陆成功消息,反之,返回登陆失败消息。通信服务器所完成的主要功能是数据转发,这是通过与图中的共享数据结构进行交互完成的。服务器接收到用户端发来的消息后,提取消息的一部分与共享数据结构存储的内容进行比较,确定所要转发的对象,最后通过多线程及其通信机制完成数据转发。 下面,我们将分三部分来讨论系统的具体实现过程。 三、具体实施
3.1 注册/登陆服务器注册/登陆服务器程序是基于对话框的,该程序使用I/O端口56789与用户端连接。
首先,在对话框初始化的同时完成网络初始化,即执行Init_net()函数,代码(不完整)如下:BOOL CServerDlg::Init_net()
{////////////////////////网络初始化///////////////////////////////
addrLen=sizeof(SOCKADDR_IN);
status=WSAStartup(MAKEWORD(1, 1), &Data);
………
memset(&serverSockAddr, 0, sizeof(serverSockAddr));/*以下指定一个与某个SOCKET连接本地或远程地址*/ serverSockAddr.sin_port=htons(PORT);
serverSockAddr.sin_family=AF_INET;
serverSockAddr.sin_addr.s_addr=htonl(INADDR_ANY);
serverSocket=socket(AF_INET, SOCK_STREAM, 0);//初始化SOCKET
……… status=bind(serverSocket,(LPSOCKADDR)&serverSockAddr,sizeof(serverSockAddr)); //将SOCKET与地址绑定
………
status=listen(serverSocket, 5); //开始监听
………
return true;
}
接着按下RUN键开始服务器功能,执行Reg_Load()函数,使服务器始终处于等待连接状态,但这样也使该线程始终阻塞。当有用户连接时,该函数创建一个任务用于处理与用户及数据库的事务。具体任务函数略(详见原始代码文件)。 void CServerDlg::Reg_Load()
{
while(1)
{
CWinThread* hHandle;
clientSocket=accept(serverSocket,(LPSOCKADDR)&clientSockAddr,&addrLen); //等待连接,阻塞
hHandle=AfxBeginThread(talkToClient,(LPVOID)clientSocket);//有连接时,创建任务
………
}
}
任务函数在接收到消息时,要对数据库进行操作,由于数据库较简单,采用ODBC连接ACCESS数据库(将netuser.mdb在ODBC数据管理器中安装成同名数据源)具体代码略。
3.2 通信服务器通信服务器是本程序实现的关键,它运用共享数据结构技术及多线程技术,通过I/O端口56790与用户端连接,实现了数据转发功能。首先,程序初始化网络Init_net(),接着当用户连接到服务器时,创建接收线程和发送线程,这样就可以实现数据转发。最后,当用户断开连接时,服务器关闭与他的连接,并结束相应的线程。下面我们来看一下本程序中的共享数据结构的具体内容与使用方法以及多线程的相关内容与实现。● 共享数据结构本程序的共享数据结构一共有两个,即socket_info和send_info。前者包含了所有登陆用户的一些基本资料,后者则包含了当前服务器接收到的用户端所发送的信息资料。详细内容及注释如下:struct socket_info
{
SOCKET s_client; //用户的SOCKET值
u_long client_addr; //用户网络地址
CString pet; //用户昵称
CWinThread* thread; //为该用户创建的发送线程对象的指针
};struct send_info
{
CString data; //用户端发送的数据内容(经过编辑)
CWinThread* thread; //需要发送数据的任务指针
};
在程序中,定义两个全局变量,用来在线程间共享: send_info info_data; CList<socket_info,socket_info&>s_info;
每当有用户连接到服务器,服务器就将用户端的一些信息以socket_info结构体的形式存入s_info列表中;而当服务器接收到用户端发送过来的数据时,就将数据格式化后存入结构体info_data,通过与结构体列表比较,确定需要恢复的发送线程(所有发送线程在创建时都被挂起)。这样,服务器就准确地转了发数据。 ●多线程
每当服务器上有用户连接成功,服务器都会为其创建两个线程:接收线程(RecvData)和发送线程(SendData),并且接收线程在创建后处于可执行状态,而发送线程则阻塞,等待服务器将其唤醒。这两个线程都执行一个无限循环的过程,只有当通信出现异常或用户端关闭连接时,线程才被自身所结束,并且,这两个线程一定是同时生成,同时结束的。很显然,每个连接产生两个线程,使得数据转发变的简单,但同时又使得服务器的任务加重。因此,用户端的连接数量有所限制,视服务器软、硬件能力而定。同时,由于多线程对结构体info_data都需要操作,所以线程间必须同步。这儿,我定义了互斥量CMutex m_mutex,用它的方法Lock()和Unlock()来完成同步。我们首先来看一下接收线程(RecvData):(不完整代码)UINT RecvData(void* cs)
{
SOCKET clientSocket=(SOCKET)cs;
while(1)
{
numrcv=recv(clientSocket, buffer, MAXBUFLEN, NO_FLAGS_SET);
buffer[numrcv]=''\0'';
if(strcmp(buffer,"Close!")!=0) //不是接收的“Close”数据
{
…………
for(i=0;i<count;i++)
{
if(po!=NULL)
{
s1=s_info.GetNext(po);
if(s1.pet.Compare(petname)==0) //比较昵称是否一样
{
m_mutex.Lock(); //互锁
info_d
int idx=0;
int nSocket=0;当新建立一个连接时(生成新的CSocket),将idx和nSocket都加一。accept时使用idx的编号关闭一个连接时nSocket减一。
也就是使用nSocket来限制总的连接数