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); //至此,结构体的发送与接收已经顺利结束了