WinSocket编程(C++)之迭代回声服务器端/客户端(上)

创建解决方案,添加ws2_32.lib就不说了,详情参考WinSocket编程(C++)实例一

迭代回声服务器端/客户端:

  • 服务器端在同一时刻只与一个客户端相连, 并提供回声服务。
  • 服务器端依次向5个客户端提供服务并退出。
  • 客户端接收用户输人的字符串并发送到服务器端。
  • 服务器端将接收的字符串数据传回客户端,即“回声”。
  • 服务器端与客户端之间的字符串回声一直执行到客户端输人Q为止。

话不多说,直接上代码:

echo_server_win.cpp

#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "winsock2.h"

#define BUF_SIZE 1024
void ErrorHandling(char *message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hServSock,hClntSock;
	char message[BUF_SIZE];
	int strLen,i;
	SOCKADDR_IN servAdr,clntAdr;
	int clntAdrSize;

	if(argc!=2)
	{
		printf("Usage:%s\n",argv[0]);
		exit(1);
	}

	if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
		ErrorHandling("WSAStartup() error!");

	hServSock=socket(PF_INET,SOCK_STREAM,0);
	if(hServSock==INVALID_SOCKET)
		ErrorHandling("socket() error!");

	memset (&servAdr,0,sizeof(servAdr));
	servAdr.sin_family=AF_INET;
	servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
	servAdr.sin_port=htons(atoi(argv[1]));

	if (bind(hServSock,(SOCKADDR*)&servAdr,sizeof(servAdr))==SOCKET_ERROR)
		ErrorHandling("bind() error!");

	if (listen(hServSock,5)==SOCKET_ERROR)
		ErrorHandling("listin() error");

	clntAdrSize=sizeof(clntAdr);

	for(i=0;i<5;i++)
	{
		hClntSock=accept(hServSock,(SOCKADDR*)&clntAdr,&clntAdrSize);
		if(hClntSock==-1)
			ErrorHandling("accept() error");
		else
			printf("Connected client %d \n",i+1);

		while((strLen=recv(hClntSock,message,BUF_SIZE,0))!=0)
			send(hClntSock,message,strLen,0);

		closesocket(hClntSock);
	}
	closesocket(hServSock);
	WSACleanup();
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}

echo_client_win.cpp

#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "winsock2.h"

#define BUF_SIZE 1024
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	char message[BUF_SIZE];
	int strLen;
	SOCKADDR_IN servAdr;

	if(argc!=3)
	{
		printf("Usage:%s  \n",argv[0]);
		exit(1);
	}

	if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
		ErrorHandling("WSAStartup() error!");

	hSocket=socket(PF_INET,SOCK_STREAM,0);
	if(hSocket==INVALID_SOCKET)
		ErrorHandling("socket() error");

	memset(&servAdr,0,sizeof(servAdr));
	servAdr.sin_family=AF_INET;
	servAdr.sin_addr.s_addr=inet_addr(argv[1]);
	servAdr.sin_port=htons(atoi(argv[2]));

	if (connect(hSocket,(SOCKADDR*)&servAdr,sizeof(servAdr))==SOCKET_ERROR) 
		ErrorHandling("connect() error!");
	else 
		puts("Connected........");

	while(1)
	{
		fputs("Input message (Q to quit):",stdout);
		fgets(message,BUF_SIZE,stdin);

		if(!strcmp(message,"q\n")||!strcmp(message,"Q\n"))
			break;

		send(hSocket,message,strlen(message),0);
		strLen=recv(hSocket,message,BUF_SIZE-1,0);
		message[strLen]=0;
		printf("Message from server:%s",message);
	}
	closesocket(hSocket);
	WSACleanup();
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}

回声客户端存在的问题

下列是echo_ client.cpp的代码。

		send(hSocket,message,strlen(message),0);
		strLen=recv(hSocket,message,BUF_SIZE-1,0);
		message[strLen]=0;
		printf("Message from server:%s",message);

以上代码有个错误假设:

“每次调用recv、send函数时都会以字符串为单位执行实际的I/O操作。’

当然,每次调用send函数都会传递1个字符串,因此这种假设在某种程度上也算合理。但大
家还记得“TCP不存在数据边界”吗?上述客户端是基于TCP的,因此,多次调用send函数传递的字符串有可能一次性传递到服务器端。此时客户端有可能从服务器端收到多个字
符串,这不是我们希望看到的结果。还需考虑服务器端的如下情况:

“字符串太长,需要分2个数据包发送!”

服务器端希望通过调用1次write函数传输数据,但如果数据太大,操作系统就有可能把数据
分成多个数据包发送到客户端。另外,在此过程中,客户端有可能在尚未收到全部数据包时就调用read函数。

所有这些问题都源自TCP的数据传输特性。那该如何解决呢?请继续阅读下文

“但上述示例不是正常运转了吗?”

当然,我们的回声服务器端/客户端给出的结果是正确的。但这只是运气好罢了!只是因为
收发的数据小,而且运行环境为同一台计算机或相邻的两台计算机,所以没发生错误,可实际上仍存在发生错误的可能。

回声服务器端没有问题,只有回声客户端有问题?

问题不在服务器端,而在客户端。但只看代码也许不太好理解,因为I/O中使用了相同的函
数。先回顾一下回声服务器端的I/O相关代码,下面是echo_ server.cpp中的代码。

while((str_ len = read(c1nt_ sock, message, BUF_ SIZE)) != e)
	write(c1nt_ sock, message, str_ _len);

接着回顾回声客户端代码,下面是echo cient.cpp中的代码。

write(sock, message, strlen(message));
str_ len = read(sock, message, BUF_ SIZE - 1);

二者都在循环调用read或write函数。实际上之前的回声客户端将0接收自己传输的数据,
不过接收数据时的单位有些问题。扩展客户端代码回颐范围,下面是echo_client.cpp中的代码。

while(1)
	{
		fputs("Input message (Q to quit):",stdout);
		fgets(message,BUF_SIZE,stdin);

		if(!strcmp(message,"q\n")||!strcmp(message,"Q\n"))
			break;

		send(hSocket,message,strlen(message),0);
		strLen=recv(hSocket,message,BUF_SIZE-1,0);
		message[strLen]=0;
		printf("Message from server:%s",message);
	}

大家现在理解了吧?回声客户端传输的是字符串,而且是通过调用write函数-次性发送的。
之后还调用一次read函数,期待着接收自己传输的字符串。这就是问题所在。

“既然回声客户端会收到所有字符串数据,是否只需多等一会儿?过一段时间后再
调用read函数是否可以一次性读取所有字符串数据?”

的确,过一段时间后即可接收,但需要等多久?要等10分钟吗?这不符合常理,理想的客户
端应在收到字符串数据时立即读取并输出。

回声客户端问题解决方法

#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "winsock2.h"

//#include "unistd.h"
//#include "arpa/inet.h"
//#include "sys/socket.h"
#define BUF_SIZE 1024
void ErrorHandling(char* message);


int main(int argc, char* argv[])
{
	int sock;
	WSADATA wsaData;
	int write,read,close;
	char message[BUF_SIZE];
	int str_len,recv_len,recv_cnt;
	struct sockaddr_in serv_adr;

	if(argc!=3)
	{
		printf("Usage:%s  \n",argv[0]);
		exit(1);
	}

	if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
		ErrorHandling("WSAStartup() error!");

	sock=socket(PF_INET,SOCK_STREAM,0);
	if(sock==-1)
		ErrorHandling("socket() error");

	memset(&serv_adr,0,sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));

	if (connect(sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr))==-1) 
		ErrorHandling("connect() error!");
	else 
		puts("Connected........");
	while(1)
	{
		fputs("Input message (Q to quit):",stdout);
		fgets(message,BUF_SIZE,stdin);

		if(!strcmp(message,"q\n")||!strcmp(message,"Q\n"))
			break;

		str_len=send(sock,message,strlen(message),0);
		
		recv_len=0;
		while (recv_len

示例:
WinSocket编程(C++)之迭代回声服务器端/客户端(上)_第1张图片

虽然服务器可以接收两个客户端的请求,但是只能一个一个服务,暂时还不能实现同时接受请求。

你可能感兴趣的:(网络编程)