赞
踩
要用到Winsock2库里的WSAStartup函数,需要包含相应的头文件。
函数的返回值是一个整型,通过查看VS官方帮助文档,我们判断加载库是否成功。
- #include<winsock2.h>
- #pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"Ws2_32.lib")表示链接Ws2_32.lib这个库。
- //1、加载库
- WORD version = MAKEWORD(2, 2);
- WSADATA data = {};
- int err=WSAStartup(version,&data);
- if (0 != err) {
- cout << "WSAStartup fail" << endl;
- return 1;
- }
- else {
- cout << "WSAStartup success" << endl;
- }
- if (2 != HIBYTE(data.wVersion) || 2 != LOBYTE(data.wVersion)) {
- //虽然加载库成功了,但是版本加载不对
- cout << "version error" << endl;
- WSACleanup();
- return 1;
- }
- else {
- cout << "WSAStartup version success" << endl;
- }
创建套接字需要使用socket函数。函数的3个参数,分别代表 地址族,套接字类型,通讯协议
根据返回值是否为异常,来判断套接字创建是否成功。
若创建失败,需要卸载库,并终止运行。
- SOCKET WSAAPI socket(
- //VS官方帮助文档中的[in]表示输入的参数
- [in] int af,
- [in] int type,
- [in] int protocol
- );
- //2、创建套接字
- SOCKET sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if (INVALID_SOCKET != sock) {
- cout << "socket success" << endl;
- }
- else {
- cout << "socket fail" << WSAGetLastError() << endl;
- WSACleanup();
- return 1;
- }
绑定IP地址使用的函数是bind函数。函数的返回值是一个整型,根据函数的返回值来判断IP绑定是否成功,失败返回SOCKET_ERROR,此时不仅要卸载库,还要关闭套接字。
bind函数有三个参数,套接字,sockaddr结构体类型的指针,结构体大小
- int WSAAPI bind(
- [in] SOCKET s,
- [in] const sockaddr *name,
- [in] int namelen
- );
通过帮助文档查看sockaddr结构体,给出了sockaddr和sockaddr_in。为了方便设置端口号和IP,我们使用sockaddr_in,传入函数时,再强制类型转换将其地址转换为sockaddr*类型。
- typedef struct sockaddr {
- u_short sa_family;
- char sa_data[14];
- } SOCKADDR, *PSOCKADDR, *LPSOCKADDR;
- typedef struct sockaddr_in {
- short sin_family;
- u_short sin_port;
- struct in_addr sin_addr;
- char sin_zero[8];
- } SOCKADDR_IN, *PSOCKADDR_IN, *LPSOCKADDR_IN;
sockaddr_in结构体的成员有一个in_addr结构体,在帮助文档中搜索查看,发现结构体里面是个联合体。用于传入IP。
- struct in_addr {
- union {
- struct {
- u_char s_b1;
- u_char s_b2;
- u_char s_b3;
- u_char s_b4;
- } S_un_b;
- struct {
- u_short s_w1;
- u_short s_w2;
- } S_un_w;
- u_long S_addr;
- } S_un;
- };
- //3、绑定IP和端口号
- sockaddr_in sockServer;
- sockServer.sin_family = AF_INET;
- sockServer.sin_port = htons(9876); //设置端口号
- sockServer.sin_addr.S_un.S_addr = ADDR_ANY; //绑定任意所有IP
- err=bind(sock,(sockaddr*)&sockServer,sizeof(sockServer));
- if (SOCKET_ERROR != err) {
- cout << "bind success" << endl;
- }
- else
- {
- cout << "bind fail" << WSAGetLastError() << endl;
- //绑定失败,不仅要卸载库,还要关闭套接字
- closesocket(sock);
- WSACleanup();
- return 1;
- }
-
- char recvBuf[4096] = "";
- char sendBuf[4096] = "";
- sockaddr_in sockClient = {};
- int sockClientSize=sizeof(sockClient);
这里需要注意的是,我们在设置端口号时,需要转一下网络字节序。如果不转换的话,PC之间的客户端和服务端进行数据传输,由于都是小端存储,所以不会有影响。但是如果对端不是PC,可能就会有问题。
sockServer.sin_port = htons(9876); //设置端口号,并转换网络字节序
接收消息使用的是recvfrom函数。函数有6个参数,第一个参数是上面定义的套接字;第二个参数是输出参数,字符串类型,用来存放收到的消息;第三个参数是字符串长度;第四个参数是标志位(填0即可);第五个参数是输出参数,是sockaddr类型的结构体指针,表示消息的来源;第七个参数是消息来源结构体的长度的地址。
若能正常接收,则返回值>0;如果连接关闭,则返回0,如果接收异常返回错误信息。
在能正常收到消息的情况下,我们打印对端IP地址 和 用来存放消息的字符串即消息内容。这里我们使用 inet_ntoa函数将ulong型IP表示转换为十进制字符串四等分型IP表示。
- int WSAAPI recvfrom(
- [in] SOCKET s,
- [out] char *buf,
- [in] int len,
- [in] int flags,
- [out] sockaddr *from,
- [in, out, optional] int *fromlen
- );
- //4、接收数据
- recv=recvfrom(sock, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&sockClient, &sockClientSize);
- if (recv > 0)
- {
- //接收成功,输出client的ip和发送的内容
- //ip存放有两种方式,ulong型和十进制字符串四等分型
- //inet_ntoa();
- //inet_addr();
- cout << "server recv: " ;
- cout << "ip:" << inet_ntoa(sockClient.sin_addr) << " say:" << recvBuf << endl;
- }
- else if (recv == 0)
- {
- cout << "connection closed" << endl;
- }
- else {
- cout << "recvfrom error" << WSAGetLastError() << endl;
- break;
- }
发送消息使用的是sendto函数,函数有6个参数。第一个参数是上面定义的套接字,第二个参数是字符串,用来存放我们发送的内容,第三个参数是字符串的长度,第四个参数是标志位,填0即可;第五个参数是客户端的地址,第六个参数是客户端的大小
- int WSAAPI sendto(
- [in] SOCKET s,
- [in] const char *buf,
- [in] int len,
- [in] int flags,
- [in] const sockaddr *to,
- [in] int tolen
- );
- //5、发送数据
- cout << "server send: ";
- gets_s(sendBuf);
- sendto(sock, sendBuf, sizeof(sendBuf), 0, (sockaddr*)&sockClient, sockClientSize);
关闭我们上面创建的套接字,使用的是closesocket函数,传入的参数是套接字
- //6、关闭套接字
- closesocket(sock);
使用WSACleanup函数即可,没有参数。
- //7、卸载库
- WSACleanup();
- #include<iostream>
- #include<winsock2.h>
- #pragma comment(lib,"ws2_32.lib")
- using namespace std;
- int main() {
- //1、加载库
- WORD version = MAKEWORD(2, 2);
- WSADATA data = {};
- int err=WSAStartup(version,&data);
- if (0 != err) {
- cout << "WSAStartup fail" << endl;
- return 1;
- }
- else {
- cout << "WSAStartup success" << endl;
- }
- if (2 != HIBYTE(data.wVersion) || 2 != LOBYTE(data.wVersion)) {
- //虽然加载库成功了,但是版本加载不对
- cout << "version error" << endl;
- WSACleanup();
- return 1;
- }
- else {
- cout << "WSAStartup version success" << endl;
- }
- //2、创建套接字
- SOCKET sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if (INVALID_SOCKET != sock) {
- cout << "socket success" << endl;
- }
- else {
- cout << "socket fail" << WSAGetLastError() << endl;
- WSACleanup();
- return 1;
- }
- //3、绑定IP和端口号
- sockaddr_in sockServer;
- sockServer.sin_family = AF_INET;
- sockServer.sin_port = htons(9876); //设置端口号
- sockServer.sin_addr.S_un.S_addr = ADDR_ANY; //绑定任意所有IP
- err=bind(sock,(sockaddr*)&sockServer,sizeof(sockServer));
- if (SOCKET_ERROR != err) {
- cout << "bind success" << endl;
- }
- else
- {
- cout << "bind fail" << WSAGetLastError() << endl;
- //绑定失败,不仅要卸载库,还要关闭套接字
- closesocket(sock);
- WSACleanup();
- return 1;
- }
-
- char recvBuf[4096] = "";
- char sendBuf[4096] = "";
- sockaddr_in sockClient = {};
- int sockClientSize=sizeof(sockClient);
- cout << "-----Server init over-----" << endl;
- while (true)
- {
- //4、接收数据
- cout << "server recv: " ;
- int recv=recvfrom(sock, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&sockClient, &sockClientSize);
- if (recv > 0)
- {
- //接收成功,输出client的ip和发送的内容
- //ip存放有两种方式,ulong型和十进制字符串四等分型
- //inet_ntoa();
- cout << "ip:" << inet_ntoa(sockClient.sin_addr) << " say:" << recvBuf << endl;
- }
- else if (recv == 0)
- {
- cout << "connection closed" << endl;
- }
- else {
- cout << "recvfrom error" << WSAGetLastError() << endl;
- break;
- }
-
-
- //5、发送数据
- cout << "server send: ";
- gets_s(sendBuf);
- sendto(sock, sendBuf, sizeof(sendBuf), 0, (sockaddr*)&sockClient, sockClientSize);
- }
-
- //6、关闭套接字
- closesocket(sock);
- //7、卸载库
- WSACleanup();
- return 0;
- }
recvfrom阻塞函数,等待客户端发送消息。
与服务端不同的是,客户端不需要进行绑定IP。
但是这里,我们发送数据的IP不能是所有IP了,而是指定为服务端的IP。
- //3、配置IP和端口号
- sockaddr_in sockServer;
- sockServer.sin_family = AF_INET;
- sockServer.sin_port = htons(9876); //设置端口号
- sockServer.sin_addr.S_un.S_addr = inet_addr("192.168.3.108"); //客户端IP
其它部分与服务端基本相同。
客户端代码:
- #include<iostream>
- #include<winsock2.h>
- #pragma comment(lib,"ws2_32.lib")
- using namespace std;
- int main() {
- //1、加载库
- WORD version = MAKEWORD(2, 2);
- WSADATA data = {};
- int err = WSAStartup(version, &data);
- if (0 != err) {
- cout << "WSAStartup fail" << endl;
- return 1;
- }
- else {
- cout << "WSAStartup success" << endl;
- }
- if (2 != HIBYTE(data.wVersion) || 2 != LOBYTE(data.wVersion)) {
- //虽然加载库成功了,但是版本加载不对
- cout << "version error" << endl;
- WSACleanup();
- return 1;
- }
- else {
- cout << "WSAStartup version success" << endl;
- }
- //2、创建套接字
- SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if (INVALID_SOCKET != sock) {
- cout << "socket success" << endl;
- }
- else {
- cout << "socket fail" << WSAGetLastError() << endl;
- WSACleanup();
- return 1;
- }
- //3、配置IP和端口号
-
- //设置有限广播权限
- BOOL bval = TRUE;
- setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&bval, sizeof(bval));
-
- sockaddr_in sockServer;
- sockServer.sin_family = AF_INET;
- //sockServer.sin_port = htons(9876); //设置端口号
- //sockServer.sin_addr.S_un.S_addr = inet_addr("192.168.1.186"); //客户端IP
- sockServer.sin_port = htons(98765); //设置端口号
- sockServer.sin_addr.S_un.S_addr = inet_addr("255.255.255.255"); //客户端IP
-
- char recvBuf[4096] = "";
- char sendBuf[4096] = "";
- int recv = 0;
- int send = 0;
- cout << "-----Client init over-----" << endl;
- while (true)
- {
- //5、发送数据
- cout << "Client send: ";
- gets_s(sendBuf);
- send=sendto(sock, sendBuf, sizeof(sendBuf), 0, (sockaddr*)&sockServer, sizeof(sockServer));
- if (SOCKET_ERROR == send) {
- cout << "sendto fail" << endl;
- }
-
- }
-
- //6、关闭套接字
- closesocket(sock);
- //7、卸载库
- WSACleanup();
- return 0;
- }
让我们来看看通信成功的结果:
面向非连接,可以接收任意方发来的数据,可以是1对1,也可以是1对多(广播和组播)
数据报文的通讯方式,数据包不可拆分
传输效率高(相对TCP)
没有效验和检查,容易丢包,也可能会出现乱序。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。