Socket编程实现UDP通信

1.建立服务端

1.1创建套接字(本质是创建了一个文件)

套接字=IP地址+端口号

第一个参数详解

第二个参数详解

socket有指定的类型,该类型确定了通信的语义(规则)

SOCK_STREAM 提供有序、可靠、双向(全双工)、基于连接的字节流。two-way:翻译为全双工,意思是连接一旦建立成功双方可以同时发数据和接收数据;

返回值:

成功返回一个文件描述符fd,失败返回-1;

1.2 绑定

端口号的作用究竟是什么呢?

IP地址可以确定在哪一台机器上,而端口号的作用是确定是哪个程序(进程),所以服务器绑定自己的ip+端口号是为了让客服端精中无误的找到自己这个客户端程序,所以一个端口号只能绑定一个进程,但是一个进程可以绑定多个端口号;

在创建服务器时,bind 绑定的 IP 和端口号,是服务器自身的 IP 和端口 ,而非客户端的。服务器通过 bind 将自身地址注册给操作系统,明确告诉系统 "我要在这个 IP、这个端口上监听客户端请求",让客户端能通过固定地址找到并连接它;而客户端无需主动绑定地址,调用 connect 时操作系统会自动为其分配临时端口和本机 IP,客户端只需知道服务器地址即可主动发起通信,服务器后续能通过客户端发来的数据包自动识别其地址,从而实现双向通信。服务器不会临时分配端口,回复消息依然使用 bind 绑定的固定端口;只有客户端不 bind,由系统自动分配临时端口。

客户端的 IP 地址由本机网卡决定,通信过程中始终固定不变 ;端口则由操作系统自动随机动态分配,且分配规则由连接状态与目标决定,因为客户端并不需要被找到,只需要具有唯一性即可,并且客户端如果自己绑定端口号很容易造成匹配冲突,使得客户端启动不起来,与同一服务器建立长连接时,客户端在连接生命周期内只会使用同一个临时端口,直到连接断开才会被系统回收;与不同服务器通信时,操作系统会为每条新连接的客户端分配独立的临时端口,以保证四元组(源 IP + 源端口 + 目标 IP + 目标端口)不冲突,实现多进程、多连接的并行通信;即使断开后重连同一服务器,系统也大概率会分配新的临时端口,而非强制复用旧端口。

绑定成功返回0,否则返回1,并设置错误码;

值得注意的是不同CPU存储数字的方式并不完全一样,我们必须要将我们的Ip+port(端口)转成大家都能认识的字节序,不然对方很有可能根本读不懂我们的IP+port;而世界上只有两种CPU,一种是小端序,一种是大端序,绝大多数的电脑使用的是小端序,即低地址放在前面,少数系统和网络设备使用的是大端序,即高地址放在前面,为了防止冲突发生,网络规定全世界使用一种格式即网络字节序,也就是大端序,无论任何设备,手机,MAC,window,linux,在发送数据的时候都要转成网络字节序(大端序),接收收据之后都要转成主机字节序(小端序);以下是对应的API

而我们在写IP的时候由于IP是数字加字符的格式,所以在初始化的时候我们只能使用string(char *),但是网络中需要的uint32_t,所以我们得使用Linux给我们已经定义好的函数来进行转换

我们发现sin_addr底层是struct,结构体只能整体初始化,不能赋值

1.3发送和接收数据

1.3.1API

注意:recvfrom的struct * src_addr 和socklen_t * addrlen是输入形参数;

1.3.2 netstat

netstat是Linux系统的网络查看工具,主要用于查看本机端口占用情况、TCP/UDP连接状态、服务监听状态与网络统计数据,常用来排查bind失败、端口冲突、服务未正常监听等网络编程问题,辅助快速定位网络程序运行异常。

bash 复制代码
# 查看所有TCP监听端口(最常用)
netstat -lt

# 查看所有TCP+UDP端口
netstat -lut

# 显示端口号+进程PID
netstat -ltp

# 查看所有连接,包含TIME_WAIT
netstat -at

2.建立客户端

客户端必须手动提前知道 / 写死服务器的 IP 与端口号,才能主动发起 connect 连接;服务器提前通过 bind 绑定固定端口并监听,IP 一般填本机地址或公网地址,不会主动把自身 IP、端口发给客户端,而是被动等待连接,客户端依靠预先配置的服务端地址信息,精准定位并访问服务端进程。

值得注意的是客户端绑定的服务器不一定只有一个;

3.服务器是否应该绑定确定的IP?

4.建立UDP通信的服务器端实现

cpp 复制代码
#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__

#include <iostream>
#include <memory>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <functional>
#include "log.hpp"
#include "conmmen.hpp"
#include "InetAddr.hpp"

using namespace LogModule;

using func_t = std::function<std::string(const std::string&)>;
const static int gsocketfd =-1;
const static std::string gdefaultIp="127.0.0.1";             //表示你当前正在用的主机ip,也叫本地环回地址,通常用于做代码测试
const static uint16_t gdefaultport=8081;
class UdpServer
{
    public:
    UdpServer(func_t func,uint16_t port=gdefaultport)
    :_sockfd(gsocketfd)
    ,_func(func)
    ,_addr(port)
    ,_isrunning(false)
    {}

    void initUdpServer()
    {
        //1.创建套接字
        _sockfd=socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd < 0)
        {
            LOG(loglevel::FATAL)<<"socket:"<<strerror(errno);
            Die(SOCKET_ERR);
        }
        LOG(loglevel::INFO)<<"创建socket成功,socket:"<<_sockfd;


        //2.绑定端口号和IP
        //2.1填充端口号和IP信息
        // struct sockaddr_in local;                                      //sockaddr_in 是 Linux 网络编程里定义 IPv4 地址的结构体
        // local.sin_family=AF_INET;
        // local.sin_port= htons(_port);
        // local.sin_addr.s_addr= INADDR_ANY;
        //2.2绑定  -> 设置到内核中
        int n = bind(_sockfd,_addr.ofNetAddr(),_addr.ofNetAddrLen());
        if( n < 0)
        {
            LOG(loglevel::FATAL)<<"bind:"<<strerror(errno);
            Die(BIND_ERR);
        }
        LOG(loglevel::INFO)<<"绑定成功";
    }
    void start()
    {
        _isrunning=true;
        while(1)
        {
            char inbuffer[1024];  //输入缓冲区
            struct sockaddr_in  peer;
            socklen_t len = sizeof(peer);   
            ssize_t n= recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,cast(&peer),&len);   //&len这个接受形参数是为了确定接收回来的缓冲区的大小,输出形参数,所以必须用指针
            // uint16_t clientport=::ntohs(peer.sin_port);
            // std::string clientip=::inet_ntoa(peer.sin_addr);
            InetAddr client(peer);
            //接收成功返回的是接收数据的字节数
            if(n>0)
            {
                inbuffer[n]=0;    //清空缓冲区
                std::string clientinfo = client.Ip() + ":"+ std::to_string(client.Port())+inbuffer;
                LOG(loglevel::DEBUG)<<clientinfo;;
                //std::cout<<clientport<<clinetip<<std::endl;
                // std::string echo_string="echo#";
                // std::string message;
                // std::getline(std::cin,message);
                // echo_string += message;
                std::string result = _func(inbuffer);
                ::sendto(_sockfd,result.c_str(),result.size(),0,cast(&peer),sizeof(peer));
            }
        }
        _isrunning =false;
    }
    ~UdpServer()
    {
        if(_sockfd > gsocketfd)
        {
            ::close(_sockfd);
        }
    }
    
    private:
    int _sockfd;
    uint16_t _port;
    std::string _ip;
    bool _isrunning;
    InetAddr _addr;
    func_t _func;

};

#endif
相关推荐
切糕师学AI1 小时前
Remmina:Linux 平台的全能远程桌面客户端详解
linux·运维·远程控制·远程桌面·remmina
dualven_in_csdn1 小时前
【assist】 需要用到的方法
linux·运维·服务器
minji...1 小时前
Linux 网络基础(二)HTTP协议,域名,URL,URI,认识HTTP的请求和响应
linux·服务器·网络·网络协议·http·tcp
05候补工程师1 小时前
[408考研笔记] 传输层与网络层核心辨析:从逻辑通信到滑动窗口计算
网络·经验分享·笔记·网络协议·tcp/ip·考研·ip
萑澈2 小时前
Linux内核安全态势报告:2021-2026年高危漏洞演进与深度技术分析
linux·ubuntu
diangedan2 小时前
focuswindow
linux·运维·服务器
林熙蕾LXL2 小时前
文件IO操作
linux
minji...2 小时前
Linux 网络套接字编程(八)自定义实现 HTTP 服务器,HTTP 的工作模式
linux·服务器·网络·http·udp·tcp
~黄夫人~2 小时前
Kubernetes 入门到实战:概念详解 + kubeadm 安装 + 节点克隆全流程
linux·运维·学习·k8s·集群