[Win32]服务器端程序

服务器端程序流程

服务器端的功能在指定的端口上监听,等待客户端的连接。在连接建立后可使用send()、recv()发送、接收数据。一般情况下,socket程序服务端过程如下

  1. 程序在运行后,首先需要调用WSAStartup()加载ws2_32.dll
  2. 调用socket()创建用于监听的SOCKET,在创建时需要指定使用的网络协议,连接类型等
  3. 调用bind()将SOCKET绑定到网络地址和端口
  4. 调用listen()开始监听
  5. 调用accept()等待客户端连接。在客户端连接后,accept()返回,得到连接SOCKET。在accept()返回后,可立即在调用,以处理其他客户端的连接
  6. 得到连接SCOKET后,可调用send()和recv()发生接收数据
  7. 在数据传输完成后,可调用closesocket()关闭SOCKET
  8. 调用WSACleanup()释放ws2_32.dll

面向连接的应用程序流程图[Win32]服务器端程序_第1张图片

服务器程序

新建win32项目控制台程序 Win32Server项目:

// Win32Server.cpp : 定义控制台应用程序的入口点。 

#include "stdafx.h"
#include <WinSock2.h>
#include <ws2tcpip.h>

#pragma comment(lib,"wsock32.lib")
#pragma comment(lib,"ws2_32.lib") 
#define DEFPORT "10000"
#define DEFPORTTWO  10000
//处理接收的消息
void HandleMsg(SOCKET sockfd,char* msg)
{
	int nSend=0;
	char sendBuf[2048]={0}; 
	if (lstrcmpiA(msg,"download") == 0)
	{ 		
		strcpy(sendBuf,"we get downLoad\n");
	}
	else if (lstrcmpiA(msg,"get information") == 0)
	{ 
		strcpy(sendBuf,"we get information!!!!\n");
	}
	nSend=send(sockfd,sendBuf,strlen(sendBuf),0);
	if (nSend == SOCKET_ERROR)
	{
		printf("error at send(),threadID=%d, errno=%d\n",sockfd,WSAGetLastError());
		closesocket(sockfd);
	}
	printf("sockID=%d,send client [%d]bytes----%s",sockfd,nSend,sendBuf); 
}
//连接线程
DWORD WINAPI ConnectionThread(LPVOID lpParam)
{
	DWORD dwThreadID=GetCurrentProcessId();
	SOCKET sockfd=(SOCKET)lpParam;
	int recvByte=0;
	char recvBuf[2096];
	recvByte=recv(sockfd,recvBuf,strlen(recvBuf)+1,0);
	if (recvByte == 0)//接收数据失败,连接关闭
	{
		printf("接收数据失败,连接关闭!!!");
		closesocket(sockfd);
		return 0;
	}
	else if (recvByte == SOCKET_ERROR)//接收数据失败,socket错误
	{
		printf("error at recv,erron=%d\n",WSAGetLastError());
		closesocket(sockfd);
		return 0;
	}
	else if (recvByte > 0)
	{
		printf("ConnectionThread(%d),[%d] Bytes received:%s\n",dwThreadID,recvByte,recvBuf);
		HandleMsg(sockfd,recvBuf);
	}
	closesocket(sockfd);
	return 0;
}
void main()
{
	WSADATA data;
	if(WSAStartup(MAKEWORD(2,2),&data) != NO_ERROR)
	{
		printf("error at WSAStartup,errno=%d\n",GetLastError());
		return;
	} 
	SOCKET listenSocket=INVALID_SOCKET;
	if((listenSocket=socket(AF_INET,SOCK_STREAM,0)) == INVALID_SOCKET)
	{
		printf("error at socket(),errno=%d\n",WSAGetLastError());
		WSACleanup();
		return;
	}
	printf("socket successfully!!!\n");
	 
// 	SOCKADDR_IN addr;
// 	addr.sin_family=AF_INET;
// 	addr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
// 	addr.sin_port=htons(DEFPORTTWO);
// 	if (bind(listenSocket,(SOCKADDR*)&addr,sizeof(addr)) == SOCKET_ERROR)
// 	{
// 		printf("error at bind(),errno=%d\n",WSAGetLastError()); 
// 		closesocket(listenSocket);
// 		WSACleanup();
// 		return;
// 	}
//等价于下面的代码
          
	addrinfo *result=NULL,hints;
	ZeroMemory(&hints,sizeof(hints));
	SOCKADDR_IN dd;
	hints.ai_family=AF_INET;
	hints.ai_socktype=SOCK_STREAM;
	hints.ai_socktype=0;
	hints.ai_flags=AI_PASSIVE;
	if(getaddrinfo(NULL,DEFPORT,&hints,&result) != 0) 
	{
		printf("error at getaddrinfo\n");
		closesocket(listenSocket);
		WSACleanup();
		return;
	}

	if (bind(listenSocket,result->ai_addr,result->ai_addrlen) == SOCKET_ERROR)
	{
		printf("error at bind(),errno=%d\n",WSAGetLastError());
		freeaddrinfo(result);
		closesocket(listenSocket);
		WSACleanup();
		return;
	}
	freeaddrinfo(result);//result不再需要
	printf("bind successfully!!!\n");

	if (listen(listenSocket,SOMAXCONN) == SOCKET_ERROR)
	{
		printf("error at listen(),errno=%d\n",WSAGetLastError()); 
		closesocket(listenSocket);
		WSACleanup();
		return;
	}
	printf("listen successfully!!!\n");

	SOCKET clientSocket=INVALID_SOCKET;
	SOCKADDR_IN clientAddr;
	int clientAddrLen=sizeof(clientAddr);
	while(1)
	{
		printf("ready to accept\n");
		clientSocket=accept(listenSocket,(SOCKADDR*)&clientAddr,&clientAddrLen);
		if(clientSocket == INVALID_SOCKET)
		{
			printf("error at accept(),errno=%d\n",WSAGetLastError());
			closesocket(listenSocket);
			break;//等待连接错误,退出循环
		}
		//printf("accept a connetion[%d.%d.%d.%d]:%d,sockfd=%d\n\n\n\n",clientAddr.sin_addr.s_net,clientAddr.sin_addr.s_host,clientAddr.sin_addr.s_lh,clientAddr.sin_addr.s_impno,ntohs(clientAddr.sin_port),clientSocket);//等价于下一行
		printf("accept a connetion[%s]:%d,sockfd=%d\n\n\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),clientSocket);
		//为每一个连接创建一个数据发送的接收线程,使服务端又可以立即接收其他的客户端连接
		if (!CreateThread(NULL,0,ConnectionThread,(LPVOID)clientSocket,0,NULL))
		{
			printf("create thread error(%d)\n",GetLastError());
			break;
		}
	}
	WSACleanup();
}

函数解析

 /*
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
bind()函数把一个地址族中的特定地址赋给socket。例如,对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
int bind(SOCKET s,sockaddr * name,int namelen);
参 数: s:Socket对象名,它通过socket()创建了,唯一标识一个socket
name:服务器地址信息名称(包含信息有:地址协议族,服务器本机的IP,要监听的端口)
namelen:name的长度
返回值:成功返回0,否则返回SOCKET_ERROR
通常服务器在启动的时候会绑定一个众说周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它(ip+port)来连接服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不用调用,而是在connect()时由系统随机生成一个。

int listen(SOCKET s,int backlog);
参 数: s:一个已绑定的套接字
backlog:连接请求队列的最大长度(一般2~4,用SOMAXCONN则有系统确定)。socket可以排队的最大连接个数,不是最多可以连接几个客户端。更具体些:TCP模块允许的已完成三次握手过程(TCP模块完成)但还没来得及被应用程序accep()的最大连接数。
返回值:成功返回0,否则返回SOCKET_ERROR
socket()函数创建的socket默认是一个主动类型的,listen()函数则将主动连接套接口socket变为被动连接套接口,使得这个进程可以接受其他进程的请求(客户的连接请求),从而成为一个服务器进程。

SOCKET accept(SOCKET s,_output struct sockaddr * addr,_output int * addrlen);
参数:s:监听套接字,
addr:存放来连接的客户端的地址和端口,(若客户端使用了bind()来绑定客户端本地的IP和Port,则服务器端会得到客户端bind的端口,而不是服务器端自动分配的端口)。。   当我们调用socket()创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。若对客户端的IP地址不感兴趣,则可以设为NULL
addrlen:addr的长度,sizeof(SOCKADDR_IN),当addr为NULL时,addrlen则可以为NULL
返回值:功返回一个新产生的Socket对象,否则返回INVALID_SOCKET
accept()默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字就是连接套接字。
监听套接字:监听套接字正如accept()的参数sockfd,它就是监听套接字,在调用listen()函数之后。
连接套接字:一个套接字会从主动连接的套接字变为一个监听套接字;而accept()返回的是已连接socket描述字(一个连接套接字),它代表一个网络已经存在的点点连接。
一个服务器通常仅仅只创建一个监听socket描述符,它在该服务器的生命周期内一直存在。内核为每个有服务器进程接收的客户端连接创建了一个已连接的socket描述符,当服务器完成了对某个客户的服务后,相应的已连接socket描述符就被关闭。
连接套接字并没有占有新的端口与客户端通信,依然使用的是监听套接字一样的端口号。
*/ 
运行结果: [Win32]服务器端程序_第2张图片
问题:当有多个客户端并发连接服务器端时,上程序会怎样,能处理吗?答案是不能的;需要添加一个监听线程。 看下一篇博文

你可能感兴趣的:([Win32]服务器端程序)