• UDP套接字编程详解


        UDP 是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议。

        UDP协议TCP协议一样用于处理数据包。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但即使在今天UDP仍然不失为一项非常实用和可行的网络传输层协议

        当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择。在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP也是一个好的选择。 [3] 

    UDP的一般实现流程图:

    UDP服务端 

    初始化套接字库

    1. //初始化套接字库
    2. WORD wVersion;
    3. WSADATA wsaData;
    4. int err;
    5. wVersion = MAKEWORD(1, 1);
    6. err = WSAStartup(wVersion, &wsaData);
    7. //判断执行结果
    8. if (err != 0) {
    9. return err;
    10. }
    11. //判断初始化结果是否与选择的版本库一致
    12. if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
    13. //清理套接字库
    14. WSACleanup();
    15. return -1;
    16. }

        在开之前需要先初始化套接字库,告诉操作系统我们选择的套接字库的版本号。这里我们选择的是 1.1版本的套接字库 。

        初始化完成后需要判断程序的执行结果,一个是函数本身的运行情况,判断返回结果是否为0;另一个是我们初始化的套接字库的版本号是否和我们指定的相一致。

    创建Socket 

    1. //创建Socket
    2. //指定协议族为IPV4,socket通信类型为支持UDP连接
    3. SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
    4. //准本一个处理网络通信的地址
    5. SOCKADDR_IN addrSrv;
    6. //指定要绑定的IP地址为本机上的任意IP地址
    7. addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    8. //协议族要与创建给的Socket一致
    9. addrSrv.sin_family = AF_INET;
    10. //指明绑定的端口号
    11. addrSrv.sin_port = htons(6001);

    创建一个服务器的Socket, 并指明协议族为IPV4,通信类型为支持UDP协议。

    还需要准本一个用于处理网络通信的结构体,并指绑定的IP地址、端口号和协议族(协议族要与创建Socket时的协议族保持一致)。

    绑定到本机

    1. //绑定套接字
    2. bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

    bind函数会将端口和IP地址绑定到我们的socket描述符上,返回0时表示绑定成功 。

    等待发送与接收数据

    1. //保存发送数据的地址
    2. SOCKADDR_IN addrCli;
    3. int len = sizeof(SOCKADDR_IN);
    4. char recvBuf[100];
    5. char sendBuf[100];
    6. while (true) {
    7. //接收数据
    8. recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
    9. //将接收数据打印下来
    10. std::cout << recvBuf << std::endl;
    11. sprintf_s(sendBuf, 100, "ACK%s", recvBuf);
    12. //发送数据
    13. sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrCli, len);
    14. }

    UDP没有TCP的监听和请求步骤,而是直接去接收客户端发来的信息,并将客户端的socket信息保存下来,在发送数据的时候使用。 

    recvfrom函数用于从(已连接)套接口上接收数据,并捕获数据发送源的地址。

    recvfrom函数原型为

    1. int recvfrom(
    2. SOCKET s,
    3. char* buf,
    4. int len,
    5. int flags,
    6. sockaddr * from,
    7. int* fromlen
    8. );
    •  s:标识绑定套接字的描述符
    • buf:传入数据的缓冲区
    • len:指向缓冲区的长度
    • flags:可一般默认为0
    • from:保存数据的发送地址
    • fromlen:发送数据的源地址长度指针

    sendto是一个向指定目的地发送数据的函数,将recvfrom捕获的客户端地址传入sendto里,就可以向该客户端发送数据 

    sendto函数原型

    1. int sendto(
    2. int s,
    3. const void * msg,
    4. int len,
    5. unsigned int flags,
    6. const struct sockaddr * to,
    7. int tolen
    8. );

    s:标识绑定的套接字描述符

    msg:发送数据的缓冲区

    len:指向发送缓冲区的长度

    flags:可一般默认为0

    to:向指定地址发送数据的地址长度

    完整代码

    1. #include
    2. #include
    3. int main() {
    4. //初始化套接字库
    5. WORD wVersion;
    6. WSADATA wsaData;
    7. int err;
    8. wVersion = MAKEWORD(1, 1);
    9. err = WSAStartup(wVersion, &wsaData);
    10. //判断执行结果
    11. if (err != 0) {
    12. return err;
    13. }
    14. //判断初始化结果是否与选择的版本库一致
    15. if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
    16. //清理套接字库
    17. WSACleanup();
    18. return -1;
    19. }
    20. //创建Socket
    21. //指定协议族为IPV4,socket通信类型为支持UDP连接
    22. SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
    23. //准本一个处理网络通信的地址
    24. SOCKADDR_IN addrSrv;
    25. //指定要绑定的IP地址为本机上的任意IP地址
    26. addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    27. //协议族要与创建给的Socket一致
    28. addrSrv.sin_family = AF_INET;
    29. //指明绑定的端口号
    30. addrSrv.sin_port = htons(6001);
    31. //绑定套接字
    32. bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
    33. //保存发送数据的地址
    34. SOCKADDR_IN addrCli;
    35. int len = sizeof(SOCKADDR_IN);
    36. char recvBuf[100];
    37. char sendBuf[100];
    38. while (true) {
    39. //接收数据
    40. recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
    41. //将接收数据打印下来
    42. std::cout << recvBuf << std::endl;
    43. sprintf_s(sendBuf, 100, "ACK%s", recvBuf);
    44. //发送数据
    45. sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrCli, len);
    46. }
    47. //关闭套接字
    48. closesocket(sockSrv);
    49. //清理套接字库
    50. WSACleanup();
    51. system("pause");
    52. return 0;
    53. }

    UDP客户端 

    初始化套接字库

    1. //初始化套接字库
    2. WORD wVersion;
    3. WSADATA wsaData;
    4. int err;
    5. wVersion = MAKEWORD(1, 1);
    6. err = WSAStartup(wVersion, &wsaData);
    7. //判断执行结果
    8. if (err != 0) {
    9. return err;
    10. }
    11. //判断初始化结果是否与选择的版本库一致
    12. if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
    13. //清理套接字库
    14. WSACleanup();
    15. return -1;
    16. }

    客户端的实现也需要先初始化套接字库,注意库的版本和服务器的版本相一致。

    创建套接字 

    1. //创建Socket
    2. //指定协议族为IPV4,socket通信类型为支持UDP连接
    3. SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
    4. //准本一个处理网络通信的地址
    5. SOCKADDR_IN addrSrv;
    6. //指定要连接的IP地址
    7. addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    8. //协议族要与创建给的Socket一致
    9. addrSrv.sin_family = AF_INET;
    10. //指明绑定的端口号
    11. addrSrv.sin_port = htons(6001);

    创建一个套接字,套接字的协议族与通信类型要和服务器的保持一致。之后准备一个处理网络通信的地址,也就是需要连接服务器的地址,并指明连接的IP地址和端口号。 

    发送与接收数据

    1. //保存发送数据的地址
    2. SOCKADDR_IN addrCli;
    3. int len = sizeof(SOCKADDR_IN);
    4. char recvBuf[100];
    5. char sendBuf[100] = "UDP Cli come to connect";
    6. //发送数据
    7. sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len);
    8. //接收数据,这里也可也不保存地址
    9. recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
    10. //将发送数据打印下来
    11. std::cout << recvBuf << std::endl;

    其实这里我们可以不写保存地址的SOCKADDR_IN结构体,因为我们已经知道要发送到的地址了

    完整代码 

    1. #include
    2. #include
    3. #pragma comment(lib,"ws2_32.lib")
    4. int main()
    5. {
    6. //初始化套接字库
    7. WORD wVersion;
    8. WSADATA wsaData;
    9. int err;
    10. wVersion = MAKEWORD(1, 1);
    11. err = WSAStartup(wVersion, &wsaData);
    12. //判断执行结果
    13. if (err != 0) {
    14. return err;
    15. }
    16. //判断初始化结果是否与选择的版本库一致
    17. if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
    18. //清理套接字库
    19. WSACleanup();
    20. return -1;
    21. }
    22. //创建Socket
    23. //指定协议族为IPV4,socket通信类型为支持UDP连接
    24. SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
    25. //准本一个处理网络通信的地址
    26. SOCKADDR_IN addrSrv;
    27. //指定要连接的IP地址
    28. addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    29. //协议族要与创建给的Socket一致
    30. addrSrv.sin_family = AF_INET;
    31. //指明绑定的端口号
    32. addrSrv.sin_port = htons(6001);
    33. //保存发送数据的地址
    34. SOCKADDR_IN addrCli;
    35. int len = sizeof(SOCKADDR_IN);
    36. char recvBuf[100];
    37. char sendBuf[100] = "UDP Cli come to connect";
    38. //发送数据
    39. sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len);
    40. //接收数据
    41. recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
    42. //将发送数据打印下来
    43. std::cout << recvBuf << std::endl;
    44. system("pause");
    45. return 0;
    46. }
  • 相关阅读:
    Android系统修改驱动固定USB摄像头节点绑定前后置摄像头
    othofinder跑出来结果缺少single_copy_othologue_sequences.
    curl网络请求命令
    又一微信自动化框架wxauto横空出世了!
    LC1305. 两棵二叉搜索树中的所有元素(JAVA)
    el-table实现穿梭功能
    洛谷P5545 炸弹攻击2
    对CMSIS的学习(第4-5部分)
    API与C++中的API
    软件面试逻辑题
  • 原文地址:https://blog.csdn.net/qq_54169998/article/details/127700809