Socket-UDP

Socket(套接字 )是计算机网络中用于实现进程间通信的重要编程接口,是对 TCP/IP 协议的封装 ,可看作是不同主机上应用进程之间双向通信端点的抽象。以下是详细介绍:

作用与地位

  • 作为应用层与传输层、网络层协议间的中间抽象层,屏蔽了网络通信底层如 TCP/IP 协议的复杂细节,让开发者只需通过简单接口就能实现网络通信 。例如开发网络聊天程序时,借助 Socket 接口,无需深入了解网络协议具体实现就能完成消息收发 。
  • 向上为应用进程提供数据交换机制,向下连接网络协议栈,是应用程序与网络协议交互的接口。

类型

  • 流式套接字(SOCK_STREAM ):基于 TCP 协议 ,提供面向连接、可靠的数据传输服务。通信前需建立连接,保证数据无差错、无重复发送且按序接收,还内置流量控制机制。适用于对数据准确性和顺序要求高的场景,如文件传输、网页浏览、数据库连接等 。
  • 数据报套接字(SOCK_DGRAM ):基于 UDP 协议 ,是无连接的,不保证数据可靠传输,数据包可能丢失、重复或乱序。但传输速度快,适合实时性要求高、能容忍少量数据丢失的场景,如视频直播、在线游戏、实时音频通信等 。
  • 原始套接字(SOCK_RAW ):允许直接访问较低层次协议,如 IP、ICMP 等 。可用于开发自定义网络协议、网络嗅探、网络攻击检测等特殊网络应用,但使用难度大且需较高权限 。

工作流程(以常见的 C/S 模式为例 )

  • 服务端
    1. 创建 Socket 对象:调用 socket 函数创建用于监听的套接字 。
    2. 绑定地址信息:通过 bind 函数将 Socket 与特定 IP 地址和端口号绑定,使服务端在该地址和端口监听 。
    3. 监听连接请求:使用 listen 函数设置监听队列,准备接受客户端连接 。
    4. 接受连接:调用 accept 函数阻塞等待,一旦有客户端连接请求,就创建新的 Socket 用于与该客户端通信 。
    5. 数据交互:通过新 Socket 的 recv 和 send 等函数进行数据接收和发送 。
    6. 关闭连接:通信结束后,调用 close 函数关闭 Socket 。
  • 客户端
    1. 创建 Socket 对象:同样调用 socket 函数创建 Socket 。
    2. 发起连接请求:使用 connect 函数连接到服务端指定的 IP 地址和端口号 。
    3. 数据交互:连接成功后,用 send 和 recv 等函数进行数据收发 。
    4. 关闭连接:完成通信后,调用 close 函数关闭 Socket 。

下面代码是对Socket一些接口函数的模拟:

#pragma once

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include"Log.hpp"
#include <string.h>
using namespace std;

Log log;
const int backlog=10;//backlog 参数的值决定了服务器可以排队等待处理的最大连接请求数量
enum{
    SocketError=2,
    BindError,
    ListenError,
    AcceptError
};
class Sock
{
public:
    Sock()
    {}
    ~Sock()
    {}
public:
    void Socket()
    {
        int sockfd=socket(AF_INET,SOCK_STREAM,0);
        if(sockfd<0)
        {
            log.logmessage(fatal,"socket fail,errno:%d,strerr:%s",errno,strerror(errno));
            exit(SocketError);
        }
        // 通过设置 SO_REUSEADDR 选项 ,使用 setsockopt 函数可以开启端口复用功能,
        // 允许套接字绑定到处于 TIME_WAIT 状态的端口 ,使得服务器能快速重启并继续使用原端口提供服务 。
        int opt=1;
        setsockopt(_sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
        log.logmessage(info,"socket success,errno:%d,strerr:%s",errno,strerror(errno));

    }
    void Bind(uint16_t port)
    {
        struct sockaddr_in local;
        bzero(&local,sizeof(local));
        local.sin_family=AF_INET;
        local.sin_port=htons(port);
        local.sin_addr.s_addr=INADDR_ANY;

        // 开始bind
        int b=bind(_sockfd,(const sockaddr*)&local,sizeof(local));
        if(b<0)
        {
            log.logmessage(fatal,"bind fail,errno:%d,strerr:%s",errno,strerror(errno));
            exit(BindError);
        }
        log.logmessage(info,"bind success,errno:%d,strerr:%s",errno,strerror(errno));
    }
    void Listen()
    {
        if(listen(_sockfd,backlog)<0)
        {
            log.logmessage(fatal,"listen fail,errno:%d,strerr:%s",errno,strerror(errno));
            exit(ListenError);
        }
        log.logmessage(info,"listen success,errno:%d,strerr:%s",errno,strerror(errno));
    }
    int Accept(string *client_ip,uint16_t *client_port)//带*是输出型参数
    {
        struct sockaddr_in client;
        socklen_t len=sizeof(client);
        int newfd=accept(_sockfd,(sockaddr*)&client,&len);
        if(newfd<0)
        {
            log.logmessage(fatal,"accept fail,errno:%d,strerr:%s",errno,strerror(errno));
            // exit(AcceptError);
            return -1;
        }
        log.logmessage(info,"accept success,errno:%d,strerr:%s",errno,strerror(errno));

        char clientIP[64];
        inet_ntop(AF_INET,&client,clientIP,sizeof(clientIP));
        *client_ip=clientIP;
        *client_port=ntohs(client.sin_port);
        
        return newfd;
    }
    bool Connect(const string&serverip,const uint16_t port)
    {
        struct sockaddr_in peer;
        bzero(&peer,sizeof(peer));
        peer.sin_family=AF_INET;
        peer.sin_port=htons(port);
        inet_pton(AF_INET,serverip.c_str(),&(peer.sin_addr));

        int c=connect(_sockfd,(const sockaddr*)&peer,sizeof(peer));
        if(c<0)
        {
            cout<<"Link fail..."<<endl;
            return false;
        }

        return true;
    }

    void Close()
    {
        close(_sockfd);
    }
    int fd()
    {
        return _sockfd;
    }
private:
    int _sockfd;
};

其中有一个setsockopt这一行代码的目的是:使得服务器能快速重启并继续使用原端口提供服务 。

UDP 的全称是 User Datagram Protocol,即用户数据报协议 ,是 OSI 参考模型中一种无连接的传输层协议 ,提供面向事务的简单不可靠信息传送服务 。它能让应用程序在无需建立连接的情况下,发送封装的 IP 数据包 。

UDP的代码案例:客户端:

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cstdlib>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <pthread.h>
using namespace std;

struct ThreadData
{
    struct sockaddr_in server;
    int sockfd;
};

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << "serverip serverport\n"
              << std::endl;
}
void* recv_message(void*argc)
{
    ThreadData* td=static_cast<ThreadData*>(argc);
    char buffer[1024];
    while(true)
    {
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&tmp, &len);
        if (s > 0)
        {
            buffer[s] = 0;
            cout << buffer << endl;
        }
    }
    return nullptr;
}

void* send_message(void * argc)
{
    // int *sockfd=static_cast<int*>(argc);
    ThreadData* td=static_cast<ThreadData*>(argc);
    string message;
    socklen_t len = sizeof(td->server);
    while(true)
    {
        cout << "Please Enter@ ";
        getline(cin, message);
        // 1. 数据 2. 给谁发
        sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->server), len);
    }
    return nullptr;
}
int main(int argc, char *argv[])
{
    if (argc != 3) // 这里就是看main是不是收到了两个参数,一个是程序名一个是端口号!
    {
        Usage(argv[0]);
        exit(0);
    }
    string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct ThreadData td;
    // struct sockaddr_in server;

    bzero(&td.server, sizeof(td.server));
    td.server.sin_family = AF_INET;
    td.server.sin_port = htons(serverport); //?
    td.server.sin_addr.s_addr = inet_addr(serverip.c_str());
    // socklen_t len = sizeof(td.server);
    td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (td.sockfd < 0)
    {
        cout << "socket fail" << endl;
        return 1;
    }

    // client要bind吗?要,但是它不需要显示的进行bind绑定!一般由操作系统进行随机绑定!
    // 原因就是防止端口号相同导致进程起不来,例如dy开了,ks就开不了!
    // 其实客户端的port不重要只要它能保证唯一性就可以!
    // 在系统服务端首次sendto的时候客户端开始绑定


    pthread_t recv,sender;
    pthread_create(&recv,nullptr,recv_message,&td);
    pthread_create(&sender,nullptr,send_message,&td);

    //对下面单线程进行拆分,让它不会在getline处造成阻塞,解决的办法就是拆分成多线程一个线程进行输出,一个线程用来接收!
    // string message;
    // char buffer[1024];
    // while (true)
    // {
    //     // cout << "Please Enter@ ";
    //     // getline(cin, message);
    //     // // 1. 数据 2. 给谁发
    //     // sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len);
    //     // struct sockaddr_in tmp;
    //     // socklen_t len = sizeof(tmp);
    //     // ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr *)&tmp, &len);
    //     // if (s > 0)
    //     // {
    //     //     buffer[s] = 0;
    //     //     cout << buffer << endl;
    //     // }
    // }
    pthread_join(recv,nullptr);
    pthread_join(sender,nullptr);
    close(td.sockfd);

    // 一个端口号只能有一个进程进行绑定!!!!
    return 0;
}

服务端:

#pragma once
#include <iostream>
#include "Log.hpp"
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <functional>
#include <stdio.h>
#include <unordered_map>
using namespace std;

using func_t = std::function<std::string(const std::string &, const std::string &, std::uint16_t)>; // 返回值是string参数是const string
extern Log log;
enum
{
    SOCK_ERROR = 1,
    BIND_ERROR
};

uint16_t defaultport = 8080;
const int size = 1024;
string defaultip = "0.0.0.0"; // 0的ip默认就是接收所有可用网络的信息!
class UdpServer
{
public:
    UdpServer(const uint16_t &port = defaultport, const string &ip = defaultip) : _port(port), _ip(ip)
    {
    }
    void Init()
    {
        // 1.创建一个Udp socket
        // UDP的socket是全双工的!就是允许被同时读写的
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            log.logmessage(fatal, "socket create fail,socket:%d", sockfd);
            exit(SOCK_ERROR);
        }
        log.logmessage(info, "socket create success,socket:%d", sockfd);
        // 2.bind socket
        struct sockaddr_in _local; // 这个结构体实例出来是用于下面系统调用bind绑定时候告诉os要通信的具体信息
        bzero(&_local, sizeof(_local));
        // memset(&_local, 0, sizeof(_local));
        _local.sin_family = AF_INET;    // 表示使用 IPv4 地址族
        _local.sin_port = htons(_port); // 把我们主机序列转换为端口序列,如果你本身就是大端那不发生变化,如果是小端会自动转换为大端!
        // 使用htons函数将主机字节序的端口号转换为网络字节序,这样在网络传输时,接收方就能正确解析端口号
        // _local.sin_addr.s_addr=inet_addr(_ip.c_str());//1.string-》unit32_t 2.他必须是网络序列!
        _local.sin_addr.s_addr = INADDR_ANY;
        int n = bind(sockfd, (const sockaddr *)&_local, sizeof(_local));
        if (n < 0)
        {
            log.logmessage(fatal, "bind fail,errno:%d ,erron string:%s", errno, strerror(errno));
            exit(BIND_ERROR);
        }
        log.logmessage(info, "bind success,errno:%d ,erron string:%s", errno, strerror(errno));
    }
    void CheckUser(const struct sockaddr_in &client, const string &Client_ip1, uint16_t Client_port1)
    {

        auto iter = onlineUser.find(Client_ip1);
        if (iter == onlineUser.end())
        {
            onlineUser.insert({Client_ip1, client});
            cout << "[" << Client_ip1 << ": " << Client_port1 << "]Add Online User" << endl;
        }
    }
    void BroadCast(const string &_info, const string &Client_ip1, uint16_t Client_port1)
    {
        for (const auto &user : onlineUser)
        {
            std::string message = "[";
            message += Client_ip1;
            message += ":";
            message += std::to_string(Client_port1);
            message += "]#";
            message += _info;
            socklen_t len = sizeof(user.second);
            sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)(&user.second), len);
        }
    }
    void Run(func_t func)
    {
        _isRunning = true;
        char buffer[size];
        while (_isRunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&client, &len);
            if (n < 0)
            {
                log.logmessage(warn, "recvform fail,errno:%d ,erron string:%s", errno, strerror(errno));
                continue;
            }

            uint16_t Client_port1 = ntohs(client.sin_port); // 这是把网络字节序列转换为主机序列
            // ip地址本身是点分十进制的
            string Client_ip1 = inet_ntoa(client.sin_addr); // 把ip地址转化为字符串!这里返回的其实是字符串的首地址
            // 检查用户是新用户?
            CheckUser(client, Client_ip1, Client_port1);
            string _info = buffer;
            BroadCast(_info, Client_ip1, Client_port1);
            // 接收到数据后--充当一次数据处理
            //  buffer[size]=0;
            //  string _info=buffer;
            //  string echo_string=func(info,Client_ip1,Client_port1);
            //  // string echo_string="server echo##"+_info;
            //  cout<<echo_string<<endl;
            //  // 发送回去
            //  sendto(sockfd,echo_string.c_str(),echo_string.size(),0,(const sockaddr*)&client,len);
        }
    }
    ~UdpServer()
    {
        if (sockfd > 0)
        {
            close(sockfd);
        }
    }

private:
    int sockfd;     // 网络文件描述符!
    string _ip;     // 任意地址绑定就是 全0;
    uint16_t _port; // 表明服务器进程的端口号!
    bool _isRunning;
    unordered_map<string, struct sockaddr_in> onlineUser; // K代表主机ip,V代表的是用户网络信息!
};

main.c:

#include "udpserver.hpp"
#include <memory>
#include <stdio.h>
#include<vector>

Log log;
void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n"
              << std::endl;
}
std::string Handler(const std::string &str,const string &Client_ip1,uint16_t Client_port1 )
{
    cout<<"["<<Client_ip1<<": "<<Client_port1<<"]##"<<endl;
    std::string res = "Server get a message: ";
    res += str;
    cout << res << endl;
    return res;
}
bool SafeCheck(const string& cmd)
{
    int safe=false;
    std::vector<std::string> key_word = {
        "rm",
        "mv",
        "cp",
        "kill",
        "sudo",
        "unlink",
        "uninstall",
        "yum",
        "top",
        "while"
    };
    for(auto &word:key_word)
    {
        auto pos=cmd.find(word);
        if(pos!=string::npos)
        {
            return false;
        }
    }

    return true;

}
std::string ExecuteCommand(const std::string &cmd)
{
    cout<<"get a request :"<<cmd<<endl;

    if(!SafeCheck(cmd)) return "Bad Man";
    FILE *fp = popen(cmd.c_str(), "r");
    if (nullptr == fp)
    {
        perror("popen");
        return "error";
    }
    std::string result;
    char _buffer[4096];
    while (true)
    {
        char *r = fgets(_buffer, sizeof(_buffer), fp);
        if (r == nullptr)
            break;
        result += _buffer; // 正确的字符串拼接方式,将读取内容追加到result
    }

    pclose(fp);
}
int main(int argc, char *argv[])
{
    if (argc != 2) // 这里就是看main是不是收到了两个参数,一个是程序名一个是端口号!
    {
        Usage(argv[0]);
        exit(0);
    }

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

    std::unique_ptr<UdpServer> svr(new UdpServer(port/*,"127.0.0.1"*/));
    // std::unique_ptr<UdpServer> svr = std::make_unique<UdpServer>(port,"127.0.0.1");

    svr->Init(/***/);
    svr->Run(Handler);
    // svr->Run();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值