Linux网络——socket编程与UDP实现服务器与客户机通信

news2024/9/23 15:27:21

文章目录

    • 端口号
    • TCP/UDP
    • 网络字节序
    • socket的常见API
    • UDP实现服务器与客户机通信
      • 服务器
      • 客户机
      • 运行效果如下

端口号

我们说即便是计算机网络,他们之间的通信也仍然是进程间通信

那么要如何在这么多计算机中,找到你想要的那个进程呢

在网络中标识的唯一的计算机使用的是ip地址

在同一台计算机的进程是通过进程id区分的,而要在对方的计算机中按照进程id来找恐怕不是一共好的想法,因为你也不知道对方进程的id是多少,于是就商量(传输层协议)使用port端口来确定进程

因此ip加上port就能确定这个世界上的唯一一台计算机中的唯一一个进程

ip地址是用四个八位二进制数来表示

而port端口号是一共2字节16位整数

端口号用来标识进程,告诉操作系统数据要交给哪一个进程

都已经有了进程id了为什么还要有个端口号呢

这是因为一个端口号只能被一个进程占用,但是一个进程是可以拥有多个端口号的

他们之间并不是完美的1对1关系

TCP/UDP

我们先简单重新认识一下这两个协议

这两个都是传输层的协议

TCP面向的是有连接,意思是在正式的传递信息之前,需要建立连接,确保是能收到的,就像对暗号一样,土豆土豆我是地瓜

而UDP则是无连接的,相当于直接把数据扔出去

640?wx_fmt=jpeg

img

由此可见,TCP是可靠传输,他规定了一些措施来保证传输的可靠性,例如三次握手四次挥手

“嗨,我想听一个TCP的笑话。”

“你好,你想听 TCP 的笑话么?”

“嗯,我想听一个 TCP 的笑话。”

“好的,我会给你讲一个TCP 的笑话。”

“好的,我会听一个TCP 的笑话。”

“你准备好听一个TCP 的笑话么?”

“嗯,我准备好听一个TCP 的笑话”

“OK,那我要发 TCP 笑话了。大概有 10 秒,20 个字。”

“嗯,我准备收你那个 10 秒时长,20 个字的笑话了。”

“抱歉,你的链接超时了。你好,你想听 TCP 的笑话么?”

而UDP是完全没有的,因此他是不可靠的

我给你们讲个UDP 的笑话吧! 哈哈哈哈哈哈哈哈哈是不是很好笑

除此之外,TCP由于建立了连接,就可以像水流一样传输数据,是面向字节流的,而UDP则没有,所以UDP是面向数据包的

网络字节序

计算机内存分为大端存储和小端存储,大端存储是低地址存高位数据,小端存储是低地址存低位数据

在这里插入图片描述

至今这两个流派也没有分出胜负,但是计算机网络不知道啊,他不知道两个计算机之间是如何存储的,只能硬性规定,网络数据流是按照低地址存高位数据,也就是大端存储

发送方的主机发送时是按照地址从低到高发送的

如果发送方是小端,则先将数据传换成大端

我们可以使用系统调用,将大小端字节序进行交换

在这里插入图片描述

也就是说小端机器发送时,调用hton,小端机器接收时,调用ntoh

大端直接就原封不动返回了,因此不确定大小端时,调用就对了

socket的常见API

socket有一个媲美鲁棒性的翻译,套接字,让人看得云里雾里,简直是完美的反自学机制

在这里插入图片描述

套接字的本质就是一个文件描述符,这个东西非常非常非常重要

在这里插入图片描述

socket的第一个参数是标识套接字的类型,AF_INET表示IPv4,也是最常用的

第二个参数表示通信的类型,是使用UDP(SOCK_DGRAM)还是使用TCP(SOCK_STREAM)

bind的作用是将这个进程提供的服务绑定到操作系统中,当外部访问时就能知道这个服务的端口号,当信息发出时,也能携带自身的ip和port

listen和accept是TCP通信中需要用到的,listen用于开始监听是否有请求,accept用于将拿到的请求解析,connect则是连接的请求

接下来我们会使用UDP进行主机和服务器之间的通信

UDP实现服务器与客户机通信

首先我们需要知道的是客户机和服务器需要的程序是不一样的,因此需要分别实现服务器和客户机的代码

我们需要知道,所谓的服务器和客户机不过都是进程

服务器

服务器的代码相对复杂,我们分不同文件来说明

// nocopy.hpp
#pragma once
#include <iostream>
class nocopy
{
public:
    nocopy() {}
    nocopy(const nocopy &) = delete;
    const nocopy &operator=(const nocopy &) = delete;
    ~nocopy() {}
};

这个类主要是实现一个最简单的单例模式,防止服务器进程被拷贝等内容

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

class InetAddr // 将接收到的网络信息格式化
{
public:
    InetAddr(struct sockaddr_in &addr)
        : _addr(addr)
    {
        _port = ntohs(_addr.sin_port);   // 将网络字节序转换为主机字节序
        _ip = inet_ntoa(_addr.sin_addr); // 将网络字节序的IP转换为点分十进制的字符串
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    std::string PrintDebug() // 输出调试信息
    {
        std::string info = _ip;
        info += ':';
        info += std::to_string(_port);
        return info;
    }
    ~InetAddr() {}

private:
    std::string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;
};

这个类主要是给客户机提供一个存储客户机信息的类,用于返回给客户机的请求

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "nocopy.hpp"
#include "InetAddr.hpp"

const static uint16_t defaultport = 1141; // 默认端口号
const static uint16_t defaultfd = -1;     // 默认socket
const static uint16_t defaultsize = 1024; // 默认缓冲区大小

class UdpServer : public nocopy // 服务器不允许被拷贝,简化的单例模式
{
public:
    UdpServer(uint16_t port = 11451)
        : _port(port)
    {
    }
    void Init()
    {
        // 创建socket文件描述符,对其进行初始化
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 使用IPv4,UDP协议
        if (_sockfd < 0)
        {
            perror("socket创建失败");
            exit(errno);
        }
        std::cout << "socket创建成功,文件描述符为:" << _sockfd << std::endl;

        // 初始化网络信息
        struct sockaddr_in local;
        bzero(&local, sizeof(local));       // memset
        local.sin_family = AF_INET;         // 协议簇
        local.sin_port = htons(_port);      // 端口号
        local.sin_addr.s_addr = INADDR_ANY; // ip地址

        // 绑定到系统内核
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n != 0)
        {
            perror("bind出错");
            exit(errno);
        }
    }
    void Start() // 服务器不退出
    {
        char buffer[1024];
        for (;;)
        {
            struct sockaddr_in peer; // 远程地址信息,因为需要返回请求
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len); // 获取从从外部接收的数据
            // 第一个参数是套接字文件描述符,标识接收数据的套接字
            // 第二个参数是接收数据的缓冲区

            if (n > 0) // 正确接收
            {
                InetAddr addr(peer); // 格式化网络信息
                buffer[n] = '\0';    // 手动添加结束
                std::cout << '[' << addr.PrintDebug() << "]# " << buffer << std::endl;
                sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
            }
        }
    }
    ~UdpServer()
    {
    }

private:
    uint16_t _port; // 端口号
    int _sockfd;    // socket文件描述符
};

这里就是客户机的主体了,里面的代码和注释写的非常详细,主要思路就是初始化,先听,后回复

#include "udpserver.hpp"
#include <memory>

void Usage(std::string proc) // 使用提示
{
    std::cout << "Usage:\n\t" << proc << "local_port\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);
    usvr->Init();
    usvr->Start();
    return 0;
}

这里就是主程序,负责判断用户输入是否正确,调用服务器

客户机

#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

void Usage (const std::string &proc)
{
    std::cout<<"Usage:\n\t"<<proc<<"server_ip server_port" <<std::endl;
}

int main(int argc, char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        return 1;    
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd<0)
    {
        perror("socket创建失败");
        exit(errno);
    }
    std::cout<<"socket创建成功,sockfd:"<<sockfd<<std::endl;

    // 客户机也需要绑定,但是不需要主动调用,客户机会在第一次发送数据时自动绑定数据
    // 填充服务器信息
    struct sockaddr_in server;
    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(true)
    {
        // 发送的数据
        std::string inbuffer;
        std::cout<<"Please Enter# ";
        std::getline(std::cin, inbuffer);
        // 发送消息(请求)
        ssize_t n = sendto(sockfd, inbuffer.c_str(), inbuffer.size(), 0, (struct sockaddr*)&server, sizeof(server));
        // 收消息
        if(n>0)
        {
            char buffer[1024];
            struct sockaddr_in tmp;
            socklen_t len = sizeof(tmp);
            ssize_t m = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&tmp, &len);
            if(m>0)
            {
                buffer[m] = '\0';
                std::cout<<"server echo# "<<buffer<<std::endl;
            }
            else
                break;
        }
        else
            break;
    }   
    close(sockfd);
    return 0;
}

客户机是先发出请求,再接收请求

运行效果如下

请添加图片描述

使用netstat -tuln可以查到服务器的ip地址和端口,当然这里只适合使用本机调试

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

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

相关文章

反激电路的参数设计,基于TI-UC3843芯片,Simplis仿真验证

采用TI官方提供的UC3843芯片手册给的DEMO图&#xff0c;通过MATHCAD计算参数得出环路补偿的各项参数&#xff0c;最后使用simplis进行仿真验证&#xff0c;一起进行学习吧。 UCx84x 电流模式 PWM 控制器 datasheet (Rev. G) (semiee.com) UCx84x 系列控制集成电路提供了实现离…

【springboot过ingress后无法获取X-Forwarded-For头信息】

springboot过ingress后无法获取X-Forwarded-For头信息 一、现象结论修改步骤ingressspringboot 排查流程本文参考 一、现象 项目使用spring boot 2.7.18&#xff0c;有个新需求是校验X-Forwarded-For头的所有来源ip合法性&#xff0c;线上环境出现取不到X-Forwarded-For头的问…

【思博伦】史上最详细思博伦测试仪使用精讲!图解超赞超详细!!!

承接上文 目录 2.2.9.7 配置TCPFLAG 2.2.9.8 配置分片 2.2.9.9 添加VLAN标签 2.2.9.10 添加MPLS标签 2.2.9.11 添加Vntag标签 2.2.9.12 重新快速新建新的帧内容 ​​​​​​​2.2.10 导入Pcap包 2.2.11 发包配置 2.2.11.1 发包模式配置 ​​​​​​​2.2.11.2 配置…

【OJ】关于顺序表的经典题目(移除数组中指定元素的值、数组去重、合并两个有序的数组)

文章目录 前言题目1&#xff1a;移除数组中指定的元素题目描述解题思路方法1 &#xff1a;暴力法方法2&#xff1a;双指针法 题目2&#xff1a;数组去重题目描述解题思路双指针法 题目3&#xff1a;合并两个有序的数组题目描述解题思路方法1&#xff1a;暴力破解法方法2&#x…

SAP F110 批量付款付款参考(KIDNO)和分配字段(ZUONR)带入会计凭证

SAP F110 批量付款付款参考(KIDNO)和分配字段(ZUNOR)带入会计凭证 财务有个要求需要将付款建议的付款参考和分配字段写入最后的ZP凭证 最后的效果大概就是这样的 折腾了很久, 1、先是想到的GGB1的替代,但是试验了很久都没有搞定,貌似很多人遇到了这个情况。 2、然后…

初识爬虫4

1.理解代理ip&#xff0c;正向代理和反向代理 2.代理ip分类&#xff0c;根据匿名度分类&#xff1a;透明&#xff0c;匿名&#xff0c;高匿 3.防止频繁向同一个域名发送请求被封ip,需使用代理ip # -*- coding: utf-8 -*- import requestsurl https://www.baidu.comproxies {…

Facebook如何通过AI改变你的社交体验?

在当今数字化的社交媒体环境中&#xff0c;Facebook作为全球最大的社交平台之一&#xff0c;正在通过引入和优化人工智能&#xff08;AI&#xff09;技术&#xff0c;改变用户的社交体验。人工智能不仅帮助Facebook增强了内容推荐和信息过滤的精准度&#xff0c;还让平台具备了…

故障恢复(残次版)

视频地址 一&#xff1a;文件系统故障修复案例 模拟搞坏磁盘中的某一块 dd if/dev/zero of/dev/sdb1 bs1k count10(默认4K)1.修补分区中每个组中坏掉的超级块部分 查看超级块的备份块 dumpe2fs 磁盘位置 如&#xff1a; dumpe2fs /dev/sdb1使用备份超级块进行修复&#x…

Netty笔记01-Netty的基本概念与用法

文章目录 1. 概述1.1 Netty 是什么&#xff1f;1.2 Netty 的特点1.3 Netty 的作者1.4 Netty 的地位1.5 Netty 的优势1.6 Netty 的工作原理1.7 Netty 的应用场景1.8 Netty 的重要组件 2. 第一个程序2.1 目标2.2 服务器端2.3 客户端2.4 流程梳理&#x1f4a1; 提示 1. 概述 1.1 …

【香菇的程序人生】七夕舔狗反诈指南:技术篇

本文设计技术分析及部分参考网站仅供技术研究及自我保护&#xff0c;请勿用于其他用途&#xff01; 另外&#xff0c;为什么本文只针对"舔狗"呢?因为舔狗不愿面对事实&#xff0c;本文将从科学技术角度给其指点迷津~ 文章目录 1. 你的女神/男神保熟吗1.1 AI 换脸请谨…

关于ADC单次扫描和连续扫描的操作指导(STM32F103ZET6正点原子版)作者 江汉大学 计科224尹伦能

STM32CubeMX环境配置 1 ADC1配置 选中ADC1&#xff0c;然后勾选IN0&#xff0c;其余设置均默认不变&#xff08;此为单次扫描&#xff0c;若是连续扫描&#xff0c;只需要将上图中的Continuous Conversion Mode由Disabled变为Enabled即可&#xff09;。 2 RCC及时钟配置 选中…

分析 ECharts 图表渲染导致的内存泄漏问题 - 附解决方案

一. 引言 在今年某个可视化大屏项目中&#xff0c;出现了一个问题。项目在运行一段时间后&#xff0c;页面出现了崩溃&#xff0c;而且是大概运行几天之后&#xff0c;因为大屏项目是部署到客户现场大屏&#xff0c;长时间运行不关闭。报错问题如下图所示&#xff1a; 由于这个…

重磅发布!DeepSeek-V2.5:融合通用与代码能力的全新开源模型

前沿科技速递&#x1f680; 在人工智能飞速发展的今天&#xff0c;通用对话与代码生成的融合已经成为开发者高效工作的关键工具。近日&#xff0c;DeepSeek 团队正式发布了全新的 DeepSeek-V2.5 模型&#xff0c;一个强大的开源模型&#xff0c;它将通用语言处理与代码生成能力…

Python爱心射线

系列目录 序号直达链接表白系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4Python李峋同款可写字版跳动的爱心5Python流星雨代码6Python漂浮爱心代码7Python爱心光波代码8Python普通的玫瑰花代码9Python炫酷的玫瑰花代码10Python多…

高效录屏攻略:电脑视频录制时声音同步捕获技巧,轻松搞定高清音质

在当今数字化时代&#xff0c;无论是在线教育、远程工作还是内容创作&#xff0c;录制视频已经成为我们生活中不可或缺的一部分。但是&#xff0c;很多人在录制视频时都会遇到一个问题&#xff1a;如何同时录制声音&#xff1f;如果你也在为此烦恼&#xff0c;那么这篇文章将为…

OpenCV通过鼠标提前ROI(C++实现)

文章目录 鼠标绘制矩形提取ROI任意形状绘制提前ROI 废话不多说&#xff0c;直接上代码 鼠标绘制矩形提取ROI #include <iostream> #include <opencv2\opencv.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/core/core.hpp>us…

算法_优先级队列---持续更新

文章目录 前言最后一块石头重量题目要求题目解析代码如下 数据流中的第K大元素题目要求题目解析代码如下 前K个高频单词题目要求题目解析代码如下 数据流的中位数题目要求题目解析代码如下 前言 本文将会向你分享优先级队列相关的题目&#xff1a;最后一块石头重量、数据流中的…

【运维自动化-作业平台】脚本版本管理如何使用

在蓝鲸作业平台中&#xff0c;执行脚本是最常见的功能之一&#xff0c;支持快速录入脚本执行和引用脚本执行&#xff0c;引用的脚步即单独写好的&#xff0c;并且由定期管理更新的脚本&#xff0c;主要有业务脚本和公共脚本两种。快速脚本 手动页面编写的脚本&#xff0c;没有…

将小写字母转换为大写字母(c 语言)

2.我们第一步输入字符串&#xff0c;第二步进行筛选将字符串中所以下标为奇数位置上的字母转换成大写&#xff0c;如果该位置不是字母&#xff0c;则不转换。 #include <stdio.h> #include <string.h> void fun( char *ss ) {int i 0;while (*ss ! \0){if (i % 2…

ACL 2024:交叉领域情感分析——论文阅读笔记

前言 阅读了一篇ABSA的论文&#xff0c;在这里写下自己的一些理解小笔记&#xff0c;可能有点小乱&#xff0c;原文在这下面&#xff1a; 论文链接&#xff1a;Refining and Synthesis: A Simple yet Effective Data Augmentation Framework for Cross-Domain Aspect-based Sen…