UDP通信程序设计(学习记录)
因为作为新手还是不太清楚,稍稍记录一下遇到的情况。
我使用的是Windows平台。
首先需要编写udp的通信程序,就要了解其需要用到什么函数。
一般来说,在Windows上用winsock编写udp程序需要使用winsock2.h来创建套接字,并实现收发等功能。
然而一开始发现编译并不能通过,发现是没在链接器里加上-lws2_32的语句。加上,这样就可以链接ws2_32.lib库了。
但在创建套接字之前,需要调用winsock的库。不然会返回错误。
通过WSAStartup()函数对Windows Sockets API初始化。
每个winsock应用必须加载winsock DLL的相应版本,如MAKEWORD(2,2)表示winsock的2.2版本,也是无连接类型所必须的。
当然在程序退出前应该调用函数WSAcleanup()释放使用。
WSADATA wdata; if(WSAStartup(MAKEWORD(2,2),&wdata)!=0){ cout<<"网络环境初始化失败"<<endl; return -1; }
调用库后,便可以用如下函数创建套接字了。
SOCKET socket(int domain, int type, int protocol)
SOCKET sS=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); if(sS==INVALID_SOCKET){ printf("Failed socket() %d .\n",WSAGetLastError()); return -1; }
对于现在所需要的通信方式,需要将其传输层协议指定为UDP,其中第二个参数代表套接字类型,我们设为
SOCK_DGRAM表示无连接的数据报方式(如UDP)
与之相对的是
SOCK_STREAM表示基于连接的字节流方式(如TCP)
第一个参数表示协议簇,第三个参数表示协议号。
对于服务器,我们需要把本地的IP地址和端口号绑定到刚刚创建的套接字上。我们使用
int bind(SOCKET socket, struct sockaddr * address, int addr_len)函数;在这之前,我们需要把相应的数据放入sockaddr_in中才能对socket进行绑定。
SOCKADDR_IN si; si.sin_family=AF_INET; si.sin_port=htons(PORT); si.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
其中port是端口,需要我们自行设置,而地址则可以使用
INADDR_ANY来进行绑定,
INADDR_ANY表示0.0.0.0,可以表示本机所有的地址。
绑定完成后,就要创建监听接收部分了。
个人这里用到一个
int recvfrom(SOCKET s,void *buf,int len,unsigned int flags, struct sockaddr *from,int *fromlen)函数用来监听。
参数5、6不是必须的参数,但接收这些信息可以为下面的应答提供地址。
收到信息后,服务器需要应答客户端的请求,于是又用到了一个
int sendto ( socket s , const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen)函数,其整体结构和recvfrom相似。
这样,服务器的基本功能就完成了。
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<winsock2.h> #include<time.h> using namespace std; #define MSD(X,t) memset(X,t,sizeof(X)) #define PORT 8080 int main(){ int cnt=0; WSADATA wdata; if(WSAStartup(MAKEWORD(2,2),&wdata)!=0){ cout<<"网络环境初始化失败"<<endl; return -1; } SOCKET sS=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); if(sS==INVALID_SOCKET){ printf("Failed socket() %d .\n",WSAGetLastError()); return -1; }SOCKADDR_IN si; si.sin_family=AF_INET; si.sin_port=htons(PORT); si.sin_addr.S_un.S_addr=htonl(INADDR_ANY); if(bind(sS,(sockaddr *)&si,sizeof(sockaddr)) == -1){ cout<<"bind()error."<<endl; return -1; } cout<<"现在服务器在端口 8080 运行, 等待中. \n"<<endl; char szbuf[10]; MSD(szbuf,0); while(1){ SOCKADDR_IN sC; memset(&sC, 0, sizeof(sockaddr)); int sCLen = sizeof(sockaddr); int ret = recvfrom(sS, szbuf, sizeof(szbuf), 0, (sockaddr *) &sC, &sCLen); if(ret == -1){ printf("接收失败!"); return -1; }cnt++; printf("No.%d 收到信息:%s 从 IP[%s],端口[%d]\n", cnt, szbuf, inet_ntoa(sC.sin_addr), ntohs(sC.sin_port)); sendto(sS,"拟好!",sizeof("拟好!"), 0, (sockaddr *)&sC, sCLen); printf("回复至 IP[%s],端口[%d]\n", inet_ntoa(sC.sin_addr), ntohs(sC.sin_port)); } closesocket(sS); WSACleanup(); return 0; }
对于UDP的客户端,我们并不需要对套接字进行绑定了,只需要创建好套接字地址,就能直接用sendto()对服务器进行数据发送了。同时发送完毕后也会监听服务器的回应。
但是单只是这样,万一服务器回传丢包,那么客户端就因为阻塞函数recvfrom的原因不会停止了。
那么我们可以用
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)为套接字设置一个超时。
那么recvfrom就会返回一个变化的时间,我们就可以停下没有意义的监听了。
在关闭前,我们需要关闭套接字和WSA库。以免造成资源浪费。
实际上UDP也是能用connect()函数的,不过本人对这部分了解不足,具体的运作并不清楚。
因为调整源代码改地址和数据太过麻烦,稍稍往客户端添加了一些内容。
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<string> #include<winsock2.h> #include<time.h> #include<errno.h> #include<sys/types.h> using namespace std; #define MSD(X,t) memset(X,t,sizeof(X)) #pragma comment (lib,"ws2_32.lib") #define PORT 8080 int main(){ WSADATA wdata; if(WSAStartup(MAKEWORD(2,2),&wdata)!=0){ cout<<"网络环境初始化失败"<<endl; return -1; } puts("输入地址:"); string s2; cin>>s2; char*s3=new char [s2.length()+1]; s2.copy(s3,s2.length(),0); s3[s2.length()]='\0'; SOCKADDR_IN si; si.sin_family=AF_INET; si.sin_port=htons(PORT); si.sin_addr.S_un.S_addr=inet_addr(s3); int n; string s1; printf("请输入发送内容:\n"); cin>>s1; puts("个数?:"); scanf("%d",&n); char* szbuff=new char[s1.length()+1]; s1.copy(szbuff,s1.length(),0); szbuff[s1.length()]='\0'; SOCKET sC=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); if(sC==INVALID_SOCKET){ printf("Failed socket() %d .\n",WSAGetLastError()); return -1; } int nNetTimeout=20;//20ms recv timeout setsockopt(sC,SOL_SOCKET,SO_RCVTIMEO,(char*)&nNetTimeout,sizeof(int));//获知端口不可达 int cnt=0; for(int i=1;i<=n;i++){ int dwSend = sendto(sC, szbuff, sizeof(szbuff), 0, (sockaddr *)&si, sizeof(sockaddr)); if(dwSend<=0){ cout<<"发送失败"<<endl; cnt++; } } printf("成功发送数据包:\"%s\" %d个\n", szbuff, n-cnt); char szRbuff[4096]; MSD(szRbuff,0); SOCKADDR_IN addrS = {0}; int addrSLen = sizeof(sockaddr); for(int i=1;i<=n-cnt;i++){ int timeout=recvfrom(sC, szRbuff, sizeof(sockaddr), 0, (sockaddr *)&addrS, &addrSLen); if(timeout<=0) printf("timeout:\"%s\"%d\n", inet_ntoa(addrS.sin_addr),i); else printf("从服务器[%s]收到信息:%s\n", inet_ntoa(addrS.sin_addr), szRbuff); } closesocket(sC); WSACleanup(); system("pause"); return 0; }
那么对UDP通信程序的记录暂时到这里吧,
- Linux学习笔记之---基于UDP的通信程序设计
- VC中UDP通信中记录对方的IP地址和端口号
- ActiveMQ 学习记录 之 两种基本通信方式
- [Javascript 高级程序设计]学习心得记录11 js的BOM
- 【MPI学习5】MPI并行程序设计模式:组通信MPI程序设计
- QT学习过程及简单串口和UDP通信demo开发过程及代码分享
- Java再学习—TCP/UDP通信原理
- [Javascript 高级程序设计]学习心得记录9 js面向对象
- 【MPI学习3】MPI并行程序设计模式:不同通信模式MPI并行程序的设计
- [Javascript 高级程序设计]学习心得记录3 根据对象数组的属性进行排序
- 程序必备基础知识学习:通信协议——Http、TCP、UDP
- UDP网络通信的程序设计
- UNIX环境高级编程学习之第十六章网络IPC:套接字 - 简单UDP Socket 通信
- 【MPI学习7】MPI并行程序设计模式:MPI的进程组和通信域
- java学习之路——基于UDP的Socket网络通信实例
- 记录学习点滴-《Windows 程序设计》-3-1
- 网络基础学习笔记二------UDP通信之服务器端
- [Javascript 高级程序设计]学习心得记录8 引用类型(下)
- 安卓学习日志(2)UDP通信
- Linux程序设计学习笔记----网络通信编程API及其示例应用