【网络】网络编程套接字——UDP、TCP、UDP接口使用、TCP接口使用、UDP程序实例、TCP程序实例

Linux网络

1. UDP

  在使用我们的UDP和TCP函数的时候,我们需要理解一些预备的知识:

源 IP 地址和目的 IP 地址:

  在网络通信中,IP 数据包头部的源 IP 地址和目的 IP 地址起着至关重要的作用。源 IP 地址指明了数据包的发送方所在的网络位置,就像寄信时的发信人地址;目的 IP 地址则指明了数据包的接收方所在的网络位置,如同收信人的地址。

  例如,当您在网上浏览网页时,您的设备(如电脑或手机)发送的请求数据包中就包含了您设备的源 IP 地址,而您所请求的网站服务器的 IP 地址则作为目的 IP 地址。

  但仅有 IP 地址并不足以完成完整的通信。以发送 QQ 消息为例,虽然 IP 地址能将消息送达对方的机器,但机器上可能运行着多个程序,还需要其他标识来确定这个数据应由哪个程序来解析处理,这就引入了端口号的概念。

  

端口号:

  端口号是传输层协议的重要组成部分,它是一个 2 字节 16 位的整数。端口号的作用在于标识一个进程,告诉操作系统应该将接收到的数据交给哪一个进程来处理。

  比如说,您的电脑上同时运行着浏览器和即时通讯软件,当网络数据到达时,端口号就像一个“指示牌”,告诉操作系统把数据准确地传递给对应的程序。

  就像一个电话号码,IP 地址能让数据找到正确的主机,而端口号则能让数据找到主机上正确的进程。

  IP 地址和端口号的组合能够唯一标识网络上某一台主机的某一个进程。

  

端口号和进程 ID(pid):

  在系统编程中,pid 用于唯一标识一个进程。而端口号在某种程度上也类似于 pid 对进程的标识作用。

  然而,它们之间也存在一些区别。pid 是操作系统内部用于管理进程的标识符,而端口号则主要用于网络通信中标识进程。

  例如,在一个服务器系统中,操作系统通过 pid 来管理进程的资源分配和调度,而当网络数据到达时,服务器通过端口号来确定将数据传递给哪个网络服务进程。

  

源端口号和目的端口号:

  在传输层协议(如 TCP 和 UDP)的数据段中,源端口号和目的端口号分别描述了“数据是谁发的,要发给谁”。

  比如,您通过浏览器访问一个网站,您的浏览器使用一个随机分配的源端口号(通常是大于 1024 的临时端口)向网站服务器的特定目的端口号(如 HTTP 协议通常使用 80 端口)发送请求,服务器接收到请求后,根据源端口号和目的端口号进行回应,从而完成一次完整的网络通信过程。

  
在这里插入图片描述

  

1.1 UDP接口使用

// 创建 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);

  

创建 UDP 套接字:

 // 创建 UDP 套接字
  int sockfd;
  if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
  {
   
   
      perror("Socket creation failed");
      exit(EXIT_FAILURE);
  }

  “socket(AF_INET, SOCK_DGRAM, 0)”这个函数调用是用来创建一个套接字。

  “AF_INET”意思是使用 IPv4 地址格式

  “SOCK_DGRAM”表示创建的UDP数据报套接字,数据以独立的包发送,可能无序、丢失或重复。

  “0”让系统自动选择适合前面设置的默认传输协议,这里就是 UDP 协议。

  函数成功会返回一个套接字描述符,用于后续操作;失败则返回 -1 。

  

绑定端口号:

  struct sockaddr_in servaddr, cliaddr;
  memset(&servaddr, 0, sizeof(servaddr));
  memset(&cliaddr, 0, sizeof(cliaddr));

  // 填充服务器地址信息
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = INADDR_ANY;
  servaddr.sin_port = htons(PORT);

  // 绑定套接字到指定端口
  if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
   
   
      perror("Bind failed");
      exit(EXIT_FAILURE);
  }

  我们首先定义了两个 struct sockaddr_in 类型的结构体 servaddr(服务器地址)和 cliaddr(客户端地址)。

  然后使用 memset 函数将这两个结构体的内存空间初始化为 0,以清除可能存在的垃圾数据。

  接下来为服务器地址结构体 servaddr 进行填充:

  servaddr.sin_family 设为 AF_INET,表示使用 IPv4 地址格式

  servaddr.sin_addr.s_addr 设为 INADDR_ANY,表示服务器可以接收来自任何本地 IPv4 地址的连接请求

  servaddr.sin_port 通过 htons 函数将指定的端口号(比如前面定义的 PORT)转换为网络字节序并进行设置。

  最后,使用 bind 函数将创建的套接字 sockfd 与填充好的服务器地址 servaddr 进行绑定。如果绑定失败,会打印错误信息并退出程序。

  

网络字节序:

  网络字节序是网络数据流的规定格式,采用大端字节序(低地址高字节)。

  TCP/IP 协议要求网络数据按这种格式传输。发送主机按内存地址从低到高发送数据,接收主机也按内存地址从低到高保存数据。

  不管主机是大端还是小端字节序,都要按网络字节序发送/接收数据。小端主机需转换数据,大端主机可直接发送。

  为让网络程序在不同主机都能正常运行,可调用库函数进行网络字节序和主机字节序转换,htons、htonl、ntohs、ntohl 。

  

sockaddr结构:

  Socket API 是适用于多种底层网络协议的抽象编程接口,如 IPv4、IPv6 及 UNIX Domain Socket 。IPv4 地址用 sockaddr_in 结构体表示,其包含地址类型、端口号和 IP 地址。

  IPv4、IPv6 地址类型分别定义为常数 AF_INET 、 AF_INET6 。Socket API 都能用 struct sockaddr * 类型表示,使用时需强制转化为 sockaddr_in ,好处是使程序具有通用性,能接收各种类型的 sockaddr 结构体指针作为参数。

  

  sockaddr_in 结构:

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

  

  in_addr结构:

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

  

1.1 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 <unordered_map>
#include "Log.hpp"

// using func_t = std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string &, const std::string &, uint16_t)> 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
        // 2. 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 CheckUser(const struct sockaddr_in &client, const std::string clientip, uint16_t clientport)
    {
   
   
        auto iter = online_user_.find
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鳄鱼麻薯球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值