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

网络实践——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.xdnf.cn/news/18422.html

相关文章:

  • 视频拼接融合技术:打造全景视界的革命性产品
  • API模型与接口弃用指南:历史、替代方案及开发者应对策略
  • `git mv` 重命名 Git 仓库中的文件夹
  • 多人编程新方式:cpolar 让 OpenHands 远程开发更轻松
  • 20250822在Ubuntu24.04.2下指定以太网卡的IP地址
  • 数据分析专栏记录之 -基础数学与统计知识 2 概率论基础与python
  • 安全帽检测算法如何提升工地安全管理效率
  • 【C++组件】Elasticsearch 安装及使用
  • Seaborn数据可视化实战:Seaborn时间序列可视化入门
  • Logstash_Input插件
  • 偶现型Bug处理方法---用系统方法对抗随机性
  • (附源码)基于SSM的餐饮企业食材采购管理系统的设计与实现
  • 攻防世界—bug
  • 以下是基于图论的归一化切割(Normalized Cut)图像分割工具的完整实现,结合Tkinter界面设计及Python代码示
  • 基于SpringBoot的考研学习交流平台【2026最新】
  • 十年磨一剑!Apache Hive 性能优化演进全史(2013 - )
  • 哈希和字符串哈希
  • 电子基石:硬件工程师的器件手册 (十三) - 电源管理IC:能量供给的艺术
  • Leetcode—1683. 无效的推文【简单】
  • Unity设置UI显示区域
  • 数据分类分级的概念、标准解读及实现路径
  • Spring Boot+Docker+Kubernetes 云原生部署实战指南
  • 网易云音乐歌曲导出缓存为原始音乐文件。低调,低调。。。
  • Java实现快速排序算法
  • Jetson Xavier NX 与 NVIDIA RTX 4070 (12GB)
  • Kafka中zk的作用是什么
  • 【Java后端】【可直接落地的 Redis 分布式锁实现】
  • Linux设备模型交互机制详细分析
  • 突击复习清单(高频核心考点)
  • RORPCAP: retrieval-based objects and relations prompt for image captioning