当前位置: 首页 > news >正文

网络实践——Socket编程UDP

文章目录

  • Socket编程UDP
    • UDP接口的使用铺垫
      • socket
      • recvform && sendto
      • bind
    • 字节序转化使用(Tips)
    • 实践部分
      • version_1@echo_server
      • version_2@dict_server
      • version_3@chat_server

Socket编程UDP

在了解了相关的网络基础知识后,我们不会像学系统知识一样,先学原理,再讲应用。
我们先先不选择学习网络原理。我们先来试着学习一下使用接口来完成一些简单地实践。

在这个部分中,我们不只使用网络相关知识,而是结合前面系统部分学习时,完成的一些组件代码来使用!这些我们后面会见到的!

UDP接口的使用铺垫

先说一下,有了网络基础知识 + 系统部分的知识。其实我们只需要了解一下UDP相关接口的使用,其实就能较为熟练地学习如何使用。下面,我们将把网络中将用的接口进行简单讲解。

socket

NAMEsocket - create an endpoint for communication //打开通信的一端SYNOPSIS#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int socket(int domain, int type, int protocol);

这个文件,是用来创建套接字的!我们在网络基础部分讲过,两个进程的通信是基于套接字(端口号 + ip)的!具体的创建,就是用这个接口。

第一个参数domain是选择使用什么域来进行通信:
在这里插入图片描述
这里我们记住是选择AF_INET进行网络通信即可!

第二个参数type是选择使用什么方式传输,比如TCP/UDP:
在这里插入图片描述
字符流 -> SOCK_STREAM -> TCP
数据包 -> SOCK_DGRAM -> UDP

第三个参数protocol是要我们选择协议,默认给0就是TCP/IP了,记住即可!

返回值:
在这里插入图片描述
成功,返回一个file descriptor,即文件描述符!即使我们不知道这个网络通信的原理,但是我们至少可以直到,当前进程的文件描述符表定会有一个位置指向socket文件!

所以,我们就把它当成特殊的网络文件来使用!这符合 Linux下一切皆文件!

recvform && sendto

我们先来看着两个函数的相关信息,它们具有较强相似性:

recvform

NAMErecv, recvfrom, recvmsg - receive a message from a socketSYNOPSIS#include <sys/types.h>#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);RETURN VALUEThese calls return the number of bytes received, or -1 if an error occurred.  In the event of an error, errno is set to indicate the error.

sendto

NAMEsend, sendto, sendmsg - send a message on a socketSYNOPSIS#include <sys/types.h>#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);RETURN VALUEOn success, these calls return the number of bytes sent.  On error, -1 is returned, and errno is set appropriately.

见名思意:
recvform是从套接字文件中获取内容;sendto是向套接字文件中发送内容。


参数解释:

  1. int sockfd,就是对应的套接字文件!这个不需要解释。

  2. const void *buf,这个就是一个缓冲区,recvfrom把从套接字文件中读到的内容读取到该缓冲区。sendto将该缓冲区内的内容发送到套接字文件。

  3. size_t len,即缓冲区长度。

  4. int flags,这个是选择是否阻塞读取/发送的。默认给0就是阻塞。也就是说,recvfrom读不到内容就会阻塞,sendto没有内容发送也会阻塞。这个我们在系统那里也见过。

  5. struct sockaddr,这个其实我们早在网络基础部分的时候,就已经讲到过这个。我们说了,设计socket网络通信的时候,为了能够让网络通信和本地通信公用一套接口,所以设计了这么个c语言版的基类

    所以,如果需要收发消息,其实对方进程的相关信息:ip、端口号、通信协议,都会在对应的结构体上体现出来。所以,未来在使用socket通信的时候,如何知道或者发送自己的相关信息,就是靠着这个结构体sockaddr_in或者sockaddr_un,强转类型为sockaddr对应的地址变量后,然后进行相关操作!

6.socklen_t *addrlensocklen_t addrlen
6.1. 对于recvfrom收消息来说,参数是socklen_t *addrlen,这很明显是一个指针地址变量!所以,是需要我们把sockaddr_in的地址传进去给第五个参数,然后需要定义一个变量指明该结构体大小,传入地址给第六个参数。
Tips:因为该变量是输出型参数,实际上它会返回真正读到的字节数(因为网络传输可能丢包)

6.2. 对于sendto发消息来说,这个就没什么好说的了,本身数据就在,直接传对应的大小即可

bind

NAMEbind - bind a name to a socketSYNOPSIS#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);RETURN VALUEOn success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

没看错,这里还有一个接口叫做bind,但是,这个不是c++11后提出的std::bind

这个接口最重要的就是,将套接字文件和对应的进程的IP地址和端口号(在结构体sockaddr_in内存储了),绑定到对应的套接字文件中!

​​bind的作用​​:
显式绑定:bind()将套接字固定到特定IP+端口,成为该地址的唯一接收者。
未绑定时:系统在首次发送数据时自动分配随机端口(IP默认为所有本地接口)

这里理解一下就好,实在看不懂的话等后续讲解的时候再说一遍如何使用。现在只需要学会使用即可,原理等后期再来讲解!

字节序转化使用(Tips)

这个在网络基础中也是提到了。这里再多提醒一下,因为后续的实践中,必然是有从网络序列转主机序列的内容,也有主机序列转网络序列的!所以,这些是必须使用的!
在这里插入图片描述

实践部分

version_1@echo_server

源码:version_1@echo_server

第一个版本,我们希望实现一种功能:
即有一个服务器,然后其余所有的进程都可以给其发消息,然后服务器处理后再转发给对应的进程,这样子就起到了一个回显服务器的效果!

所有的过程都已经在代码的注释中展现出来了。

version_2@dict_server

源码:version_2@dict_server

第二个版本,其实就是进行一次解耦合!让第一个版本中对于信息回显的处理模块,转化为服务器调用词典翻译模块!其实主要的逻辑和第一个版本差别不大。

只不过是引入多了一个模块,让词典从对应的配置文件中读取对应信息,然后服务器进行调用后再进行转发结果给请求该服务的进程!

version_3@chat_server

源码:version_3@chat_server

第三个版本我们来详细说明一下:
第三个版本我们希望做到一个群聊转发功能,即服务器在接收到某个客户端发送来的消息后,要转发给所有处于在线的用户。

1.我们这里规定:
默认第一次发送消息的就是要加入群聊的。服务器接收到消息之后,要怎么样才能转发给所有的在线用户呢?-> 添加一层路由层,用于管理在线用户(组织描述)和消息转发!

2.但是,我们觉得效率太低了,所以希望的是,服务器将收到的信息推给后端线程池,让线程池自行调用路由转发功能!所以引入了线程池。

3.此前版本1、2写的客户端使用代码是有问题的!因为强行规定了先发消息才能收消息。所以,在实现群聊过程中,发现客户端只有发了消息,才能接收到其他客户端被转发的消息。所以,为此我们进行了处理,就是让客户端多线程处理!即创建两个线程,同时进行收发!

4.线程池访问路由表的时候(就是底层的一个哈希),也是会涉及到线程安全的。但是,因为今天的实现并没有规定消息的协议(是否退出、私法、群发、还是请求服务器处理…)。我们仅仅只是把收到的内容当字符串处理!
但是不管怎么说,线程池内每个线程访问的时候,是会出现数据不一致的问题的!因为STL不是线程安全的,所以我们这里就粗暴一点,直接加锁!

5.实践的时候,因为我没有多台Linux机器,所以,使用了Windows进行辅助测试,测试是否能够跨网通信!代码如下:

#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h>
#include <Windows.h>#pragma warning(disable : 4996)#pragma comment(lib, "ws2_32.lib")std::string serverip = "";  // 填写你的云服务器ip
uint16_t serverport = ; // 填写你的云服务开放的端口号SOCKET sockfd; 
struct sockaddr_in server;void recv_msg() {while (1) {char buffer[1024];struct sockaddr_in temp;int len = sizeof(temp);int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << buffer << std::endl;}}
}void send_msg() {memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(serverport); //? server.sin_addr.s_addr = inet_addr(serverip.c_str()); while (1) {std::string message; std::getline(std::cin, message);if (message.empty()) continue; sendto(sockfd, message.c_str(), (int)message.size(), 0, (struct sockaddr*)&server, sizeof(server));}
}int main() {WSADATA wsd; WSAStartup(MAKEWORD(2, 2), &wsd); sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == SOCKET_ERROR){std::cout << "socker error" << std::endl;return 1;}std::thread Recv(recv_msg);std::thread Send(send_msg);Recv.join();Send.join();closesocket(sockfd);WSACleanup();return 0;
}//int main()
//{
//    WSADATA wsd;
//    WSAStartup(MAKEWORD(2, 2), &wsd);
//
//
//    memset(&server, 0, sizeof(server));
//    server.sin_family = AF_INET;
//    server.sin_port = htons(serverport); //?
//    server.sin_addr.s_addr = inet_addr(serverip.c_str());
//
//    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
//    if (sockfd == SOCKET_ERROR)
//    {
//        std::cout << "socker error" << std::endl;
//        return 1;
//    }
//
//
//
//    std::string message;
//    char buffer[1024];
//    while (true)
//    {
//        std::cout << "please input: ";
//        std::getline(std::cin, message);
//        if (message.empty()) continue;
//        sendto(sockfd, message.c_str(), (int)message.size(), 0, (struct sockaddr*)&server, sizeof(server));
//        struct sockaddr_in temp;
//        int len = sizeof(temp);
//        int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
//        if (s > 0)
//        {
//            buffer[s] = 0;
//            std::cout << buffer << std::endl;
//        }
//    }
//
//    closesocket(sockfd);
//    WSACleanup();
//    return 0;
//}

前面一份是用于版本3的测试,后面是用于版本1、2的测试!这里可以搜一下相关大模型了解一下用法,其实用法和Linux下的基本一致。

http://www.dtcms.com/a/344757.html

相关文章:

  • Seaborn数据可视化实战:Seaborn颜色与样式定制教程
  • elasticsearch的使用
  • odoo-065 两个视图中的action类型的button互相引用,造成死循环
  • ubuntu使用fstab挂载USB设备(移动硬盘)
  • Claude Code接入Serena mcp
  • ESP32C5,使用espidf框架配置wifi扫描时报错,为什么会提示,ghz_5_channels的参数无效呢
  • 开发避坑指南(32):FastJSON异常JSONArray cannot be cast to JSONObject解决方案
  • 什么是数据分类分级?数据分类分级技术实现路径及产品推荐
  • ​Kubernetes 详解:云原生时代的容器编排与管理
  • 08.21总结
  • 【yocto】BitBake指令汇总解析
  • 基于springboot的农产品社区配送系统
  • 线性回归的学习
  • C++ unistd.h库文件介绍(文件与目录操作, 进程管理, 系统环境访问, 底层I/O操作, 系统休眠/执行控制)
  • golang 非error错误分类
  • 【如何生成专业级 API 接口文档:从规范到实战】
  • 指针实现数组的逆序存放并输出
  • IKE 与 ISAKMP 核心笔记
  • JCTools Spmc 单生产者-多消费者的无锁并发有界队列
  • 支持轻量化部署的混元3D世界模型Lite版本上线魔乐社区,昇腾部署实践来啦
  • FCT/ATE/ICT通用测试上位机软件
  • Leetcode—595. 大的国家【简单】
  • JUC之Fork/Join
  • WindowsAPI|每天了解几个winAPI接口之网络配置相关文档Iphlpapi.h详细分析9
  • 2-3.Python 编码基础 - 类型检测与类型转换
  • Vue 实现可拖拽分割布局(支持左右、上下拖拽调整)
  • Java 学习笔记(基础篇7)
  • 2025年游戏盾SDK动态加密技术全景解析:从防御破解到重塑游戏安全基石
  • CSM5110 5V/1A降压芯片 SOT23-5封装 可替代RY3408 带OVP保护
  • vim的使用