当前位置:   article > 正文

【计算机网络】基于UDP的服务器端和客户端

【计算机网络】基于UDP的服务器端和客户端

服务器端:

加载库

要用到Winsock2库里的WSAStartup函数,需要包含相应的头文件。

函数的返回值是一个整型,通过查看VS官方帮助文档,我们判断加载库是否成功。

  1. #include<winsock2.h>
  2. #pragma comment(lib,"ws2_32.lib")

#pragma comment(lib,"Ws2_32.lib")表示链接Ws2_32.lib这个库。

  1. //1、加载库
  2. WORD version = MAKEWORD(2, 2);
  3. WSADATA data = {};
  4. int err=WSAStartup(version,&data);
  5. if (0 != err) {
  6. cout << "WSAStartup fail" << endl;
  7. return 1;
  8. }
  9. else {
  10. cout << "WSAStartup success" << endl;
  11. }
  12. if (2 != HIBYTE(data.wVersion) || 2 != LOBYTE(data.wVersion)) {
  13. //虽然加载库成功了,但是版本加载不对
  14. cout << "version error" << endl;
  15. WSACleanup();
  16. return 1;
  17. }
  18. else {
  19. cout << "WSAStartup version success" << endl;
  20. }

创建套接字

创建套接字需要使用socket函数。函数的3个参数,分别代表 地址族,套接字类型,通讯协议

根据返回值是否为异常,来判断套接字创建是否成功。

若创建失败,需要卸载库,并终止运行。

  1. SOCKET WSAAPI socket(
  2. //VS官方帮助文档中的[in]表示输入的参数
  3. [in] int af,
  4. [in] int type,
  5. [in] int protocol
  6. );
  1. //2、创建套接字
  2. SOCKET sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  3. if (INVALID_SOCKET != sock) {
  4. cout << "socket success" << endl;
  5. }
  6. else {
  7. cout << "socket fail" << WSAGetLastError() << endl;
  8. WSACleanup();
  9. return 1;
  10. }

绑定IP

绑定IP地址使用的函数是bind函数。函数的返回值是一个整型,根据函数的返回值来判断IP绑定是否成功,失败返回SOCKET_ERROR,此时不仅要卸载库,还要关闭套接字。

bind函数有三个参数,套接字,sockaddr结构体类型的指针,结构体大小

  1. int WSAAPI bind(
  2. [in] SOCKET s,
  3. [in] const sockaddr *name,
  4. [in] int namelen
  5. );

通过帮助文档查看sockaddr结构体,给出了sockaddr和sockaddr_in。为了方便设置端口号和IP,我们使用sockaddr_in,传入函数时,再强制类型转换将其地址转换为sockaddr*类型。

  1. typedef struct sockaddr {
  2. u_short sa_family;
  3. char sa_data[14];
  4. } SOCKADDR, *PSOCKADDR, *LPSOCKADDR;
  1. typedef struct sockaddr_in {
  2. short sin_family;
  3. u_short sin_port;
  4. struct in_addr sin_addr;
  5. char sin_zero[8];
  6. } SOCKADDR_IN, *PSOCKADDR_IN, *LPSOCKADDR_IN;

sockaddr_in结构体的成员有一个in_addr结构体,在帮助文档中搜索查看,发现结构体里面是个联合体。用于传入IP。

  1. struct in_addr {
  2. union {
  3. struct {
  4. u_char s_b1;
  5. u_char s_b2;
  6. u_char s_b3;
  7. u_char s_b4;
  8. } S_un_b;
  9. struct {
  10. u_short s_w1;
  11. u_short s_w2;
  12. } S_un_w;
  13. u_long S_addr;
  14. } S_un;
  15. };
  1. //3、绑定IP和端口号
  2. sockaddr_in sockServer;
  3. sockServer.sin_family = AF_INET;
  4. sockServer.sin_port = htons(9876); //设置端口号
  5. sockServer.sin_addr.S_un.S_addr = ADDR_ANY; //绑定任意所有IP
  6. err=bind(sock,(sockaddr*)&sockServer,sizeof(sockServer));
  7. if (SOCKET_ERROR != err) {
  8. cout << "bind success" << endl;
  9. }
  10. else
  11. {
  12. cout << "bind fail" << WSAGetLastError() << endl;
  13. //绑定失败,不仅要卸载库,还要关闭套接字
  14. closesocket(sock);
  15. WSACleanup();
  16. return 1;
  17. }
  18. char recvBuf[4096] = "";
  19. char sendBuf[4096] = "";
  20. sockaddr_in sockClient = {};
  21. int sockClientSize=sizeof(sockClient);

这里需要注意的是,我们在设置端口号时,需要转一下网络字节序。如果不转换的话,PC之间的客户端和服务端进行数据传输,由于都是小端存储,所以不会有影响。但是如果对端不是PC,可能就会有问题。

sockServer.sin_port = htons(9876);  //设置端口号,并转换网络字节序

接收消息

接收消息使用的是recvfrom函数。函数有6个参数,第一个参数是上面定义的套接字;第二个参数是输出参数,字符串类型,用来存放收到的消息;第三个参数是字符串长度;第四个参数是标志位(填0即可);第五个参数是输出参数,是sockaddr类型的结构体指针,表示消息的来源;第七个参数是消息来源结构体的长度的地址。

若能正常接收,则返回值>0;如果连接关闭,则返回0,如果接收异常返回错误信息。

在能正常收到消息的情况下,我们打印对端IP地址 和 用来存放消息的字符串即消息内容。这里我们使用 inet_ntoa函数将ulong型IP表示转换为十进制字符串四等分型IP表示。

  1. int WSAAPI recvfrom(
  2. [in] SOCKET s,
  3. [out] char *buf,
  4. [in] int len,
  5. [in] int flags,
  6. [out] sockaddr *from,
  7. [in, out, optional] int *fromlen
  8. );
  1. //4、接收数据
  2. recv=recvfrom(sock, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&sockClient, &sockClientSize);
  3. if (recv > 0)
  4. {
  5. //接收成功,输出client的ip和发送的内容
  6. //ip存放有两种方式,ulong型和十进制字符串四等分型
  7. //inet_ntoa();
  8. //inet_addr();
  9. cout << "server recv: " ;
  10. cout << "ip:" << inet_ntoa(sockClient.sin_addr) << " say:" << recvBuf << endl;
  11. }
  12. else if (recv == 0)
  13. {
  14. cout << "connection closed" << endl;
  15. }
  16. else {
  17. cout << "recvfrom error" << WSAGetLastError() << endl;
  18. break;
  19. }

发送消息

发送消息使用的是sendto函数,函数有6个参数。第一个参数是上面定义的套接字,第二个参数是字符串,用来存放我们发送的内容,第三个参数是字符串的长度,第四个参数是标志位,填0即可;第五个参数是客户端的地址,第六个参数是客户端的大小

  1. int WSAAPI sendto(
  2. [in] SOCKET s,
  3. [in] const char *buf,
  4. [in] int len,
  5. [in] int flags,
  6. [in] const sockaddr *to,
  7. [in] int tolen
  8. );
  1. //5、发送数据
  2. cout << "server send: ";
  3. gets_s(sendBuf);
  4. sendto(sock, sendBuf, sizeof(sendBuf), 0, (sockaddr*)&sockClient, sockClientSize);

关闭套接字

关闭我们上面创建的套接字,使用的是closesocket函数,传入的参数是套接字

  1. //6、关闭套接字
  2. closesocket(sock);

卸载库

使用WSACleanup函数即可,没有参数。

  1. //7、卸载库
  2. WSACleanup();

完整代码

  1. #include<iostream>
  2. #include<winsock2.h>
  3. #pragma comment(lib,"ws2_32.lib")
  4. using namespace std;
  5. int main() {
  6. //1、加载库
  7. WORD version = MAKEWORD(2, 2);
  8. WSADATA data = {};
  9. int err=WSAStartup(version,&data);
  10. if (0 != err) {
  11. cout << "WSAStartup fail" << endl;
  12. return 1;
  13. }
  14. else {
  15. cout << "WSAStartup success" << endl;
  16. }
  17. if (2 != HIBYTE(data.wVersion) || 2 != LOBYTE(data.wVersion)) {
  18. //虽然加载库成功了,但是版本加载不对
  19. cout << "version error" << endl;
  20. WSACleanup();
  21. return 1;
  22. }
  23. else {
  24. cout << "WSAStartup version success" << endl;
  25. }
  26. //2、创建套接字
  27. SOCKET sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  28. if (INVALID_SOCKET != sock) {
  29. cout << "socket success" << endl;
  30. }
  31. else {
  32. cout << "socket fail" << WSAGetLastError() << endl;
  33. WSACleanup();
  34. return 1;
  35. }
  36. //3、绑定IP和端口号
  37. sockaddr_in sockServer;
  38. sockServer.sin_family = AF_INET;
  39. sockServer.sin_port = htons(9876); //设置端口号
  40. sockServer.sin_addr.S_un.S_addr = ADDR_ANY; //绑定任意所有IP
  41. err=bind(sock,(sockaddr*)&sockServer,sizeof(sockServer));
  42. if (SOCKET_ERROR != err) {
  43. cout << "bind success" << endl;
  44. }
  45. else
  46. {
  47. cout << "bind fail" << WSAGetLastError() << endl;
  48. //绑定失败,不仅要卸载库,还要关闭套接字
  49. closesocket(sock);
  50. WSACleanup();
  51. return 1;
  52. }
  53. char recvBuf[4096] = "";
  54. char sendBuf[4096] = "";
  55. sockaddr_in sockClient = {};
  56. int sockClientSize=sizeof(sockClient);
  57. cout << "-----Server init over-----" << endl;
  58. while (true)
  59. {
  60. //4、接收数据
  61. cout << "server recv: " ;
  62. int recv=recvfrom(sock, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&sockClient, &sockClientSize);
  63. if (recv > 0)
  64. {
  65. //接收成功,输出client的ip和发送的内容
  66. //ip存放有两种方式,ulong型和十进制字符串四等分型
  67. //inet_ntoa();
  68. cout << "ip:" << inet_ntoa(sockClient.sin_addr) << " say:" << recvBuf << endl;
  69. }
  70. else if (recv == 0)
  71. {
  72. cout << "connection closed" << endl;
  73. }
  74. else {
  75. cout << "recvfrom error" << WSAGetLastError() << endl;
  76. break;
  77. }
  78. //5、发送数据
  79. cout << "server send: ";
  80. gets_s(sendBuf);
  81. sendto(sock, sendBuf, sizeof(sendBuf), 0, (sockaddr*)&sockClient, sockClientSize);
  82. }
  83. //6、关闭套接字
  84. closesocket(sock);
  85. //7、卸载库
  86. WSACleanup();
  87. return 0;
  88. }

运行截图

recvfrom阻塞函数,等待客户端发送消息。

客户端:

与服务端不同的是,客户端不需要进行绑定IP。

但是这里,我们发送数据的IP不能是所有IP了,而是指定为服务端的IP。

  1. //3、配置IP和端口号
  2. sockaddr_in sockServer;
  3. sockServer.sin_family = AF_INET;
  4. sockServer.sin_port = htons(9876); //设置端口号
  5. sockServer.sin_addr.S_un.S_addr = inet_addr("192.168.3.108"); //客户端IP

其它部分与服务端基本相同。

客户端代码:

  1. #include<iostream>
  2. #include<winsock2.h>
  3. #pragma comment(lib,"ws2_32.lib")
  4. using namespace std;
  5. int main() {
  6. //1、加载库
  7. WORD version = MAKEWORD(2, 2);
  8. WSADATA data = {};
  9. int err = WSAStartup(version, &data);
  10. if (0 != err) {
  11. cout << "WSAStartup fail" << endl;
  12. return 1;
  13. }
  14. else {
  15. cout << "WSAStartup success" << endl;
  16. }
  17. if (2 != HIBYTE(data.wVersion) || 2 != LOBYTE(data.wVersion)) {
  18. //虽然加载库成功了,但是版本加载不对
  19. cout << "version error" << endl;
  20. WSACleanup();
  21. return 1;
  22. }
  23. else {
  24. cout << "WSAStartup version success" << endl;
  25. }
  26. //2、创建套接字
  27. SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  28. if (INVALID_SOCKET != sock) {
  29. cout << "socket success" << endl;
  30. }
  31. else {
  32. cout << "socket fail" << WSAGetLastError() << endl;
  33. WSACleanup();
  34. return 1;
  35. }
  36. //3、配置IP和端口号
  37. //设置有限广播权限
  38. BOOL bval = TRUE;
  39. setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&bval, sizeof(bval));
  40. sockaddr_in sockServer;
  41. sockServer.sin_family = AF_INET;
  42. //sockServer.sin_port = htons(9876); //设置端口号
  43. //sockServer.sin_addr.S_un.S_addr = inet_addr("192.168.1.186"); //客户端IP
  44. sockServer.sin_port = htons(98765); //设置端口号
  45. sockServer.sin_addr.S_un.S_addr = inet_addr("255.255.255.255"); //客户端IP
  46. char recvBuf[4096] = "";
  47. char sendBuf[4096] = "";
  48. int recv = 0;
  49. int send = 0;
  50. cout << "-----Client init over-----" << endl;
  51. while (true)
  52. {
  53. //5、发送数据
  54. cout << "Client send: ";
  55. gets_s(sendBuf);
  56. send=sendto(sock, sendBuf, sizeof(sendBuf), 0, (sockaddr*)&sockServer, sizeof(sockServer));
  57. if (SOCKET_ERROR == send) {
  58. cout << "sendto fail" << endl;
  59. }
  60. }
  61. //6、关闭套接字
  62. closesocket(sock);
  63. //7、卸载库
  64. WSACleanup();
  65. return 0;
  66. }

让我们来看看通信成功的结果:

UDP特点

面向非连接,可以接收任意方发来的数据,可以是1对1,也可以是1对多(广播和组播)

数据报文的通讯方式,数据包不可拆分

传输效率高(相对TCP)

没有效验和检查,容易丢包,也可能会出现乱序。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/638988
推荐阅读
相关标签
  

闽ICP备14008679号