socket网络编程之TCP_Client

#include <Winsock2.h>
#include <stdio.h>
#include <windows.h>
#include <string.h>

DWORD WINAPI RecvProc(
  LPVOID lpParameter   // thread data
);


BOOL g_bRun;

void main()
{
	WORD wVersionRequseted;
	WSADATA wsaData;
	int err;
	wVersionRequseted = MAKEWORD(1, 1);
	err = WSAStartup(wVersionRequseted, &wsaData);
	if(err!=0)
	{
		return;
	}
	if(LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) !=1)
	{
		WSACleanup();
		return;
	}

	SOCKET socketClt = socket(AF_INET, SOCK_STREAM, 0);
	SOCKADDR_IN addrClt;
	addrClt.sin_addr.S_un.S_addr = inet_addr("172.20.33.82");
	addrClt.sin_family = AF_INET;
	addrClt.sin_port = htons(5000);
	
	int i=	connect(socketClt, (SOCKADDR *)&addrClt, sizeof(SOCKADDR));
	if(i==0)
	{
		printf("connect sucess!\n");
		g_bRun = TRUE;
	}
	else
	{
		printf("connect error!");
		return;
	}

	HANDLE hRecvThread;

	hRecvThread=CreateThread(NULL,0,RecvProc,(LPVOID)socketClt,0,NULL);

	char ch;
	while(1)
	{

		ch = getchar();
		if(ch=='q')
		{
			g_bRun=FALSE;
			break;
		}
	}
	
}

DWORD WINAPI RecvProc(
  LPVOID lpParameter   // thread data
)
{
	char recvBuf[512];
	int len;
	memset(recvBuf,0 ,512);
	FILE *fp = fopen("file.txt", "w");
	SOCKET sockClient =(SOCKET)lpParameter;

	while(g_bRun)
	{
		len=recv(sockClient, recvBuf, 512,0);
		if(len==SOCKET_ERROR)
		{
			int errCode=GetLastError();
			printf("链接错误:%d\n",errCode);
			break;
		}else if(len==0)
		{
			printf("服务器断开\n");
			break;
		}
		for(int i=0;i<len;i++)  
		{
			fprintf(fp,"%d\t",recvBuf[i]); //save data
		}
		printf("%d\n",len);
	}
	fclose(fp);
	printf("线程终止\n");
	return 0;
}

    在数据接收线程中,recv函数阻塞当前线程等待数据到来,返回实际copy的字节数(copy表示TCP/IP协议将数据拷贝到socketClient指定的缓冲区中),如果在copy的时候出现网络错误(比如被其它应用程序中断),那么它将返回SOCKET_ERROR(-1),这时可调用GetLastError()查找错误原因,如果服务器“优雅”的中断了链接,那么它将返回0。

    如果socketClient接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待(阻塞),直到协议把数据接收完毕,当数据接收完毕,recv就把socketClient接收缓存区的数据copy到recvBuf中。

注意:

1. 协议接收到的数据可能大于recvBuf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的。

(对于winsock, AFD.SYS驱动负责缓冲区的管理,也就是说,当一个程序调用send或WSASend函数发送数据的时候,数据被复制到AFD.SYS的内部缓冲里(大小根据SO_SNDBUF设置),然后send和WSASend立刻返回。之后数据由AFD.SYS负责发送到网络上,与应用程序无关。当然,如果应用程序希望发送比SO_SNDBUF设置的缓冲区还大的数据,WSASend函数将会被堵塞,直到所有数据均被发送完毕为止。同样,当从远地客户端接受数据的时候,如果应用程序没有提交receive请求,而且线上数据没有超出SO_RCVBUF设置的缓冲大小,那么AFD.SYS就把网络上的数据复制到自己的内部缓冲保存。当应用程序调用recv或WSARecv函数的时候,数据即从AFD.SYS的缓冲复制到应用程序提供的缓冲区里。)

2. tcp是一种流式的协议,发送方在发送一个固定长度数据时,可能被拆成多个包发送,因此接收方就需要进行多次接收,直到接收完整。比如发送500字节数据,可能发送的时候就被拆成100,200,200发送,接收的时候就要读取3次。

3. WIndows默认的socket缓存区大小为8K,如果要重新设置缓冲区大小可调用以下函数,但不建议修改:

int setsockopt(  SOCKET s,
                 int level,                              // SOL_SOCKET
                 int optname,                            // SO_RCVBUF or SO_SNDBUF
                 const char* optval,
                 int optlen
               );

示例代码如下所示(注意须在connect函数之前设置):

int optVal = 1024*20; // 20K
int optLen = sizeof(int);
setsockopt(sockSrv, SOL_SOCKET, SO_RCVBUF, (char *)&optVal, optLen);

如果确实有大量数据发送,需注意send函数的返回值(实际发送出去的字节数),可参看以下代码(来源于网络):

char *Buf;
#define BUF_LEN 10*1024*1024//10MB
Buf = new char[BUF_LEN];//此时Buf当中都是随机数据,实际需要的话,自己填充

int iNeedSend = BUF_LEN;
char *p = Buf;
while(iNeedSend){
  int iRet = send(sock,p,iNeedSend,0);
  if(iRet <= 0) return;//小于0表示出错,等于0表示断开
  p += iRet;
  iNeedSend -= iRet;
}

delete[] Buf;


Ps:在实际应用中,服务器端以固定的速率(大概200kB/s)发送128字节数据,接收的时候发现被拆包,大部分时候是以112和16接收。最终数据保存完整,没有出现丢包现象。

如果双方传输速率比较高的情况下,参考下面解决方案(来源于网络):

作为一个套接字,它拥有两个缓冲,接收数据缓冲和发送数据缓冲(此缓冲不同与你自己定义的缓冲),当有数据到达时,首先进入的就是接收数据缓冲,然后用户从这个缓冲中将数据读出来,这就是套接字接受的过程,这个缓冲的大小可以自己用SetSocketOpt()设定,同时操作系统对它有一个默认大小,如果对方在很短时间内发送大量数据到达这个套接子时,可能它没来得及接收完,因此接收缓冲处于满的状态,再有数据来的时候就进不去了,因此对方的 SEND可能就返回错误,在对方发送的数据量很小时不会出现这种情况,当数据量很大时,情况就很明显了,很容易造成收不到的情况。同样,发送方的发送缓冲也有相对应的问题。解决这个问题的办法有几种:  
1. 利用SetSocketOpt()函数将接收方套接子接收缓冲设为足够大小;  
2. 发送方进行数据发送时判断发送是否成功,如果不成功重发;  
3. 要求接收方收到数据后给发送方回应,发送方只在收到回应后才发送下一条数据。







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值