Linux网络编程套接字

目录

前言

一、预备知识

1.1 源IP地址和目的IP地址

1.2 区分端口号和进程ID

1.3 TCP协议和UDP协议

1.4 网络字节序

二、socket编程接口

2.1 socket套接字的概念

2.2 socket常见API

​编辑

2.3 sockaddr结构

三、关于IP和Port的绑定问题

四、编写简单的UDP服务端和客户端


前言

        在进入本章内容学习前,我们先要了解网络编程是什么:

        实际上网路编程指的是网络上的主机通过不同的进程,以编程的方式实现网络通信。

一、预备知识

1.1 源IP地址和目的IP地址

        在IP数据包头部中有两个IP地址分别叫做IP地址目的IP地址源IP地址和目的IP地址在进行网络通讯时是不会改变的,你可以理解为外出旅行时的出发地和目的地在旅行过程中是不会改变的。

1.2 区分端口号和进程ID

进程PID已经能够标识一台主机上进程的唯一性了,为什么还要搞一个端口号?

  1. 不是所有的进程都要网络通信,但所有的进程都要有PID;
  2. 更重要的说应该是要实现系统和网络功能的解耦。        

        IP标定互联网中唯一一台主机,端口号port用来标识该主机上的唯一的一个进程,因此

IP地址+端口号(port)能够标识全网内唯一的一个进程。

        另外,一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定

        传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号。就是在描述 "数据是谁发的,要发给谁。

1.3 TCP协议和UDP协议

TCP协议(Transmission Control Protocol 传输控制协议):

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

UDP协议(User Datagram Protocol 用户数据报协议):

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

1.4 网络字节序

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

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

        为使网络程序具有可移植性, 使同样的 C 代码在大端和小端计算机上编译后都能正常运行 , 可以调用以下库函数做网络字节序和主机字节序的转换:
  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数;
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送;
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

二、socket编程接口

2.1 socket套接字的概念

由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。
基于Socket套接字的网络程序开发就是网络编程。

套接字编程的种类:

  • 域间套接字编程(一般用于一个主机内的多个进程通信)
  • 原始套接字编程(通常是网络工具)
  • 网络套接字编程(用户间的网络通信)

2.2 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);

(头文件均为<sys/socket.h>)

 接收:

recvfrom() 函数用于从已连接或未连接的套接字接收数据,并将数据存储到缓冲区 buf 中。它的参数说明如下:

  • sockfd:套接字描述符。
  • buf:指向接收数据的缓冲区。
  • len:缓冲区的大小。
  • flags:额外的选项标志,通常设为 0。
  • src_addr:指向 struct sockaddr 结构体(见2.3)的指针,用于存储发送方的地址信息。
  • addrlen:指向 socklen_t 类型的指针,用于存储 src_addr 的长度。
  • 函数返回值为 ssize_t 类型,表示接收到的数据的字节数。如果返回 -1,则表示接收出错

发送:

sendto() 函数用于向指定的目标地址发送数据。它的参数说明如下:

  • sockfd:套接字描述符。
  • buf:指向要发送数据的缓冲区。
  • len:发送数据的大小。
  • flags:额外的选项标志,通常设为 0。
  • dest_addr:指向 struct sockaddr 结构体(见2.3)的指针,用于目标地址信息。
  • addrlen:指向 socklen_t 类型的指针,用于存储 src_addr 的长度。
  • 函数返回值为 ssize_t 类型,表示实际发送的数据的字节数。如果返回 -1,则表示接收出错

2.3 sockaddr结构

下面三种 sockaddr结构 分别对应:通用的网络地址结构体、IPv4地址结构体和Unix域地址结构体

接下来我们深入看看sockaddr_in的结构:

 框内第一个元素是端口号,第二个元素又是一个结构体类型:

可以看到它其实就是一个四字节的整数。

在使用时端口号是要在网络里来回发送的,因此必须保证端口号是网络字节序列;

同样的,IP如果要被网络使用,那么它也必须是网络字节序列的。

如果要把字符串IP转成四字节整型是不是很麻烦呢?

        其实系统早已为我们封装好了接口:

三、关于IP和Port的绑定问题

首先关于IP的绑定,若为0.0.0.0:

  1. 对于服务器:

    • 当服务器将IP地址绑定为 0.0.0.0 并且指定了特定的端口号时,服务器将会监听该端口号,并可以接受来自任何可用网络接口的连接请求。
    • 这意味着服务器将在所有网络接口上等待连接,包括本地回环接口(127.0.0.1)和物理网络接口。
    • 当有连接请求到达时,服务器可以根据需要选择特定的网络接口来处理连接。
  2. 对于客户端:

    • 当客户端将IP地址绑定为 0.0.0.0 并指定了特定的端口号时,客户端将通过任何可用的网络接口发送数据。
    • 这意味着客户端可以使用任何可用的网络接口来与目标服务器进行通信。

 关于Port的绑定:

        [0,1023]是系统内定的端口号,一般都有固定的应用层协议使用,如http:80 https:443

mysql:3306

四、编写简单的UDP服务端和客户端

UdpServer.hpp:

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"

// using func_t = std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string&)> func_t;

Log lg;

enum{
    SOCKET_ERR=1,
    BIND_ERR
};

uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
const int size = 1024;

class UdpServer{
public:
    UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip):sockfd_(0), port_(port), ip_(ip),isrunning_(false)
    {}
    void Init()
    {
        // 1. 创建udp socket
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INET
        if(sockfd_ < 0)
        {
            lg(Fatal, "socket create error, sockfd: %d", sockfd_);
            exit(SOCKET_ERR);
        }
        lg(Info, "socket create success, sockfd: %d", sockfd_);
        // 2. bind socket
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_); //需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的
        local.sin_addr.s_addr = inet_addr(ip_.c_str()); //1. string -> uint32_t 2. uint32_t必须是网络序列的 // ??
        // local.sin_addr.s_addr = htonl(INADDR_ANY);

        if(bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));
    }
    void Run(func_t func) // 对代码进行分层
    {
        isrunning_ = true;
        char inbuffer[size];
        while(isrunning_)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }
            inbuffer[n] = 0;
            std::string info = inbuffer;
            std::string echo_string = func(info);
            sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);
        }
    }
    ~UdpServer()
    {
        if(sockfd_>0) close(sockfd_);
    }
private:
    int sockfd_;     // 网路文件描述符
    std::string ip_; // 任意地址bind 0
    uint16_t port_;  // 表明服务器进程的端口号
    bool isrunning_;
};

 UdpClient.cpp:

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

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

    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport); //?
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    socklen_t len = sizeof(server);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        cout << "socker error" << endl;
        return 1;
    }

    // client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!
    // 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!
    // 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!
    // 系统什么时候给我bind呢?首次发送数据的时候

    string message;
    char buffer[1024];
    while (true)
    {
        cout << "Please Enter@ ";
        getline(cin, message);

        // std::cout << message << std::endl;
        // 1. 数据 2. 给谁发
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len);
        
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);

        ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
        if(s > 0)
        {
            buffer[s] = 0;
            cout << buffer << endl;
        }
    }

    close(sockfd);
    return 0;
}

main.cpp:

#include "UdpServer.hpp"
#include <memory>
#include <cstdio>


void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}

std::string Handler(const std::string &str)
{
    std::string res = "Server get a message: ";
    res += str;
    std::cout << res << std::endl;
    return res;
}

std::string ExcuteCommand(const std::string &cmd)
{
    // SafeCheck(cmd);

    FILE *fp = popen(cmd.c_str(), "r");
    if(nullptr == fp)
    {
        perror("popen");
        return "error";
    }
    std::string result;
    char buffer[4096];
    while(true)
    {
        char *ok = fgets(buffer, sizeof(buffer), fp);
        if(ok == nullptr) break;
        result += buffer;
    }
    pclose(fp);

    return result;
}

// ./udpserver port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));

    svr->Init(/**/);
    svr->Run(Handler);

    return 0;
}
运行结果:

        

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于stm32F103的蜂鸣器周期发声实验

蜂鸣器作为一种声音报警器件,应用广泛。本实验基于stm32F103单片机,通过控制蜂鸣器的IO口电平电压,使其周期性地进行电平翻转,从而驱动蜂鸣器发出周期性的鸣叫声。该实验主要运用了stm32的GPIO和定时器TIM的相关功能,不仅可以巩固这些外设的使用,也可以通过改变时间参数,控制蜂…

Kubernetes基础(二十二)-k8s持久化存储详解

1 volume 1.1 介绍 在容器中的磁盘文件是短暂的&#xff0c;当容器崩溃时&#xff0c;Kubelet会重新启动容器&#xff0c;但容器运行时产生的数据文件都将会丢失&#xff0c;之后容器会以最干净的状态启动。另外&#xff0c;当一个Pod运行多个容器时&#xff0c;各个容器可能…

使用阿里云发送短信

使用阿里云短信服务有两种方式 API 发送和 控制台发送&#xff0c;控制台发送到话有太多限制&#xff0c;这里我们使用API 通过 调用服务端代码进行发送。 整体结构如下&#xff1a; 导入依赖 <!--阿里云短信发送--><dependency><groupId>com.aliyun<…

ffmpeg for android编译全过程与遇到的问题

编译前准备 编译环境&#xff1a;Ubuntu16&#xff0c;可自行下载VMWare最新版并百度永久许可证或在服务器上安装Ubuntu ffmpeg源码&#xff1a;ffmpeg4.2.2 NDK下载&#xff1a;Android NDK r21e 有条件的最好还是在Liunx平台下编译吧&#xff0c;Windows平台下编译坑更多…

板块一 Servlet编程:第三节 HttpServletRequest对象全解与请求转发 来自【汤米尼克的JAVAEE全套教程专栏】

板块一 Servlet编程&#xff1a;第三节 HttpServletRequest对象全解与请求转发 一、什么是HttpServletRequest二、接收请求的常用方法三、请求乱码问题四、请求转发&#xff1a;forward五、Request作用域getParameter和getAttribute的区别 在上一节中我们已经学习了完整的Servl…

【JavaEE】_线程的状态与转移

目录 1. 线程的状态 1.1 NEW 1.2 RUNNABLE 1.3 BLOCKED 1.4 WAITING 1.5 TIMED_WAITING 1.6 TERMINATED 2. 线程状态的转移 在多线程Thread类相关一文中已经介绍过进程的状态&#xff1a;就绪状态与阻塞状态&#xff1b; 若需详情请查看原文&#xff0c;链接如下&#…

为项目配置spring boot3+jdk17的开发环境

1&#xff0c;项目【灯灯】 https://gitee.com/dromara/lamp-cloud/tree/3.4.0/ 具体项目介绍开源查看项目简介&#xff0c;克隆下来以后发现项目使用的是spring boot3jdk17编译模式&#xff0c;本地我们一般使用spring boot2jdk8&#xff0c;为了使项目能够运行起来 需要为这…

人工智能|机器学习——基于机器学习的舌苔检测

代码下载&#xff1a; 基于深度学习的舌苔检测毕设留档.zip资源-CSDN文库 1 研究背景 1.1.研究背景与意义 目前随着人们生活水平的不断提高&#xff0c;对于中医主张的理念越来越认可&#xff0c;对中医的需求也越来越多。在诊断中&#xff0c;中医通过观察人的舌头的舌质、苔…

前端|Day3:CSS基础(黑马笔记)

Day3:CSS基础 目录 Day3:CSS基础一、CSS初体验二、CSS引入方式三、选择器1.标签选择器2.类选择器3.id选择器4.通配符选择器 四、盒子尺寸和背景色五、文字控制属性1.字体大小2.字体样式&#xff08;是否倾斜&#xff09;3.行高单行文字垂直居中 4.字体族5.font复合属性6.文本缩…

安装Joplin Server私有化部署(docker)

安装Joplin Server私有化部署(docker) 前言: 老规矩官方文档链接 1. 首先拥有一个自己的云服务器(如果没有外网访问需求的话就随意吧) 安装docker安装方式 这里Joplin是使用PostgreSQL数据库的形式, 如果没有PostgreSQL库的话, Joplin默认使用的是SQLLite数据库 我这里使用的是…

国外高防服务器需要注意哪些方面

随着互联网的快速发展&#xff0c;网络安全问题日益突出&#xff0c;高防服务器逐渐成为企业和个人用户的首选。然而&#xff0c;在选择和使用国外高防服务器时&#xff0c;需要注意以下几个方面&#xff0c;以确保安全和稳定。 一、防御能力 首先&#xff0c;需要考虑国外高防…

Go应用性能分析实战

Go很适合用来开发高性能网络应用&#xff0c;但仍然需要借助有效的工具进行性能分析&#xff0c;优化代码逻辑。本文介绍了如何通过go test benchmark和pprof进行性能分析&#xff0c;从而实现最优的代码效能。原文: Profiling Go Applications in the Right Way with Examples…

哈希+set+map

哈希表 哈希表定义 散列表&#xff08;Hash table&#xff0c;也叫哈希表&#xff09;&#xff0c;是根据关键码值(Key value)而直接进行访问的数据结构 它通过把关键码值映射到表中一个位置来访问记录&#xff0c;以加快查找的速度映射函数叫做散列函数存放记录的数组叫做散…

ChatGPT-用ChatGPT指令,自学任何领域的系统知识

1. 指令位置 Github仓库&#xff1a;Mr Ranedeer AI Tutor 但是需要开通chatgtp plus版本&#xff0c;并且打开代码解释器 2 使用 学习内容 开始学习 AI甚至可以给你思考题&#xff0c;给出的答案还能进行评价 配置 通过配置表修改 深度 学习风格 沟通风格 语气风格 推…

【iOS】系统框架

文章目录 前言四十七、熟悉系统框架四十八、多用块枚举&#xff0c;少用for循环四十九、对自定义其内存管理语义的collection使用无缝桥接五十、构建缓存时选用NSCache而非NSDictionary五十一、精简initialize与load的实现代码五十二、别忘了NSTimer会保留其目标对象 前言 本次…

基于SpringBoot+Dubbo构建的电商平台-微服务架构、商城、电商、微服务、高并发、kafka、Elasticsearc+源代码+文档说明

文章目录 项目用到的技术前端使用的技术后端使用的技术项目模块说明项目搭建方式项目开发进度源码下载地址 项目基于springboot2.1.6.RELEASEDubbo2.7.3 来构建微服务。 业务模块划分&#xff0c;尽量贴合互联网公司的架构体系。所以&#xff0c;除了业务本身的复杂度不是很高之…

《Solidity 简易速速上手小册》第4章:智能合约的设计与开发(2024 最新版)

文章目录 4.1 合约结构和布局4.1.1 基础知识解析深入合约布局原则理解组织结构高效布局的重要性 4.1.2 重点案例&#xff1a;构建一个在线商店合约案例 Demo&#xff1a;编写在线商店智能合约案例代码&#xff1a;OnlineStore.sol测试和验证拓展功能 4.1.3 拓展案例 1&#xff…

Django学习笔记-创建第一个django项目

1.创建一个虚拟环境的python项目 2.点击解释器设置 3.安装django包 4.终端选择Command Prompt 5.创建django项目运行django-admin startproject demo01(自命名) 6.修改连接数据库为mysql 7.修改语言(中国汉语)和时区(亚洲上海) 8.修改TEMPLATES 9.创建templates文件夹 10.安…

二、双指针问题

283、移动零&#xff08;简单&#xff09; 题目描述 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12]…

深度学习发展的艺术

将人类直觉和相关数学见解结合后&#xff0c;经过大量研究试错后的结晶&#xff0c;产生了一些成功的深度学习模型。 深度学习模型的进展是理论研究与实践经验相结合的产物。科学家和工程师们借鉴了人类大脑神经元工作原理的基本直觉&#xff0c;并将这种生物学灵感转化为数学模…