SOCKET DUP 使用心得

1、客户端绑定端口的问题

对于UDP,客户端不需要调用BIND绑定本机地址和端口,即便是绑定了本机地址,服务器也不能从这个绑定的端口发送过来信息,因为到路由器就会被丢弃,本机是根本接不到的,那客户端如何接收服务器端的信息呢,这就是服务器端recvfrom()的作用了,服务器端在接收的时候,最后两个参数就是客户端OS给临时分配的一个sockaddr结构体,里面包含要通信的客户端地址跟端口信息,对于发送到这个地址和端口的消息,路由是不拦的,对于为什么上一个拦,而这个又不拦呢,这是因为上一个消息是不请自来的,路由会将其当成攻击消息,将其丢弃,而下一个则是客户端先发送的,而后服务器跟据这个端口发送的消息则会被路由器当成回复消息,而不会拦截。

2、客户端使用创建线程来接收来自服务器的信息

平时我们一般会将接收信息的while(true)超级循环独自封装到一个线程中,但有个问题要注意一下,就是,在客户端由于不能像服务器那样运行,也就说,在客户端是不能接收随便发过来的信息的,所以如果我们在recvfrom()之前,没有提前调用sendto()的话,程序会报错!在运行到recvfrom()时,一直报10022的错误,即参数不对或第一个参数SOCKET没配置正确,这个错误是很难找到的,它的主要原因就是recvfrom()了一个不确定的地址和端口,即sockaddr_in参数不对,虽然写得是服务器的地址和端口,但它并不认识。这里我写了个小例子,VC++ 2008 控制台应用程序。大家可以下载

下面只贴出部分代码:

这个是客户端的接收线程:

DWORD WINAPI RecvServerThreadProc(LPVOID lpParameter){
	char recvBuf[128];
	memset(recvBuf,0,128);
	while(true){
		if(SOCKET_ERROR==recvfrom(sserver,recvBuf,128,0,(sockaddr*)&Server_addr_in,&sockaddr_len)){
			int lastError=WSAGetLastError();
			printf("recv error;%d",lastError);
		}else{
			char* ipSvr = inet_ntoa(Server_addr_in.sin_addr);  
			printf("%s said: %s/n",ipSvr,recvBuf);  
		}
	}
}
_tmain()里的创建线程代码:

//创建服务器消息接收线程  
	string str="登陆成功!";
	strcpy(sendBuf,str.c_str());
	if(SOCKET_ERROR ==sendto(sserver,sendBuf,strlen(sendBuf)+1,0,(sockaddr*)&Server_addr_in,sockaddr_len)){
		printf("send error!");   //这里先向SERVER发送信息,再再创建线程,这样才不会报错
	}else{
		printf("send success");
	}
	HANDLE threadhandle = CreateThread(NULL, 0, RecvServerThreadProc, NULL, 0, NULL);
	CloseHandle(threadhandle);//释放句柄资源,放弃对句柄的引用
完整源码下载地址:

http://download.csdn.net/detail/harvic880925/5300759

3、recvfrom()/sendto()最后一个参数fromlen/tolen的值

recvfrom有一个容易出错的地方,就是关于from地址的长度,在调用时这个长度必须是结构体struct sockaddr的长度,因为在recvfrom的实现中将会用到这个值。

对于recvform:

int len = sizeof(sockaddr);
recvfrom(xxxx,xxxx,xxx,0,(sockaddr*)&addr_in,&len);
对于sendto:

int len = sizeof(sockaddr);
sendto(xxx,xxx,xxx,0,(sockaddr*)&addr_in,len)
4、网络字节序绑定转换总结

本机字节顺序与网络字节顺序的转换

htons------"host to network short"
htonl   -------"host to network long"
ntohs -------"network to host short"        //
用于转换端口
ntohl   -------"network to host long"

Sockaddr_in相关

//初始化IP地址:

Sockaddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//这个一般用过初始化服务器监听地址
Sockaddr.sin_addr.s_addr =inet_addr("222.195.151.20");//这个一般用于初始化客户端发送时的服务器的IP地址

//初始化端口

Sockaddr.sin_port= htons(600000);

5、sendto()消息使用结构体

注意:使用这种方法能转换成功的关键点在于:

1、使用一字节对齐

2、结构体的各成员的大小是固定的,这里不能使用不固定大小的成员参数如:string info;这样在转换的时候会出错,要么乱码,要么没有信息,出错的原因在于,发送的信息与接收的信息无法实现字节对齐!!!!

定义的结构体如下:

struct send_info
{
char info_from[20]; //发送者ID
char info_to[20]; //接收者ID
int info_length; //发送的消息主体的长度
char info_content[1024]; //消息主体
};
发送端主要代码(为了简洁说明问题,我把用户输入的内容、长度等验证的代码去掉了):

struct send_info info1; //定义结构体变量
printf("This is client,please input message:");
//从键盘读取用户输入的数据,并写入info1.info_content
memset(info1.info_content,0,sizeof(info1.info_content));//清空缓存
info1.info_length=read(STDIN_FILENO,info1.info_content,1024) - 1;//读取用户输入的数据
  
memset(snd_buf,0,1024);//清空发送缓存,不清空的话可能导致接收时产生乱码,
//或者如果本次发送的内容少于上次的话,snd_buf中会包含有上次的内容
  
memcpy(snd_buf,&info1,sizeof(info1)); //结构体转换成字符串
send(connect_fd,snd_buf,sizeof(snd_buf),0);//发送信息
接收端主要代码:

struct send_info clt; //定义结构体变量
  
memset(recv_buf,'z',1024);//清空缓存
recv(fd,recv_buf,1024,0 );//读取数据
  
memset(&clt,0,sizeof(clt));//清空结构体
memcpy(&clt,recv_buf,sizeof(clt));//把接收到的信息转换成结构体
  
clt.info_content[clt.info_length]='';
//消息内容结束,没有这句的话,可能导致消息乱码或输出异常
//有网友建议说传递的结构体中尽量不要有string类型的字段,估计就是串尾符定位的问题
  
if(clt.info_content) //判断接收内容并输出
printf("nclt.info_from is %snclt.info_to is %snclt.info_content is%snclt.info_length is %dn",clt.info_from,clt.info_to,clt.info_content,clt.info_length);
//至此,结构体的发送与接收已经顺利结束了



你可能感兴趣的:(SOCKET DUP 使用心得)