socket怎样使得多个客户端连接到一个服务器啊?

u010020198 2013-07-17 06:47:08
现在勉强实现到一个客户端连接到服务器可以传输字符串,但是现在要求我至少可以让50个客户端连接上服务器。每个客户端需要我新建一个SOCKET对应,我用了下SOCKET soc[50]好像是不能这样用的,请问要怎么实现啊??刚刚学socket.还有能解释下socket传输数据的原理吗?为什么还需要有socketaddr_in这样的结构体存在,它是用来干嘛的?
...全文
11903 28 打赏 收藏 转发到动态 举报
写回复
用AI写文章
28 条回复
切换为时间正序
请发表友善的回复…
发表回复
aa68454650 2013-12-04
  • 打赏
  • 举报
回复
或者你看一下《精通Windows Sockets网络开发——基于Visual C++实现》这本书,里面讲得很详细
aa68454650 2013-12-04
  • 打赏
  • 举报
回复
还是我给出代码吧 首先初始化一个服务器的套接字,然后创建一个监听进程,监听每个客户端的连接,然后为每个客户端动态分配一个接收线程和发送线程

	int reVal;
	
	//初始化Windows Sockets DLL
	WSADATA  wsData;
	reVal = WSAStartup(MAKEWORD(2,2),&wsData);
	
	//创建套接字			
	sServer = socket(AF_INET, SOCK_STREAM, 0);
	if(INVALID_SOCKET== sServer)
		return FALSE;
	
	//设置套接字非阻塞模式
	unsigned long ul = 1;
	reVal = ioctlsocket(sServer, FIONBIO, (unsigned long*)&ul);
	if (SOCKET_ERROR == reVal)
		return FALSE;

	//绑定套接字
	sockaddr_in serAddr;	
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(SERVERPORT);
	serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
	reVal = bind(sServer, (struct sockaddr*)&serAddr, sizeof(serAddr));
	if(SOCKET_ERROR == reVal )
		return FALSE;	
	
	//监听
	reVal = listen(sServer, SOMAXCONN);
	if(SOCKET_ERROR == reVal)
		return FALSE;
        	bServerRunning = TRUE;//设置服务器为运行状态
	
	//创建释放资源线程
	unsigned long ulThreadId;
	hThreadHelp = CreateThread(NULL, 0, HelperThread, NULL, 0, &ulThreadId);
	if( NULL == hThreadHelp)
	{
		bServerRunning = FALSE;
		return FALSE;
	}else{
		CloseHandle(hThreadHelp);
	}

	//创建接收客户端请求线程
	hThreadAccept = CreateThread(NULL, 0, AcceptThread, NULL, 0, &ulThreadId);
	

/** 
 * 接受客户端连接
 */
DWORD __stdcall AcceptThread(void* pParam)
{
	SOCKET  sAccept;							//接受客户端连接的套接字
	sockaddr_in addrClient;						//客户端SOCKET地址

	for (;bServerRunning;)						//服务器的状态
	{
		memset(&addrClient, 0, sizeof(sockaddr_in));					//初始化
		int			lenClient = sizeof(sockaddr_in);					//地址长度
		sAccept = accept(sServer, (sockaddr*)&addrClient, &lenClient);	//接受客户请求
		
		if(INVALID_SOCKET == sAccept )
		{ 
			int nErrCode = WSAGetLastError();
			if(nErrCode == WSAEWOULDBLOCK)	//无法立即完成一个非阻挡性套接字操作
			{
				Sleep(TIMEFOR_THREAD_SLEEP);
				continue;//继续等待
			}else {
				return 0;//线程退出
			}
			
		}
		else//接受客户端的请求
		{
			CClient *pClient = new CClient(sAccept,addrClient);	//创建客户端对象			
			EnterCriticalSection(&csClientList);				//进入在临界区
			clientlist.push_back(pClient);						//加入链表
			LeaveCriticalSection(&csClientList);				//离开临界区
			
			pClient->StartRuning();								//为接受的客户端建立接收数据和发送数据线程			
		}		
	}

	return 0;//线程退出
}

class CClient
{
public:
	CClient(const SOCKET sClient,const sockaddr_in &addrClient);
	virtual ~CClient();

public:
	BOOL		StartRuning(void);					//创建发送和接收数据线程
	void		HandleData(const char* pExpr);		//计算表达式
	BOOL		IsConning(void){					//是否连接存在
				return m_bConning;
				} 
	void		DisConning(void){					//断开与客户端的连接
				m_bConning = FALSE;
				}
	BOOL		IsExit(void){						//接收和发送线程是否已经退出
				return m_bExit;
				}

public:
	static DWORD __stdcall	 RecvDataThread(void* pParam);		//接收客户端数据
	static DWORD __stdcall	 SendDataThread(void* pParam);		//向客户端发送数据

private:
	CClient();
private:
	SOCKET		m_socket;			//套接字
	sockaddr_in	m_addr;				//地址
	DATABUF		m_data;				//数据
	HANDLE		m_hEvent;			//事件对象
	HANDLE		m_hThreadSend;		//发送数据线程句柄
	HANDLE		m_hThreadRecv;		//接收数据线程句柄
	CRITICAL_SECTION m_cs;			//临界区对象
	BOOL		m_bConning;			//客户端连接状态
	BOOL		m_bExit;			//线程退出
};
#endif

CClient::CClient(const SOCKET sClient, const sockaddr_in &addrClient)
{
	//初始化变量
	m_hThreadRecv = NULL;
	m_hThreadSend = NULL;
	m_socket = sClient;
	m_addr = addrClient;
	m_bConning = FALSE;	
	m_bExit = FALSE;
	memset(m_data.buf, 0, MAX_NUM_BUF);

	//创建事件
	m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//手动设置信号状态,初始化为无信号状态

	//初始化临界区
	InitializeCriticalSection(&m_cs);
}
/*
 * 析构函数
 */
CClient::~CClient()
{
	closesocket(m_socket);			//关闭套接字
	m_socket = INVALID_SOCKET;		//套接字无效
	DeleteCriticalSection(&m_cs);	//释放临界区对象	
	CloseHandle(m_hEvent);			//释放事件对象
}

/*
 * 创建发送和接收数据线程
 */
BOOL CClient::StartRuning(void)
{
	m_bConning = TRUE;//设置连接状态

	//创建接收数据线程
	unsigned long ulThreadId;
	m_hThreadRecv = CreateThread(NULL, 0, RecvDataThread, this, 0, &ulThreadId);
	if(NULL == m_hThreadRecv)
	{
		return FALSE;
	}else{
		CloseHandle(m_hThreadRecv);
	}

	//创建接收客户端数据的线程
	m_hThreadSend =  CreateThread(NULL, 0, SendDataThread, this, 0, &ulThreadId);
	if(NULL == m_hThreadSend)
	{
		return FALSE;
	}else{
		CloseHandle(m_hThreadSend);
	}
	
	return TRUE;
}


/*
 * 接收客户端数据
 */
DWORD  CClient::RecvDataThread(void* pParam)
{
	CClient *pClient = (CClient*)pParam;	//客户端对象指针
	int		reVal;							//返回值
	char	temp[MAX_NUM_BUF];				//临时变量

	memset(temp, 0, MAX_NUM_BUF);
	
	for (;pClient->m_bConning;)				//连接状态
	{
		reVal = recv(pClient->m_socket, temp, MAX_NUM_BUF, 0);	//接收数据
		
		//处理错误返回值
		if (SOCKET_ERROR == reVal)
		{
			int nErrCode = WSAGetLastError();
			
			if ( WSAEWOULDBLOCK == nErrCode )	//接受数据缓冲区不可用
			{
				continue;						//继续循环
			}else if (WSAENETDOWN == nErrCode ||//客户端关闭了连接
					 WSAETIMEDOUT == nErrCode ||
					WSAECONNRESET == nErrCode )			
			{
				break;							//线程退出				
			}
		}
		
		//客户端关闭了连接
		if ( reVal == 0)	
		{
			break;
		}
	
		//收到数据
		if (reVal > HEADERLEN)			
		{		
			pClient->HandleData(temp);		//处理数据

			SetEvent(pClient->m_hEvent);	//通知发送数据线程

			memset(temp, 0, MAX_NUM_BUF);	//清空临时变量
		}
		
		Sleep(TIMEFOR_THREAD_CLIENT);		//线程睡眠
	}
	
	pClient->m_bConning = FALSE;			//与客户端的连接断开
	SetEvent(pClient->m_hEvent);			//通知发送数据线程退出
	
	return 0;								//线程退出
}

/*
 * //向客户端发送数据
 */
DWORD CClient::SendDataThread(void* pParam)	
{
	CClient *pClient = (CClient*)pParam;//转换数据类型为CClient指针

	for (;pClient->m_bConning;)//连接状态
	{	
		//收到事件通知
		if (WAIT_OBJECT_0 == WaitForSingleObject(pClient->m_hEvent, INFINITE))
		{
			//当客户端的连接断开时,接收数据线程先退出,然后该线程后退出,并设置退出标志
			if (!pClient->m_bConning)
			{
				pClient->m_bExit = TRUE;
				break ;
			}

			//进入临界区
			EnterCriticalSection(&pClient->m_cs);		
			//发送数据
			phdr pHeader = (phdr)pClient->m_data.buf;
			int nSendlen = pHeader->len;

			int val = send(pClient->m_socket, pClient->m_data.buf, nSendlen,0);
			//处理返回错误
			if (SOCKET_ERROR == val)
			{
				int nErrCode = WSAGetLastError();
				if (nErrCode == WSAEWOULDBLOCK)//发送数据缓冲区不可用
				{
					continue;
				}else if ( WSAENETDOWN == nErrCode || 
						  WSAETIMEDOUT == nErrCode ||
						  WSAECONNRESET == nErrCode)//客户端关闭了连接
				{
					//离开临界区
					LeaveCriticalSection(&pClient->m_cs);
					pClient->m_bConning = FALSE;	//连接断开
					pClient->m_bExit = TRUE;		//线程退出
					break;			
				}else {
					//离开临界区
					LeaveCriticalSection(&pClient->m_cs);
					pClient->m_bConning = FALSE;	//连接断开
					pClient->m_bExit = TRUE;		//线程退出
					break;
				}				
			}
			//成功发送数据
			//离开临界区
			LeaveCriticalSection(&pClient->m_cs);
			//设置事件为无信号状态
			ResetEvent(&pClient->m_hEvent);	
		}	
		
	}

	return 0;
}
/*
 *  计算表达式,打包数据
 */
void CClient::HandleData(const char* pExpr)	
{
	memset(m_data.buf, 0, MAX_NUM_BUF);//清空m_data
	
    //如果是“byebye”或者“Byebye”
	if (BYEBYE == ((phdr)pExpr)->type)		
	{
		EnterCriticalSection(&m_cs);
		phdr pHeaderSend = (phdr)m_data.buf;				//发送的数据		
		pHeaderSend->type = BYEBYE;							//单词类型
		pHeaderSend->len = HEADERLEN + strlen("OK");		//数据包长度
		memcpy(m_data.buf + HEADERLEN, "OK", strlen("OK"));	//复制数据到m_data"
		LeaveCriticalSection(&m_cs);
		
	}else{//算数表达式
		
		int nFirNum;		//第一个数字
		int nSecNum;		//第二个数字
		char cOper;			//算数运算符
		int nResult;		//计算结果
		//格式化读入数据
		sscanf(pExpr + HEADERLEN, "%d%c%d", &nFirNum, &cOper, &nSecNum);
		
		//计算
		switch(cOper)
		{
		case '+'://加
			{
				nResult = nFirNum + nSecNum;
				break;
			}
		case '-'://减
			{
				nResult = nFirNum - nSecNum;
				break;
			}
		case '*'://乘
			{
				nResult = nFirNum * nSecNum;				
				break;
			}
		case '/'://除
			{
				if (ZERO == nSecNum)//无效的数字
				{
					nResult = INVALID_NUM;
				}else
				{
					nResult = nFirNum / nSecNum;	
				}				
				break;
			}
		default:
			nResult = INVALID_OPERATOR;//无效操作符
			break;			
		}
		
		//将算数表达式和计算的结果写入字符数组中
		char temp[MAX_NUM_BUF];
		char cEqu = '=';
		sprintf(temp, "%d%c%d%c%d",nFirNum, cOper, nSecNum,cEqu, nResult);

		//打包数据
		EnterCriticalSection(&m_cs);
		phdr pHeaderSend = (phdr)m_data.buf;				//发送的数据		
		pHeaderSend->type = EXPRESSION;						//数据类型为算数表达式
		pHeaderSend->len = HEADERLEN + strlen(temp);		//数据包的长度
		memcpy(m_data.buf + HEADERLEN, temp, strlen(temp));	//复制数据到m_data
		LeaveCriticalSection(&m_cs);
		
	}
}
yiyefangzhou24 2013-07-19
  • 打赏
  • 举报
回复
引用 25 楼 u010020198 的回复:
[quote=引用 22 楼 yiyefangzhou24 的回复:] 楼主你该好好看看socket的模型,或者你直接根据自己的需要实现模型http://blog.csdn.net/normalnotebook/article/details/999840
我是前两天才接触SOCKET的,select可以做到一个线程处理多个客户端吗?求指教 [/quote]那你结什么贴?
yiyefangzhou24 2013-07-18
  • 打赏
  • 举报
回复
楼主你该好好看看socket的模型,或者你直接根据自己的需要实现模型http://blog.csdn.net/normalnotebook/article/details/999840
yiyefangzhou24 2013-07-18
  • 打赏
  • 举报
回复
引用 20 楼 yiyefangzhou24 的回复:
引用 楼主 u010020198 的回复:
现在勉强实现到一个客户端连接到服务器可以传输字符串,但是现在要求我至少可以让50个客户端连接上服务器。每个客户端需要我新建一个SOCKET对应,我用了下SOCKET soc[50]好像是不能这样用的,请问要怎么实现啊??刚刚学socket.还有能解释下socket传输数据的原理吗?为什么还需要有socketaddr_in这样的结构体存在,它是用来干嘛的?
你错了,可以用的
socket有种东西叫socket模型,你根据需要选择相应的模型 或者可以像你说的那样保存句柄在一个数组或者链表中,有点麻烦的是数组列表的维护,需要设定一个刷新线程
yiyefangzhou24 2013-07-18
  • 打赏
  • 举报
回复
引用 楼主 u010020198 的回复:
现在勉强实现到一个客户端连接到服务器可以传输字符串,但是现在要求我至少可以让50个客户端连接上服务器。每个客户端需要我新建一个SOCKET对应,我用了下SOCKET soc[50]好像是不能这样用的,请问要怎么实现啊??刚刚学socket.还有能解释下socket传输数据的原理吗?为什么还需要有socketaddr_in这样的结构体存在,它是用来干嘛的?
你错了,可以用的
u010020198 2013-07-18
  • 打赏
  • 举报
回复
引用 16 楼 u010020198 的回复:
[quote=引用 15 楼 u010020198 的回复:] [quote=引用 13 楼 yanasdf789 的回复:] [quote=引用 9 楼 u010020198 的回复:] [quote=引用 6 楼 yanasdf789 的回复:] 每个socket 开辟个线程
我问的是怎么产生50个socket???我知道可以为每个socket新建个线程 [/quote] 获取每个实例化socket的句柄,然后保存到数组立[/quote] 这个可以试试,明晚回来告诉你结果[/quote] 其实我也想过既然socket是一个unsigned int 类型的,我可不可以建立一个unsigned int 的数组来直接当做50个socket来用呢?[/quote] 测试过了,可以……谢谢
u010020198 2013-07-18
  • 打赏
  • 举报
回复
引用 17 楼 lcmzgy 的回复:
楼主是写的是什么代码,如果是VC/MFC的话,使用异步套接字吧,CAsyncSocket类,你可以去继承这个类,分别CAsyClient,CAsySrv,CAsyStream(CAsyncSrv创建的用来和客户端通信的),CAsyStream创建的时候 使用指针 CAsyStream *ps=new CAsyStream ()这样可以防止局部变量导致的问题。 如果不想使用mfc类,用windows提供的异步机制一样可以实现,不过会比较麻烦一些
谢谢,虽然我不怎么懂你说的是什么类……不过已经解决了,虽然是比较坑爹。开了四个客户端往服务器上传一个130m的txt文件,平均9s,不知道会不会太慢,我再想想怎么优化吧
lcmzgy 2013-07-18
  • 打赏
  • 举报
回复
楼主是写的是什么代码,如果是VC/MFC的话,使用异步套接字吧,CAsyncSocket类,你可以去继承这个类,分别CAsyClient,CAsySrv,CAsyStream(CAsyncSrv创建的用来和客户端通信的),CAsyStream创建的时候 使用指针 CAsyStream *ps=new CAsyStream ()这样可以防止局部变量导致的问题。 如果不想使用mfc类,用windows提供的异步机制一样可以实现,不过会比较麻烦一些
u010020198 2013-07-18
  • 打赏
  • 举报
回复
引用 22 楼 yiyefangzhou24 的回复:
楼主你该好好看看socket的模型,或者你直接根据自己的需要实现模型http://blog.csdn.net/normalnotebook/article/details/999840
我是前两天才接触SOCKET的,select可以做到一个线程处理多个客户端吗?求指教
u010020198 2013-07-18
  • 打赏
  • 举报
回复
引用 23 楼 stubble 的回复:
多线程可以吧
可以,但是现在上面又要求不能一客户一线程,说用select解决,可是我看了select好像不行啊
异常异长 2013-07-18
  • 打赏
  • 举报
回复
多线程可以吧
u010020198 2013-07-17
  • 打赏
  • 举报
回复
引用 15 楼 u010020198 的回复:
[quote=引用 13 楼 yanasdf789 的回复:] [quote=引用 9 楼 u010020198 的回复:] [quote=引用 6 楼 yanasdf789 的回复:] 每个socket 开辟个线程
我问的是怎么产生50个socket???我知道可以为每个socket新建个线程 [/quote] 获取每个实例化socket的句柄,然后保存到数组立[/quote] 这个可以试试,明晚回来告诉你结果[/quote] 其实我也想过既然socket是一个unsigned int 类型的,我可不可以建立一个unsigned int 的数组来直接当做50个socket来用呢?
u010020198 2013-07-17
  • 打赏
  • 举报
回复
引用 13 楼 yanasdf789 的回复:
[quote=引用 9 楼 u010020198 的回复:] [quote=引用 6 楼 yanasdf789 的回复:] 每个socket 开辟个线程
我问的是怎么产生50个socket???我知道可以为每个socket新建个线程 [/quote] 获取每个实例化socket的句柄,然后保存到数组立[/quote] 这个可以试试,明晚回来告诉你结果
u010020198 2013-07-17
  • 打赏
  • 举报
回复
引用 12 楼 cfjtaishan 的回复:
就像服务器建立了50个通道,服务器利用这50个通道与客户端分别通信。 select或者线程都可以解决这个问题,不过要建立一个50个socket还真没遇到过。
你要50个通道不是应该建立50个socket吗?当然可能有些socket建立后,客户端结束连接,socket又能重复利用的这种不算,我指的是同时有50个客户端连上服务器
yanasdf789 2013-07-17
  • 打赏
  • 举报
回复
引用 9 楼 u010020198 的回复:
[quote=引用 6 楼 yanasdf789 的回复:] 每个socket 开辟个线程
我问的是怎么产生50个socket???我知道可以为每个socket新建个线程 [/quote] 获取每个实例化socket的句柄,然后保存到数组立
自信男孩 2013-07-17
  • 打赏
  • 举报
回复
就像服务器建立了50个通道,服务器利用这50个通道与客户端分别通信。 select或者线程都可以解决这个问题,不过要建立一个50个socket还真没遇到过。
大尾巴猫 2013-07-17
  • 打赏
  • 举报
回复
去找本windows网络编程第2版,再下载那本书的附加的源代码研究下。
u010020198 2013-07-17
  • 打赏
  • 举报
回复
引用 5 楼 bl520025 的回复:
用IO 模型 selsect 什么什么的。
不怎么懂select ,只是网上找资料的时候看到下,去研究下看行不行
u010020198 2013-07-17
  • 打赏
  • 举报
回复
引用 6 楼 yanasdf789 的回复:
每个socket 开辟个线程
我问的是怎么产生50个socket???我知道可以为每个socket新建个线程
加载更多回复(8)

65,179

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧