山东济宁网站建设设计,茶叶网站建站,免费app下载网站,廊坊网站建设#x1f4dd;个人主页#x1f339;#xff1a;Eternity._ ⏩收录专栏⏪#xff1a;Linux “ 登神长阶 ” #x1f339;#x1f339;期待您的关注 #x1f339;#x1f339; ❀Linux网络编程套接字 #x1f4d2;1. 端口号#x1f4dc;2. 初识TCP协议与UDP协议#x1… 个人主页Eternity._ ⏩收录专栏⏪Linux “ 登神长阶 ” 期待您的关注 ❀Linux网络编程套接字 1. 端口号2. 初识TCP协议与UDP协议3. 网络字节序4. socket编程socket 常见APIsockaddr结构封装 sockaddr_in封装 Udp_Socket封装 TCP_Socket 5. 进程守护6. TCP协议通讯流程⭐7. TCP 和 UDP 对比8. 总结 前言在当今这个信息技术日新月异的时代网络编程已成为连接世界、构建各类互联网应用不可或缺的一部分。而Linux作为开源操作系统的典范其强大的网络功能和灵活性为开发者们提供了一个广阔而深入的实践平台。在众多网络编程技术中套接字Socket编程无疑是核心与基石它不仅支撑着Web服务、即时通讯、在线游戏等日常应用还是实现分布式系统、云计算服务的关键技术之一。 在套接字编程的世界里UDP用户数据报协议与TCP传输控制协议如同双生子各自以其独特的优势占据着不同的应用场景。TCP以其可靠的连接导向、顺序传输和错误校正机制成为了追求数据传输完整性和稳定性的不二之选如文件传输、远程登录等场景而UDP则以其无连接、快速传输和较小的开销在实时性要求高、对丢包容忍度较大的场合如视频流、在线游戏等中大放异彩。 本文旨在深入探讨Linux环境下如何通过套接字编程技术驾驭UDP与TCP这两种强大的网络传输协议从零开始构建基础的网络通信能力。
让我们一同踏上这段探索之旅揭开Linux网络编程的神秘面纱领略UDP与TCP的魅力所在共同构建更加智能、互联的世界 1. 端口号 概念 端口号(port)是传输层协议的内容 端口号是一个2字节16位的整数端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理IP地址 端口号能够标识网络上的某一台主机的某一个进程一个端口号只能被一个进程占用 注意端口号和进程ID都可以唯一表示一个进程 但是一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定 源端口号和目的端口号 传输层协议(TCP和UDP)的数据段中有两个端口号分别叫做源端口号和目的端口号 简单来说就是 “数据是哪个发的, 最后要发给谁” 2. 初识TCP协议与UDP协议 TCP(Transmission Control Protocol 传输控制协议) 传输层协议有连接可靠传输面向字节流 UDP(User Datagram Protocol 用户数据报协议) 传输层协议无连接不可靠传输面向数据报 3. 网络字节序 网络字节序Network Byte Order也称为网络字节顺序是协议中规定好的一种数据表示格式。它用于在计算机网络中进行数据通信时统一数据的字节顺序确保数据在不同主机之间传输时能够被正确解释。 因为内存中的多字节数据相对于内存地址有大端和小端之分磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分网络数据流同样有大端小端之分为了定义出网络数据流的地址我们引入了网络字节序。 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可 为使网络程序具有可移植性使同样的C代码在大端和小端计算机上编译后都能正常运行可以调用以下库函数做网络字节序和主机字节序的转换 h表示host,n表示network,l表示32位长整数,s表示16位短整数htonl表示将32位的长整数从主机字节序转换为网络字节序例如将IP地址转换后准备发送如果主机是小端字节序这些函数将参数做相应的大小端转换然后返回如果主机是大端字节序这些函数不做转换将参数原封不动地返回 4. socket编程 socket 常见API 常见通用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);Udp中常见API
// 函数用于在面向数据报的套接字如UDP套接字上发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);// 用于从套接字接收数据的方法特别是在使用UDP协议进行数据传输时
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);Tcp中常见API
// 开始监听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结构 sockaddr是一个在C语言网络编程中使用的数据结构主要用于表示套接字地址。它是一个通用的结构体能够用于表示不同类型的套接字地址如IPv4、IPv6等 IPv4和IPv6的地址格式定义在netinet/in.h中IPv4地址用sockaddr_in结构体表示包括16位地址类型, 16位端口号和32位IP地址IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6。这样,只要取得某种sockaddr结构体的首地址不需要知道具体是哪种类型的sockaddr结构体就可以根据地址类型字段确定结构体中的内容socket API可以都用struct sockaddr *类型表示在使用的时候需要强制转化成sockaddr_in这样的好处是程序的通用性可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数 sockaddr 结构
struct sockaddr
{__SOCKADDR_COMMON(sa_); /* Common data: address family and length. */char sa_data[14]; /* Address data. */
};sockaddr_in 结构
/* Structure describing an Internet socket address */
struct sockaddr in
{__SOCKADDR_COMMON(sin_); /* Port number. */in_port_t sin_port; /* Internet address. */struct in_addr sin_addr;/* Pad to size ofstruct sockaddr. */unsigned char sin_zero[sizeof(struct sockaddr) -SOCKADDR_COMMON_SIZE - sizeof(in_port_t)-sizeof(struct in_addr)];
};in_addr结构
/* Internet address. */
typedef uint32_t in_addr_t;
struct int_addr
{in_addr_t s_addr;
};封装 sockaddr_in 我们在进行套接字编程时难免会多次用到一些转换我们不妨将他们封装起来让我们的代码变得不那么复杂从而更容易获取我们想要的数据 #pragma once
#include iostream
#include string
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.husing namespace std;class InetAddr
{
public:InetAddr(struct sockaddr_in addr):_addr(addr){// 将网络字节流转换成字符串_port ntohs(_addr.sin_port);_ip inet_ntoa(_addr.sin_addr);}string Ip(){return _ip;}uint16_t Port(){return _port;}string PrintDebug(){string info _ip;info :;info to_string(_port);return info;}// 判断是否是同一个地址bool operator (const InetAddr addr){return _ip addr._ip _port addr._port;}const sockaddr_in GetAddr(){return _addr; }~InetAddr(){}
private:string _ip;uint16_t _port;struct sockaddr_in _addr;
};封装 Udp_Socket Udp_Server.hpp固定格式
class UdpServer:public nocopy
{
public:UdpServer(uint16_t port):_port(port),_sockfd(defaultfd){}void Init(){// 创建套接字_sockfd socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd 0){lg.LogMessage(Fatal, socket err, %d : %s, errno, strerror(errno));exit(Socket_Err);}lg.LogMessage(Info, socket success, sockfd: %d\n, _sockfd);// 绑定指定网络信息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; // 默认为 0// local.sin_addr.s_addr inet_addr(_ip.c_str());// 结构填写完整转到内核int n ::bind(_sockfd, (struct sockaddr*)local, sizeof(local));if(n ! 0){lg.LogMessage(Fatal, socket err, %d : %s, errno, strerror(errno));exit(Bind_Err);}}void Start(){// 服务器一直运行char buffer[defaultsize];for(;;){struct sockaddr_in peer;socklen_t len sizeof(peer); // 不能乱写ssize_t n recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)peer, len);if(n 0){// Server}}}~UdpServer(){pthread_mutex_destroy(_mutex);}private:uint16_t _port;int _sockfd;
};Udp实现聊天室功能
封装 TCP_Socket Tcp_Server.hpp固定格式
class Tcp_Server : public nocopy // 防拷贝
{
public:Tcp_Server(uint16_t port, bool isrunning false): _port(port), _isrunning(isrunning){}void Init(){// 创建套接字_listensock socket(AF_INET, SOCK_STREAM, 0);if (_listensock 0){lg.LogMessage(Fatal, socket err, %d : %s, errno, strerror(errno));exit(Socket_Err);}lg.LogMessage(Info, create socker success, sockfd: %d ... , _listensock);// 固定写法int opt 1;setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, opt, sizeof(opt));// 绑定指定网络信息struct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET;local.sin_port htons(_port);local.sin_addr.s_addr htonl(INADDR_ANY);// bindif (bind(_listensock, CONV(local), sizeof(local)) ! 0){lg.LogMessage(Fatal, bind socket err, %d : %s, errno, strerror(errno));exit(Bind_Err);}lg.LogMessage(Debug, bind socket success, sockfd: %d ... , _listensock);// 监听 listenif (listen(_listensock, default_black_log) ! 0){lg.LogMessage(Fatal, listen socket err, %d : %s, errno, strerror(errno));exit(Listen_Err);}lg.LogMessage(Debug, listen socket success, sockfd: %d ... , _listensock);}// 通过read, write来进行数据的读写void Service(int sockfd){char buffer[1024];while(true){ssize_t n read(sockfd, buffer, sizeof(buffer));if(n 0){buffer[n] 0;cout client say# buffer endl;string echo_string server echo# ;echo_string buffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n 0){lg.LogMessage(Info, client quite ... \n);break;}else{lg.LogMessage(Error, read socket err, %d : %s, errno, strerror(errno));break;}}}void Start(){_isrunning true;// signal(SIGCHLD, SIG_IGN);while (_isrunning){// 获取连接struct sockaddr_in peer;socklen_t len sizeof(peer);int sockfd accept(_listensock, CONV(peer), len);if (sockfd 0){lg.LogMessage(Fatal, accept socket err, %d : %s, errno, strerror(errno));continue;}lg.LogMessage(Debug, accept socket success, sockfd: %d ... , sockfd);// 提供服务// v1Service(sockfd);close(sockfd);}}~Tcp_Server(){}private:uint16_t _port;int _listensock;bool _isrunning;
};Tcp实现简单英译汉
5. 进程守护 在之前的学习中head -1我们经常使用但是机会没机会了解PGID和SID是啥意思今天我们来正式认识一下 我们同时创建的sleep 10000sleep 20000sleep 30000会不会在同一个进程组里面呢我们可以查询一下 移动进程组的指令 查看任务jobs 将任务放置到前台fg task_number 将任务放置到后台ctrl Z 再 dg task_number 守护进程 守护进程是在后台运行的、不受任何终端控制的进程。它们通常用于执行系统级的任务或服务如系统监控、网络通信、文件服务等。 封装 Daemon
6. TCP协议通讯流程 TCP协议的客户端/服务器程序的一般流程 建立连接的过程 调用socket, 创建文件描述符;调用connect, 向服务器发起连接请求;connect会发出SYN段并阻塞等待服务器应答; (第一次)服务器收到客户端的SYN, 会应答一个SYN-ACK段表示同意建立连接; (第二次)客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次) 这个建立连接的过程, 通常称为 三次握手
断开连接的过程 如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次)此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次)read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN;(第三次)客户端收到FIN, 再返回一个ACK给服务器;(第四次) 这个断开连接的过程, 通常称为 四次挥手
⭐7. TCP 和 UDP 对比 可靠传输 vs 不可靠传输有连接 vs 无连接字节流 vs 数据报
udp是面向用户数据包而tcp面向字节流 — 数据和数据是有边界的 sendto、recvfrom -sendto 发了一次一定对应对端recvform一次 — 面向数据报write-1,10,100-接收方read可能一次就全部收完了也可能很多次但是无论次数是多少和对方发多少无关 — 面向字节流 8. 总结 在探索Linux网络编程的浩瀚领域中UDP与TCP作为两大核心协议不仅构建了互联网通信的基石也成为了每一位网络开发者必须掌握的利器。通过这段旅程我们一同见证了从基础概念到实践应用的华丽蜕变从最初的套接字创建、绑定、监听到数据的发送与接收每一步都充满了挑战与收获。 在结束这篇文章之际愿每一位读者都能在网络编程的世界里找到自己的位置用代码编织梦想用技术照亮未来。让我们携手前行在Linux网络编程的广阔天地中共同书写属于我们的辉煌篇章 希望本文能够为你提供有益的参考和启示让我们一起在编程的道路上不断前行 谢谢大家支持本篇到这里就结束了祝大家天天开心