当前位置: 首页 > backend >正文

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;// 开始bindint 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 socketstruct 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;
}

http://www.xdnf.cn/news/3333.html

相关文章:

  • Day.js一个2k轻量级的时间日期处理库
  • Modbus转PROFIBUS网关:电动机保护新突破!
  • [CPCTF 2025] Crypto
  • YOLOv11改进:视觉变换器SwinTransformer目标检测网络
  • C 语言链表详解
  • 第 11 届蓝桥杯 C++ 青少组中 / 高级组省赛 2020 年真题答和案解析
  • 测试 用例篇
  • 指令级并行(ILP)和线程级并行(TLP)的区别,GCC -O3优化会展开循环吗?
  • Git 忽略文件配置 .gitignore
  • AI对IT行业的重塑:挑战与机遇并存的技术革命
  • URP - 序列图动画的实现
  • 多数元素题解(LC:169)
  • 扩展根分区
  • 软件产品测试报告:如何全面评估及保障软件质量?
  • kubernetes》》k8s》》Service 、Ingress 区别
  • C 语 言 - - - 动 态 内 存 分 配
  • SIwave基本操作之S参数仿真
  • 5. 进程地址空间
  • react中封装一个预览.doc和.docx文件的组件
  • Vue3 + TypeScript 实现 PC 端鼠标横向拖动滚动
  • 【蓝桥杯】第十六届蓝桥杯C/C++大学B组个人反思总结
  • 高性能架构设计-数据库(读写分离)
  • OpenHarmony - 小型系统内核(LiteOS-A)(十七)标准库
  • 加速LLM大模型推理,KV缓存技术详解与PyTorch实现
  • java: 警告: 源发行版 21 需要目标发行版 21
  • PostgreSQL的COALESCE 函数用法
  • 慧星云支持 Qwen3:开启智算新生态,共筑高效 AI 未来
  • WebGL图形编程实战【5】:层次构建 × Shader初始化深度剖析
  • 基于ssm的校园旧书交易交换平台(源码+文档)
  • Microsoft Entra ID 详解:现代身份与访问管理的核心