【网络编程】网络套接字udp通用服务器和客户端

1.预备知识

认识端口号

端口号(port)是传输层协议的内容:

  • 端口号是一个2字节16位的整数(uint16)
  • 端口号用来标识主机上的一个进程
  • IP地址+port能够标识网络上的某一台主机和某一个进程
  • 一个端口号只能被一个进程占用

认识TCP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议) 有一个直观的认识,后面再详细讨论TCP的一些细节问题。

  • 传输层协议
  • 有连接
  • 可靠性传输
  • 面向字节流

认识UDP协议

此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识,后面再详细讨论。

  • 传输层协议
  • 无连接
  • 不可靠性传输
  • 面向数据报

网络字节序

内存中的多字节数据相比于内存地址有大端和小端之分,磁盘文件中的多字节数据相比于文件中的偏移地址也有大端小端之分,网络数据流同样有大端和小端之分,那么如何定义网络数据流的地址呢?

  • 发送主机通常将发送缓冲区中的数据按照内存地址从低到高顺序发出
  • 接受主机把从网络上接到的字节序依次保存在接收缓冲区,也是按照内存地址从低到高的顺序保存
  • 因此,网络数据流的地址应该这样规定:先发出的数据是低地址,后发出的数据是高地址
  • TCP/UDP协议规定,网络数据流应该采用大端字节序,即低地址高字节
  • 不管这台主机是大端机还是小端机,都会按照这个TCP/UDP规定的网络字节序来发送/接收数据
  • 如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可

存储在内存中的数据有大端和小端之分,低位存储在低地址的是小端,低位存储在高地址的是大端。

为了使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常的运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>
uint32_t htonl(unit32_t hostlong);
uint16_t htons(unit16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数
  • 例如htonl 表示将32位长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送
  • 如果主机是小端字节序,这些函数将参数做相应的大小端进行转换,然后返回
  • 如果主机是大端字节序,这些函数不做转换,将参数原封不动的返回

2.socket编程接口

socket常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockaddr结构

sockaddr是通用的网络接口,网络通信中经常使用到的是struct sockaddr_in。

  • IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in 结构体表示,包含16位地址类型,16位端口号和32位IP地址
  • IPv4和IPv6地址类型分别定义为常数AF_INET、AF_INET6,这样主要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容
  • sock API可以都用struct sockaddr*类型表示,在使用的时候需要强制转化成sockaddr_in,这样的好处是程序的通用性,可以接收IPv4,IPv6,以及UNIX Domain Socket各种类型的sockaddr结构体指针作为参数

sockaddr结构

sockaddr_in结构

虽然socket api的接口是sockaddr,但是真正在基于IPv4编程时,使用的数据结构是sockaddr_in;这个结构里主要有三部分信息:地址类型,端口号,IP地址

in_addr结构

in_addr用来表示一个IPv4的IP地址,其实就是一个32位的整数;

3.简单的UDP网络程序

实现一个简单的英译汉功能

封装UdpSocket

udp_socket.hpp

#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cassert>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>class UdpSocket
{
public:UdpSocket() : fd_(-1){}bool Socket(){fd_ = socket(AF_INET, SOCK_DGRAM, 0);if (fd_ < 0){perror("socket");return false; //创建套接字失败}return true;}bool Close(){close(fd_); //关闭套接字return true;}bool Bind(const std::string &ip, uint16_t port){sockaddr_in addr;              //用于网络传输addr.sin_family = AF_INET;     //地址类型-网络addr.sin_addr.s_addr = inet_addr(ip.c_str());addr.sin_port = htons(port);int ret = bind(fd_, (sockaddr *)&addr, sizeof(addr));if (ret < 0){perror("bind");return false; //绑定失败}return true;}bool RecvFrom(std::string *buf, std::string *ip = NULL, uint16_t *port = NULL){char tmp[1024 * 10] = {0};sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t read_size = recvfrom(fd_, tmp,sizeof(tmp) - 1, 0, (sockaddr *)&peer, &len);if (read_size < 0){perror("recvfrom");return false;}// 将读到的缓冲区内容放到输出参数中buf->assign(tmp, read_size);if (ip != NULL){*ip = inet_ntoa(peer.sin_addr);}if (port != NULL){*port = ntohs(peer.sin_port);}return true;}bool SendTo(const std::string &buf, const std::string &ip, uint16_t port){sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(ip.c_str());addr.sin_port = htons(port);ssize_t write_size = sendto(fd_, buf.data(), buf.size(), 0, (sockaddr *)&addr, sizeof(addr));if (write_size < 0){perror("sendto");return false;}return true;}private:
//socket函数是实现网络通信的重要工具,用于创建、绑定、监听、连接和关闭套接字,以及发送和接收数据。int fd_;  
};

udp通用服务器

#pragma once
#include "udp_socket.hpp"// C 式写法
// typedef void (*Handler)(const std::string& req, std::string* resp);
// C++ 11 式写法, 能够兼容函数指针, 仿函数, 和 lamda
#include <functional>
typedef std::function<void(const std::string &, std::string *resp)> Handler;
class UdpServer
{
public:UdpServer(){assert(sock_.Socket());}~UdpServer(){sock_.Close();}bool Start(const std::string &ip, uint16_t port, Handler handler){// 1. 创建 socket// 2. 绑定端口号bool ret = sock_.Bind(ip, port);if (!ret){return false;}// 3. 进入事件循环for (;;){// 4. 尝试读取请求std::string req;std::string remote_ip;uint16_t remote_port = 0;bool ret = sock_.RecvFrom(&req, &remote_ip, &remote_port);if (!ret){continue;}std::string resp;// 5. 根据请求计算响应handler(req, &resp);// 6. 返回响应给客户端sock_.SendTo(resp, remote_ip, remote_port);printf("[%s:%d] req: %s, resp: %s\n", remote_ip.c_str(), remote_port,req.c_str(), resp.c_str());}sock_.Close();return true;}private:UdpSocket sock_;
};

实现英译汉服务器

以上代码是对udp服务器进行通用接口的封装,基于以上封装,实现一个查字典的服务器就很容易了;

dict_server.cc

#include "udp_server.hpp"
#include <unordered_map>
#include <iostream>std::unordered_map<std::string, std::string> g_dict;
void Translate(const std::string &req, std::string *resp)
{auto it = g_dict.find(req);if (it == g_dict.end()){*resp = "未查到!";return;}*resp = it->second;
}
int main(int argc, char *argv[])
{if (argc != 3){printf("Usage ./dict_server [ip] [port]\n");return 1;}// 1. 数据初始化g_dict.insert(std::make_pair("hello", "你好"));g_dict.insert(std::make_pair("world", "世界"));g_dict.insert(std::make_pair("c++", "最好的编程语言"));// 2. 启动服务器UdpServer server;server.Start(argv[1], atoi(argv[2]), Translate);return 0;
}

UDP通用客户端

udp_client.hpp

#pragma once
#include "udp_socket.hpp"
class UdpClient
{
public:UdpClient(const std::string &ip, uint16_t port) : ip_(ip), port_(port){assert(sock_.Socket());}~UdpClient(){sock_.Close();}bool RecvFrom(std::string *buf){return sock_.RecvFrom(buf);}bool SendTo(const std::string &buf){return sock_.SendTo(buf, ip_, port_);}private:UdpSocket sock_;// 服务器端的 IP 和 端口号std::string ip_;uint16_t port_;
};

实现英译汉客户端

dict_client.cpp

#include "udp_client.hpp"
#include <iostream>
int main(int argc, char *argv[])
{if (argc != 3){printf("Usage ./dict_client [ip] [port]\n");return 1;}UdpClient client(argv[1], atoi(argv[2]));for (;;){std::string word;std::cout << "请输入您要查的单词: ";std::cin >> word;if (!std::cin){std::cout << "Good Bye" << std::endl;break;}client.SendTo(word);std::string result;client.RecvFrom(&result);std::cout << word << " 意思是 " << result << std::endl;}return 0;
}

4.地址转换函数

基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位的IP地址,但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间进行转换;

字符串转in_addr函数:

in_addr转字符串的函数:

其实inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void* addrptr。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/5449.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring MVC异步上传、跨服务器上传和文件下载

一、异步上传 之前的上传方案&#xff0c;在上传成功后都会跳转页面。而在实际开发中&#xff0c;很多情况下上传后不进行跳转&#xff0c;而是进行页面的局部刷新&#xff0c;比如&#xff1a;上传头像成功后将头像显示在网页中。这时候就需要使用异步文件上传。 1.1 JSP页面 …

[golang gin框架] 41.Gin商城项目-微服务实战之后台Rbac微服务(用户登录 、Gorm数据库配置单独抽离、 Consul配置单独抽离)

上一节抽离了captcha验证码功能,集成了验证码微服务功能,这一节来看看后台Rbac功能,并抽离其中的用户登录,管理员管理,角色管理,权限管理等功能作为微服务来调用 一.引入 后台操作从登录到后台首页,然后其中的管理员管理,角色管理,权限管理等功能可以抽离出来作为 一个Rbac微服…

Python实战

官方文档 请点击下面工程名称&#xff0c;跳转到代码的仓库页面&#xff0c;将工程 下载下来 Demo Code 里有详细的注释 LearnPythonPython 实现功能点demo

OpenCV for Python 实战(一):获取图片拍摄GPS地址并自动添加水印

Hello 我们在OpenCV每天的基础博客当中已经更新了很多了&#xff0c;那么今天我们就来结合前几天的内容。做一个获取属性然后添加对应属性的水印。那让我们赶快开始吧~ 文章目录 图片EXIFPython 获取EXIFexifread库使用方法转换成文字地址 添加水印cv2.putText() 每日总结 图片…

【001 操作系统】什么是线程、进程?线程进程的区别是什么?

一、什么是线程、进程&#xff1f; 进程&#xff1a;进程是资源分配的基本单位&#xff0c;它是程序执行时的一个实例&#xff0c;在程序运行时创建。 在Linux环境下&#xff0c;每个进程有自己各自独立的 4G 地址空间&#xff0c;大家互不干扰对方&#xff0c;如果两个进程之间…

基于大模型的Text2SQL微调的实战教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

Homography单应性矩阵

1. Homography 单应性概念 考虑 同一个平面(比如书皮)的两张图片&#xff0c;红点表示同一个物理坐标点在两张图片上的各自位置。在 CV 术语中&#xff0c;我们称之为对应点。 Homography 就是将一张图像上的点映射到另一张图像上对应点的3x3变换矩阵. 因为 Homography 是一个 …

Python模块requests基本用法

简介 Python 的 requests 模块是一个流行的第三方库&#xff0c;用于发送HTTP请求。它提供了一组简洁且易于使用的API&#xff0c;使得在Python中进行网络通信变得更加简单和灵活。 目录 1. 基本概念 1.1. HTTP 协议 1.2. GET 请求 1.3. POST 请求 1.4. get 和 post 的区别…

java本地socket服务端暴露至公网访问【内网穿透】

前言 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于恒川的日常汇报系列&#xff0c;大家有兴趣的可以看一看 &#x1f4d8;相关专栏C语言初…

蓝桥杯专题-真题版含答案-【生命之树】【消除尾一】【密码脱落】【生日蜡烛】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

【Java项目实战-牛客社区】--idea maven配置

第一 IDEA集成Maven插件&#xff0c;并配置Maven 以下步骤中&#xff0c;重点关注红色方框的配置 第二 IDEA 创建 Maven 项目 步骤一&#xff1a;创建模块&#xff0c;选择Maven&#xff0c;点击Next 步骤二&#xff1a;填写模块名称&#xff0c;坐标信息&#xff0c;点击finis…

【技术面试】Java八股文业余选手-下篇(持续更新)

文章目录 5. RocketMQ 消息中间件、RabbitMQ、ActiveMQ【√】5.1 RocketMQ 6. Kafka 大数据量消息中间件、ElasticSearch、ZooKeeper【√】6.1 Kafka【√】6.2 ElasticSearch 7. 分布式、研发提效、高并发、线程安全【√】7.1 分布式与集群【√】7.2 高并发、线程安全【】7.3 研…

【JavaScript】实现网页中的选项卡

一、简易选项卡 以下是实现一个简单选项卡的代码。代码中有注释。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0"…

Airbnb 引入 HTTP Streaming,网页性能得到大幅度提升

Airbnb 通过引入HTTP Streaming来提升网站的页面加载性能。他们将测试的每个页面&#xff08;包括主页&#xff09;的首次内容绘制&#xff08;First Contentful Paint&#xff0c;FCP&#xff09;时间降低了大约 100 毫秒。他们还最小化了后端慢查询对加载时间的影响。 Airbn…

Docker概述 镜像-容器基本操作

Docker 概述 Docker是一个开源的应用容器引擎&#xff0c;基于go语言开发并遵循了apache2.0协议开源。 Docker是在Linux容器里运行应用的开源工具&#xff0c;是一种轻量级的“虚拟机”。 Docker 的容器技术可以在一台主机上轻松为任何应用创建一个轻量级的、可移植的、自给自足…

Docker 安装 和 GPU 支持

一、Docker安装过程&#xff08;ubuntu18.04环境&#xff09; 清华镜像 docker 安装&#xff1a;docker-ce | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 1、由于apt官方库里的docker版本可能比较旧&#xff0c;所以先卸载可能存在的旧版本&…

MySQL(一)基本架构、SQL语句操作、试图

MySQL系列文章 MySQL&#xff08;一&#xff09;基本架构、SQL语句操作、试图 MySQL&#xff08;二&#xff09;索引原理以及优化 MySQL&#xff08;三&#xff09;SQL优化、Buffer pool、Change buffer MySQL&#xff08;四&#xff09;事务原理及分析 MySQL&#xff08;五&a…

异步任务——CompletabelFuture

本专栏学习内容又是来自尚硅谷周阳老师的视频 有兴趣的小伙伴可以点击视频地址观看 在学习CompletableFuture之前&#xff0c;必须要先了解一下Future Future 概念 Future接口&#xff08;FutureTask实现类&#xff09;定义了操作异步任务执行的一些方法&#xff0c;如获取异…

前端学习——Vue (Day2)

指令补充 指令修饰符 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevi…

每月进度总结 7月1日~7月22日

一个月已经过了三分之二了&#xff0c;感觉这个月是在学校学的很多。也是最充实的三个星期。其中也有发呆&#xff0c;也有过懊悔&#xff0c;今天状态为什么这么差&#xff0c;就学了这一点。但是还有学到知识的喜悦。总之是认识到了自己的很多不足&#xff0c;也找到了相对正…