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

这篇具有很好参考价值的文章主要介绍了【网络编程】网络套接字&udp通用服务器和客户端。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.预备知识

认识端口号

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

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

认识TCP协议

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

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

认识UDP协议

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

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

网络字节序

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

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

【网络编程】网络套接字&udp通用服务器和客户端,Linux网络编程,网络,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结构

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

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结构

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

sockaddr_in结构

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

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

in_addr结构

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

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函数:

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

in_addr转字符串的函数:

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

其实inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void* addrptr。文章来源地址https://www.toymoban.com/news/detail-602673.html

到了这里,关于【网络编程】网络套接字&udp通用服务器和客户端的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JavaEE——网络编程(UDP套接字编程)

    概念: Socket 套接字就是操作系统给应用程序提供的网络编程 API。 我们可以认为 socket api 是和 传输层 密切相关的。 我们知道,在传输层中,提供了两个最核心的协议,UDP TCP。 因此,socket api 中也提供了两种风格。UDP TCP。 在这里我们简单认识一下 UDP 和 TCP : UDP : 无连接

    2024年02月13日
    浏览(60)
  • 网络编程套接字(3)——Java数据报套接字(UDP协议)

    目录 一、Java数据报套接字通信模型 二、UDP数据报套接字编程 1、DatagramSocket         (1)DatagramSocket构造方法         (2)DatagramSocket方法 2、DatagramPacket         (1)DatagramPacket构造方法         (2)DatagramPacket方法 3、InetSocketAddress 三、代码示例:回显服务

    2024年03月12日
    浏览(98)
  • 【Linux】网络基础+UDP网络套接字编程

    只做自己喜欢做的事情,不被社会和时代裹挟着前进,是一件很奢侈的事。 1. 首先计算机是人类设计出来提高生产力的工具,而人类的文明绵延至今一定离不开人类之间互相的协作,既然人类需要协作以完成更为复杂的工作和难题,所以计算机作为人类的工具自然也一定需要

    2024年02月08日
    浏览(63)
  • 网络编程『socket套接字 ‖ 简易UDP网络程序』

    🔭个人主页: 北 海 🛜所属专栏: Linux学习之旅、神奇的网络世界 💻操作环境: CentOS 7.6 阿里云远程服务器 在当今数字化时代,网络通信作为连接世界的桥梁,成为计算机科学领域中至关重要的一部分。理解网络编程是每一位程序员必备的技能之一,而掌握套接字编程则

    2024年02月04日
    浏览(57)
  • 【Linux网络】网络编程套接字(预备知识+UDP)

    目录 预备知识 1. 理解源IP地址和目的IP地址 2. 理解源MAC地址和目的MAC地址 3. 认识端口号  4. 理解源端口号和目的端口号 5. 端口号(port) vs 进程pid 6. 认识TCP协议和认识UDP协议 7. 网络字节序 socket编程接口  1. socket 常见API 2. sockaddr结构  简单的UDP网络程序  1. 服务端创建udp

    2024年02月19日
    浏览(58)
  • 网络编程套接字(2): 简单的UDP网络程序

    3.1 服务端创建 (1) 创建套接字 create an endpoint for communication: 创建用于通信的端点 关于socket参数详细介绍: (1) domain: 指定套接字的通信域,相当于 struct sockaddr结构体的前16比特位(2字节) domain的选项是以宏的形式给出的,我们直接选用即可。常用就是上面框住的两个: AF_UNIX,本

    2024年02月10日
    浏览(56)
  • JavaEE-网络编程套接字(UDP/TCP)

    下面写一个简单的UDP客户端服务器流程 思路: 对于服务器端:读取请求,并解析– 根据解析出的请求,做出响应(这里是一个回显,)–把响应写回客户端 对于客户端:从控制台读取用户输入的内容–从控制台读取用户输入的内容–从控制台读取用户输入的内容–将其显示在

    2024年02月07日
    浏览(62)
  • 【网络编程】详解UDP/TCP套接字的创建流程

    需要云服务器等云产品来学习Linux的同学可以移步/--腾讯云--/--阿里云--/--华为云--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。   目录 一、网络编程套接字 1、一些概念 1.1源IP地址和目的IP地址 1.2端口号port 1.3TCP和UDP的性质 1.4网络字节序、IP地址类型转换

    2024年02月05日
    浏览(53)
  • 【Linux Network】网络编程套接字(代码练习)—UDP

    目录 1. 常用接口 2. C/S 回声模拟 3. C/S myshell 的制作  Linux网络编程✨ 1. 常用接口 socket:创建套接字: 返回值: 套接字创建成功返回一个文件描述符 ,创建失败返回-1,同时错误码会被设置。 参数: domain: 网络通信 设置为 AF_INET(IPv4)或AF_INET6(IPv6) ; type:基于 UDP的网

    2024年02月03日
    浏览(95)
  • 【网络编程】网络编程概念,socket套接字,基于UDP和TCP的网络编程

    前言: 大家好,我是 良辰丫 ,今天我们一起来学习网络编程,网络编程的基本概念,认识套接字,UDP与TCP编程.💞💞💞 🧑个人主页:良辰针不戳 📖所属专栏:javaEE初阶 🍎励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。 💦期待大家三连,关注

    2023年04月20日
    浏览(61)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包