C++网络编程之服务器编程

#include<iostream>
#include<WinSock2.h>
using namespace std;
#pragma comment(lib,"Ws2_32.lib")
const int nDefaultServerPort=10000;
const int buf_len=1024;
SOCKET AcceptConnection(SOCKET sdListen);
bool ProcessConnection(SOCKET sd);
bool ShutdownConnection(SOCKET sd);
SOCKET BindListen();
void DoWork();
int main()
{
	WSAData wsaData;
	int nCode;
	if((nCode=WSAStartup(MAKEWORD(2,2),&wsaData))!=0)
	{
		cout<<"WSAStartup error"<<nCode<<endl;
		return -1;
	}
	DoWork();
	WSACleanup();
	return 0;
}
void DoWork()
{
    //获取监听套接字并进入监听状态
	SOCKET sdListen=BindListen();
	if(sdListen==INVALID_SOCKET)
	{
		return;
	}
	//服务器主循环
	while(true)
	{
		SOCKET sd=AcceptConnection(sdListen);
		if(sd==INVALID_SOCKET)
		{
			break;
		}
		//第二阶段,服务一个客户端连接
		if(ProcessConnection(sd)==false)
		{
			break;
		}
		//第三阶段,关闭一个客户端连接
		if(ShutdownConnection(sd)==false)
		{
			break;
		}
		//关闭监听套接字
		if(closesocket(sdListen)==SOCKET_ERROR)
		{
			cout<<"closesocket error"<<WSAGetLastError()<<endl;
		}
	}
}
SOCKET BindListen()
{
	//创建一个监听套接字
	SOCKET sd=socket(AF_INET,SOCK_STREAM,0);
	if(sd==INVALID_SOCKET)
	{
		cout<<"socket error"<<WSAGetLastError()<<endl;
		return INVALID_SOCKET;
	}
	//填充本地套接字
	//这里使用了通配地址INADDR_ANY。当然也可以指明一个具体的本地IP地址,但是如果使用通配地址
	//我们可以接受来自所有网络接口的连接请求。这对于带有多个网卡的服务器来说可以简化编程
	sockaddr_in saListen;
	saListen.sin_family=AF_INET;
	saListen.sin_addr.s_addr=htonl(INADDR_ANY);
	saListen.sin_port=htons(nDefaultServerPort);
	//调用bind把本地套接字地址绑定到监听套接字
	if(bind(sd,(sockaddr*)&saListen,sizeof(sockaddr_in))==SOCKET_ERROR)
	{
		cout<<"bind error"<<WSAGetLastError()<<endl;
		closesocket(sd);
		return INVALID_SOCKET;
	}
	//开始监听
	if(listen(sd,5)==SOCKET_ERROR)
	{
		cout<<"listen error"<<WSAGetLastError()<<endl;
		closesocket(sd);
		return INVALID_SOCKET;
	}
	return sd;
}
//接受一个客户端连接并返回对应于该连接的套接字句柄
SOCKET AcceptConnection(SOCKET sdListen)
{
	sockaddr_in saRemote;
	int nSize=sizeof(sockaddr_in);
	SOCKET sd=accept(sdListen,(sockaddr*)&saRemote,&nSize);
	if(sd==INVALID_SOCKET)
	{
		cout<<"accept error"<<WSAGetLastError()<<endl;
	}
	return sd;
}
//服务一个客户端连接,实现回显服务业务逻辑
bool ProcessConnection(SOCKET sd)
{
	char buff[buf_len];
	int nRecv;
	//循环直到客户端关闭数据连接
	do{
		//接收客户端的数据
		//由于套接字sd是阻塞模式,对其调用recv将会阻塞,直到recv完成返回。当
		//recv返回0时,表明客户端完成数据发送并且关闭了连接,此时就可以退出循环
		nRecv=recv(sd,buff,buf_len,0);
		if(nRecv==SOCKET_ERROR)
		{
			cout<<"recv error"<<WSAGetLastError()<<endl;
			return false;
		}
		else if(nRecv>0)
		{
			int nSent=0;
			//把数据原封不动发回客户端,即回显
			while(nSent<nRecv)
			{
				//这里的send也会阻塞,只有当send返回后,程序才能继续执行
				int nTemp=send(sd,&buff[nSent],nRecv-nSent,0);
				if(nTemp>0)
				{
					nSent+=nTemp;
				}
				else if(nTemp==SOCKET_ERROR)
				{
					cout<<"send error"<<WSAGetLastError()<<endl;
					return false;
				}
				else
				{
					//send返回0,由于此时nSent<nRecv,也就是说还有数据没有发送出错,所以连接是被客户端意外关闭的
					cout<<"Connection closed unexpectedly by peer"<<endl;
					return true;
				}
			}
		}
	}while(nRecv!=0);
	cout<<"Connection closed by peer"<<endl;
	return true;
}
//安全关闭一个TCP连接
bool ShutdownConnection(SOCKET sd)
{
	//首先发送一个TCP FIN分段,向对方表明已经完成数据发送
	if(shutdown(sd,SD_SEND)==SOCKET_ERROR)
	{
		cout<<"shutdown error"<<WSAGetLastError()<<endl;
		return false;
	}
	char buff[buf_len];
	int nRecv;
	//继续接受对方的数据,直到recv返回0为止
	do{
	    nRecv=recv(sd,buff,buf_len,0);
		if(nRecv==SOCKET_ERROR)
		{
			cout<<"recv error"<<WSAGetLastError()<<endl;
			return false;
		}
		else if(nRecv>0)
		{
			cout<<nRecv<<"unexcepted bytes received"<<endl;
		}
	}while(nRecv!=0);
	if(closesocket(sd)==SOCKET_ERROR)
	{
		cout<<"closesocket error"<<WSAGetLastError()<<endl;
		return false;
	}
	return true;
}

原文地址:https://www.cnblogs.com/zztong/p/6695273.html