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

Socket echo server

目录

一 前置知识

1. IP地址/端口号/网络字节序

2. Socket常见API

3. sockaddr结构

二 UDP echosever

API接口

UDP_Client.cc

UDP_Server.cc

UDP_Server.hpp

三 TCPechosever

API接口


一 前置知识

1. IP地址/端口号/网络字节序

在网络中,表示一个主机的唯一性就是IP地址,要跨网络传输,必须先要知道目的主机的IP地址才能发送过去。

当目的主机收到数据,交给对应的进程,在系统中标识进程的唯一性是PID,但在网络中标识进程的唯一性是端口号,那么为什么直接复用系统的PID呢?

最主要的原因:如果网络也按照PID标识进程的唯一性,那么系统就和网络关联起来了,有一方如果要改PID会影响到另一方,所以为了避免这种互相牵连的问题,才有了端口号。

1. 端口号可以指定

2. 一个进程可以绑定多个端口号,一个端口号只能被一个进程使用

端口号取值范围:0 ~ 65535,其中 0 ~ 1023 标识知名端口号,比如 110,120,119,说110就知道报警,说报警就知道110。知名端口号比如:HTTP(80),HTTPS(443),DNS(53)。

不同的主机存储数据方式可能是大端也可能是小端,如果不按统一的方式读取,可能读到的数据是乱序的,所以为了解决这类问题,在网络传输中默认使用大端。

2. Socket常见API
// 创建 socket 文件描述符
int socket(int domain, int type, int protocol);
domain:表示进行什么通信
  • AF_UNIX:本地通信
  • AF_INET:网络通信
type:表示以什么类型进行通信
  • SOCK_STREAM:流式传输
  • SOCK_DGRAM:数据包传输
protocol:表示套接字采用什么协议,一般domain和type就能表示具体的协议了,所以一般设置为0
// 绑定端口号 (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):客户端向服务器发起连接
3. sockaddr结构
struct sockaddr {sa_family_t sa_family;  // 地址族(如 AF_INET、AF_UNIX)char        sa_data[14]; // 协议特定的地址信息(如 IP + 端口)
};

一个通用的套接字地址结构,用来接收不同的通信方式,并强转提取第一个字段 sa_family_t 判断。

struct sockaddr_in IPv4 套接字地址结构,表示网络传输

struct sockaddr_in {sa_family_t    sin_family; // 地址族(必须为 AF_INET)in_port_t      sin_port;   // 16 位端口号(网络字节序)struct in_addr sin_addr;   // 32 位 IPv4 地址(网络字节序)char           sin_zero[8]; // 填充字段(未使用)
};struct in_addr {uint32_t s_addr; // IPv4 地址(32 位,网络字节序)
};

struct sockaddr_un Unix 域套接字地址结构,表示本地通信

struct sockaddr_un {sa_family_t sun_family;    // 地址族(必须为 AF_UNIX/AF_LOCAL)char        sun_path[108]; // 文件系统路径(如 "/tmp/my_socket")
};

因为他们的第一个字段是一样的,所以可以用 sockaddr_in和sockaddr_un强转成sockaddr结构,并提取第一个字段判断是AF_INET还是AF_UNIX选择强转成sockaddr_in还是sockaddr_un结构,也就是C语言版的继承和多态。

二 UDP echosever

API接口
  • 创建套接字 > socket(int AF_INET, int SOCK_DGRAM ,int 0);
  • 创建 struct sockaddr结构,并填充字段,通过bind和socket的返回描述符关联起来
int bind( int sockfd,                   // socket返回的描述符const struct sockaddr *addr,  // 主机信息socklen_t addrlen             // 他的长度);
  • sendto:发送消息
sendto( int sockfd,                          // 套接字的返回值const void *buf,                     // 发送缓冲区size_t len,                          // 缓冲区大小int flags,                           // 额外选项const struct sockaddr *dest_addr,    // 发给谁,通用sockaddr 结构,目的地址socklen_t addrlen                    // sockaddr的地址);
  • recvfrom:接收消息
ssize_t recvfrom(int sockfd,                   // 套接字描述符void *buf,                    // 接收缓冲区size_t len,                   // 缓冲区大小int flags,                    // 读方式struct sockaddr *src_addr,    // 谁发的socklen_t *addrlen            // 他的长度);

端口号转大端API:htons();

IP地址转大端API:inet_addr();

代码示例:

UDP_Client.cc
// UDP_Client.cc#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include <cstring>
int main(int argc,char* argv[])
{if(argc<3){std::cout<<"argc < 3 "<<std::endl;exit(-1);}// 创建套接字int fd=socket(AF_INET,SOCK_DGRAM,0);// 创建网络结构struct sockaddr_in sk;memset(&sk,0,sizeof(sk));// 填充信息并转大端sk.sin_family=AF_INET;sk.sin_port=htons(std::stoi(argv[2]));sk.sin_addr.s_addr=inet_addr(argv[1]);socklen_t sklen=sizeof(sk);std::cout<<argv[1]<<" "<<argv[2]<<std::endl;// 当首次调用sendto ,客户端会自动进行bind IP地址和端口号,所以不需要显示的bind// 后续的读写操作和 server.hpp 一样while(1){std::string ss = "client sendto: ";std::string mes;std::getline(std::cin,mes);ss+=mes;int n=sendto(fd,ss.c_str(),ss.size(),0,(struct sockaddr*)&sk,sklen);if(n<0){std::cout<<"client sengto failed "<<std::endl;exit(-1);}struct sockaddr des;socklen_t deslen;char buff[1024];n=recvfrom(fd,buff,sizeof(buff),0,&des,&deslen);if (n < 0){std::cout<<"client sengrecvfromto failed "<<std::endl;exit(-1);}else if(n==0)break;else{buff[n]=0;std::cout<<buff<<std::endl;}}close(fd);return 0;
}
UDP_Server.cc
#include "UDP_Server.hpp"int main(int argc,char* argv[])
{if(argc<2){std::cout<<"argc < 3 "<<std::endl;exit(-1);}// 提取IP地址std::string s=argv[1];// 传入端口号和IP地址构造UDP_server user(/*字符串转整数*/atoi(argv[2]),s);// 初始化创建套接字,binduser.Init();// 读写数据user.start();return 0;
}
UDP_Server.hpp
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include <cstring>
class UDP_server
{
public:// 初始化端口号和IP地址UDP_server(uint16_t port, const std::string &ip) : _port(port), _ip(ip){}~UDP_server(){// 关闭套接字文件if (_socked != -1)close(_socked);}void Init(){// 创建套接字文件_socked = socket(AF_INET, SOCK_DGRAM, 0);if (_socked < 0){std::cout << "socket failed" << std::endl;exit(-1);}// 创建网络结构struct sockaddr_in local;memset(&local, 0, sizeof(local));// 填充网络类型/端口号/IP地址,转大端local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());// 和socket文件关联起来,并让sockaddr知道是sockadr_in结构int n = bind(_socked, (struct sockaddr *)&local, sizeof(local));if (n < 0){std::cout << "bind failed" << std::endl;exit(-1);}}void start(){std::cout<<_ip<<" "<<_port<<std::endl;_Running = true;while (_Running){char buff[1024];struct sockaddr_in sk;socklen_t sklen = sizeof(sk);int n = recvfrom(_socked, buff, sizeof(buff)-1, 0, (struct sockaddr *)&sk, &sklen);// 读到了数据if (n > 0){buff[n]=0;std::cout<<buff<<std::endl;// 回显回去sendto(_socked,buff,sizeof(buff),0,(struct sockaddr*)&sk,sklen);}else if (n < 0){std::cout << "recvfrom failed" << std::endl;exit(-1);}else if (n == 0){std::cout << "client out" << std::endl;break;}}}private:// 套接字描述符int _socked;// 端口号uint16_t _port;// IP地址std::string _ip;bool _Running;
};

三 TCPechosever

API接口
int listen(    int sockfd,  // 套接字返回值 int backlog  // 全连接最大数量 ); // 监听事件
int accept(int sockfd,              // 套接字返回的描述符 struct sockaddr *addr,   // 发送方的信息socklen_t *addrlen       // 长度); // 等待连接
int connect(int sockfd,                       // 套接字返回的描述符const struct sockaddr *addr,      // 客户端信息socklen_t addrlen                 // 长度);  //向目的主机发起连接
ssize_t recv(int sockfd,  // accept返回值 void *buf,   // 接收缓冲区size_t len,  // 接收缓冲区大小int flags    // 怎么读);  // 接收数据
ssize_t send(int sockfd,       // accept的返回值 const void *buf,  // 发送缓冲区size_t len,       // 发送缓冲区大小int flags         // 怎么发);  // 发送数据

TCP和UDP通信大致逻辑相同,主要TCP要和目的主机建立连接来管理这条连接,也就是connect接口,而服务器bind之后要等待连接的到来,所以要监听listen,当连接到来要获取accpet。

1. 当server端初始化的时候,bind之后就需要listen进行监听事件,等待客户端建立连接。

2. 当server启动的时候,就需要accpet获取连接,也就是有连接来了,获取到的连接分配一个文件描述符来进行读写,所以要起多线程/多进程,不然会阻塞后续的accpet。

3. 客户端创建好套接字,然后向服务器请求连接connect。

3. 当客户端退出调用close(),server端也要close()accpet返回的文件描述符来关闭连接,避免文件描述符泄露。

相关文章:

  • 成功案例丨从草图到鞍座:用先进的发泡成型仿真技术变革鞍座制造
  • Tomcat 配置 HTTPS 访问全攻略(CentOS 环境)
  • 【愚公系列】《Manus极简入门》042-投资策略分析师:“投资智慧导航”
  • 从硬件角度理解“Linux下一切皆文件“,详解用户级缓冲区
  • CCIE与HCIE哪个考试难度更大?
  • Java EE初阶——wait 和 notify
  • AI与产品架构设计系列(2):Agent系统的应用架构与落地实
  • 【沉浸式求职学习day41】【Servlet】
  • 电脑出故障驱动装不上?试试驱动人生的远程服务支持
  • apisix透传客户端真实IP(real-ip插件)
  • 数字化工厂升级引擎:Modbus TCP转Profinet网关助力打造柔性生产系统
  • 【图像生成1】Latent Diffusion Models 论文学习笔记
  • uniapp实现在线pdf预览以及下载
  • Node.js 同步加载问题详解:原理、危害与优化策略
  • Linux du 命令终极指南:从基础到精通
  • Prometheus实战教程:k8s平台-Mysql监控案例
  • 15 C 语言字符类型详解:转义字符、格式化输出、字符类型本质、ASCII 码编程实战、最值宏汇总
  • gflags 安装及使用
  • 企业品牌宣传新闻媒体发稿策略与长效运营
  • 从验证码绕过到信息轰炸:全面剖析安全隐患与防范策略
  • 受关税政策影响,沃尔玛将上调部分商品在美售价
  • 自强!助残!全国200个集体和260名个人受到表彰
  • 农行再回应客户办理业务期间离世:亲属连续三次输错密码,理解亲属悲痛,将协助做好善后
  • “女硕士失踪13年生两孩”案进入审查起诉阶段,哥哥:妹妹精神状态好转
  • 上海市重大工程一季度开局良好,崇明线等按既定计划加快建设
  • 220名“特朗普币”持有者花1.48亿美元,获邀与特朗普共进晚餐