好久没弄VS的UDP通信了,使用方式都忘记的差不多了。今天遇到了正好学习一下。
UDP理论:
UDP参考链接:
socket函数
为了执行网络输入输出,一个进程必须做的第一件事就是调用socket函数获得一个文件描述符。
#include <sys/socket.h>
int socket(int family,int type,int protocol);
返回:非负描述字---成功 -1---失败
- 第一个参数指明了协议簇,目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议);
- 第二个参数指明套接口类型,有三种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和SOCK_RAW(原始套接口);
- 如果套接口类型不是原始套接口,那么第三个参数就为0。
bind函数
为套接口分配一个本地IP和协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合;如指定端口为0,调用bind时内核将选择一个临时端口,如果指定一个通配IP地址,则要等到建立连接后内核才选择一个本地IP地址。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr * server, socklen_t addrlen);
返回:0---成功 -1---失败
- 第一个参数是socket函数返回的套接口描述字;
- 第二和第第三个参数分别是一个指向特定于协议的地址结构的指针和该地址结构的长度。
recvfrom函数
UDP使用recvfrom()函数接收数据,他类似于标准的read(),但是在recvfrom()函数中要指明目的地址。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr * from, size_t *addrlen);
返回接收到数据的长度---成功 -1---失败
- 前三个参数等同于函数read()的前三个参数
- flags参数是传输控制标志。
- 最后两个参数类似于accept的最后两个参数。
sendto函数
UDP使用sendto()函数发送数据,他类似于标准的write(),但是在sendto()函数中要指明目的地址。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr * to, int addrlen);
返回发送数据的长度---成功 -1---失败
- 前三个参数等同于函数read()的前三个参数
- flags参数是传输控制标志。
- 参数to指明数据将发往的协议地址
- 他的大小由addrlen参数来指定
文章转载:(博主写的已经很详细了,为了防止丢失在这边重新保存一遍)
UDP通信模型:
vs 实现udp通信
服务器端编程的步骤
- 创建套接字(socket)
- 将套接字和IP地址、端口号绑定在一起(bind)
- 等待客户端发起数据通信(recvfrom/recvto)
- 关闭套接字
客户端编程的步骤
- 创建套接字(socket)
- 向服务器发起通信(recvfrom/recvto)
- 关闭套接字
知识点:
在vs中一般使用Winsock2实现网络通信功能,所以需要引进头文件winsock2.h和库文件"ws2_32.lib"。
-
WinSock2 是连接系统和用户使用的软件之间用于交流的一个接口,这个功能就是修复软件与系统正确的通讯的作用。
-
Winsock2 SPI(Service Provider Interface)服务提供者接口建立在Windows开放系统架构WOSA(Windows Open System Architecture)之上,是Winsock系统组件提供的面向系统底层的编程接口。
-
Winsock系统组件向上面向用户应用程序提供一个标准的API接口;向下在Winsock组件和Winsock服务提供者(比如TCP/IP协议栈)之间提供一个标准的SPI接口。
-
各种服务提供者是Windows支持的DLL,挂载在Winsock2 的Ws2_32.dll模块下。
-
对用户应用程序使用的Winsock2 API中定义的许多内部函数来说,这些服务提供者都提供了它们的对应的运作方式(例如API函数WSAConnect有相应的SPI函数WSPConnect)。
-
多数情况下,一个应用程序在调用Winsock2 API函数时,Ws2_32.dll会调用相应的Winsock2 SPI函数,利用特定的服务提供者执行所请求的服务。
pragma comment(lib,“Ws2_32.lib”)
作用:
- 表示链接Ws2_32.lib这个库,和在工程设置里写上链入Ws2_32.lib的效果一样,不过这种方法写的程序别人在使用你的代码的时候就不用再设置工程settings了。
- 告诉连接器连接的时候要找ws2_32.lib,这样你就不用在linker的lib设置里指定这个lib了。
- ws2_32.lib是winsock2的库文件。
- WinSock2就相当于连接系统和你使用的软件之间交流的一个接口,可能这个功能就是修复软件与系统正确的通讯的作用。
WASDATA
一种数据结构。这个结构被用来存储被WSAStartup函数调用后返回的Windows Sockets数据。它包含Winsock.dll执行的数据。
结构原型:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
#ifdef _WIN64
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
#else
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
#endif
} WSADATA, FAR * LPWSADATA;
MAKEWORD(a, b):声明调用不同的Winsock版本
- makeword是将两个byte型合并成一个word型,一个在高8位(b),一个在低8位(a)
- 返回值:一个无符号16位整形数。
- MAKEWORD(1,1)和MAKEWORD(2,2)的区别在于:前者只能一次接收一次,不能马上发送,而后者能。
- MAKEWORD(2,2)就是调用2.2版,MAKEWORD(1,1)就是调用1.1版。
- 1.1版只支持TCP/IP协议,而2.0版可以支持多协议。
- 2.0版有良好的向后兼容性,任何使用1.1版的源代码、二进制文件、应用程序都可以不加修改地在2.0规范下使用。
WSAStartup(sockVersion, &wsadata)
- WSAStartup,即WSA(Windows Sockets Asynchronous,Windows异步套接字)的启动命令。是Windows下的网络编程接口软件Winsock1 或 Winsock2 里面的一个命令。
- WSAStartup必须是应用程序或DLL调用的第一个Windows Sockets函数。它允许应用程序或DLL指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节。应用程序或DLL只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数。
sockaddr结构
truct sockaddr
{
unsigned short sa_family;
char sa_data[14];
} ;
sa_family: 是地址家族,一般都是“AF_xxx”的形式。通常大多用的是都是AF_INET,代表TCP/IP协议族。
sa_data: 是14字节协议地址。
- 此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构(在WinSock2.h中定义)
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
sin_family指代协议族,在socket编程中只能是AF_INET
sin_port存储端口号(使用网络字节顺序),在linux下,端口号的范围0~65535,同时0~1024范围的端口号已经被系统使用或保留。
sin_addr存储IP地址,使用in_addr这个数据结构
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
- sockaddr_in和sockaddr是并列的结构,指向sockaddr_in的结构体的指针也可以指向sockaddr的结构体,并代替它。也就是说,你可以使用sockaddr_in建立你所需要的信息,然后用memset函数初始化:
- memset((char)&mysock,0,sizeof(mysock))
c_str
- c_str是Borland封装的String类中的一个函数,它返回当前字符串的首字符地址。当需要打开一个由用户自己输入文件名的文件时,可以这样写:ifstream in(st.c_str())。
- c_str()返回的是一个分配给const char的地址,其内容已设定为不可变更,如果再把此地址赋给一个可以变更内容的char变量,就会产生冲突。但是如果放入函数调用,或者直接输出,因为这些函数和输出都是把字符串指针作为 const char*引用的,所以不会有问题。
- c_str() 以const char* 类型返回 string 内含的字符串。如果一个函数要求char*参数,可以使用c_str()方法:
string s = "Hello World!";
printf("%s", s.c_str());
- c_str在打开文件时的用处:
当需要打开一个由用户自己输入文件名的文件时,可以这样写:ifstream in(st.c_str());。其中st是string类型,存放的即为用户输入的文件名。
memset
- void *memset(void *s, int ch, size_t n);
- 将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
- 在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法 。
recvfrom
recvfrom(
_In_ SOCKET s,
_Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
_In_ int len,
_In_ int flags,
_Out_writes_bytes_to_opt_(*fromlen, *fromlen) struct sockaddr FAR * from,
_Inout_opt_ int FAR * fromlen
);
sendto
WSAAPI
sendto(
_In_ SOCKET s,
_In_reads_bytes_(len) const char FAR * buf,
_In_ int len,
_In_ int flags,
_In_reads_bytes_(tolen) const struct sockaddr FAR * to,
_In_ int tolen
);
示例代码
#include "pch.h"
#include <iostream>
#include <WinSock2.h>
#include<WS2tcpip.h>
#include<string>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main() {
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsadata;
if (WSAStartup(sockVersion, &wsadata)) {
printf("WSAStartup failed \n");
return 0;
}
SOCKET hServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (hServer == INVALID_SOCKET) {
printf("socket failed \n");
return 0;
}
sockaddr_in addrServer;
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(8889);
addrServer.sin_addr.S_un.S_addr = INADDR_ANY;
int nRet = bind(hServer, (sockaddr*)&addrServer, sizeof(addrServer));
if (nRet == SOCKET_ERROR) {
printf("socket bind failed\n");
closesocket(hServer);
WSACleanup();
return 0;
}
sockaddr_in addrClient;
int nlen = sizeof(addrClient);
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
int irecv;
int isend;
while (true) {
irecv = recvfrom(hServer, buffer, sizeof(buffer), 0, (SOCKADDR*)&addrClient, &nlen);
if (irecv > 0) {
if (! (strcmp(buffer,"byebye"))) {
cout << "ClientA: " << buffer << endl;
cout << "close connection··· " << endl;
closesocket(hServer);
WSACleanup();
cout << "5s后关闭控制它。" << endl;
Sleep(5000);
return 0;
}
else {
cout << " ClientA:" << buffer<< endl;
}
}
else {
cout << "recvFrom failed " << endl;
closesocket(hServer);
WSACleanup();
cout << "5s后关闭控制台。" << endl;
Sleep(5000);
return 0;
}
memset(buffer, 0, sizeof(buffer));
cout << "Server:";
cin >> buffer;
isend=sendto(hServer, buffer, strlen(buffer), 0, (SOCKADDR*)&addrClient, nlen);
if (isend == SOCKET_ERROR) {
cout << "sendto failed " << endl;
closesocket(hServer);
WSACleanup();
cout << "5s后关闭控制台。" << endl;
Sleep(5000);
return 0;
}
memset(buffer, 0, sizeof(buffer));
}
closesocket(hServer);
WSACleanup();
return 0;
}
#include"pch.h"
#include<iostream>
#include<WinSock2.h>
#include <string>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main() {
WSADATA wsadata;
WORD sockVersion = MAKEWORD(2, 2);
SOCKET sClient;
if (WSAStartup(sockVersion, &wsadata) != 0) {
printf("WSAStartup failed \n");
return 0;
}
sClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (SOCKET_ERROR == sClient) {
printf("socket failed !\n");
return 0;
}
sockaddr_in serverAddr;
sockaddr_in clientAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8889);
serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int slen = sizeof(serverAddr);
int clen = sizeof(clientAddr);
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
int iSend = 0;
int iRcv = 0;
cout << "开始主动与服务器建立通信:" << endl;
while (true) {
cout << "Client: ";
cin >> buffer;
iSend=sendto(sClient, buffer, strlen(buffer), 0, (SOCKADDR*)&serverAddr, slen);
if (iSend== SOCKET_ERROR) {
cout<<"sendto failed "<<endl;
closesocket(sClient);
WSACleanup();
cout << "5s后关闭控制台。" << endl;
Sleep(5000);
return 0;
}
if (!(strcmp(buffer, "byebye"))) {
cout << "close connection " << endl;
closesocket(sClient);
WSACleanup();
cout << "5s后关闭控制它。" << endl;
Sleep(5000);
return 0;
}
memset(buffer, 0, sizeof(buffer));
iRcv= recvfrom(sClient, buffer, sizeof(buffer), 0, (SOCKADDR*)&clientAddr,&clen);
if (iRcv == SOCKET_ERROR) {
cout << "recvFrom failed " << endl;
closesocket(sClient);
WSACleanup();
cout << "5s后关闭控制台。" << endl;
Sleep(5000);
return 0;
}
if (iRcv <= 0) {
cout<< "server disconnected··· " << endl;
closesocket(sClient);
WSACleanup();
cout << "5s后关闭控制台。" << endl;
Sleep(5000);
return 0;
}
else {
cout << " Server: " << buffer << endl;
}
memset(buffer, 0, sizeof(buffer));
}
closesocket(sClient);
WSACleanup();
return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)