Linux网络编程 | Socket编程(一):Socket的介绍、UDPSocket的封装、UDP服务器/客户端的实现
目录
- 套接字编程
- Sockaddr结构
- 字节序
- 地址转换
- 常用套接字接口
- UDP的通信流程
- UDPSocket的封装
- UDP服务器
- UDP客户端
套接字编程
所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。
Sockaddr结构
在linux下,根据所使用的不同协议,又分为以下三种结构,在使用时,我们可以选择自己所需要的结构,通信时再将我们所使用的结构强转为sockaddr,这样就能保证数据格式的一致
- 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结构体指针做为 参数;
通常情况下,因为我们使用的都是IPV4,所以一般用的都是sockaddr_in结构,这个结构中用来描述通信双方的主要信息就是端口号和ip地址
sockaddr_in结构
sin_family:指代协议族,在socket编程中只能是AF_INET
sin_port:存储端口号(使用网络字节顺序)
sin_addr:存储IP地址,使用in_addr这个数据结构
sin_zero:是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
in_addr结构
这个结构中存储的其实就是一个32位的整型ip地址。
字节序
字节序就是CPU对数据再内存中以字节为单位的存取顺序,也就是我们通常所说的大端小端问题。
关于大小端的问题我之前有写过一篇博客
大端小端存储解析
这里就简要说一下
大端存储模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。
小端存储模式:是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
在网络通信中,网络字节序采用大端的存储模式,而主机字节序根据主机不同也不一样,我们现在的家用机一般都是小端,但网络上的通信不能确保主机字节序的唯一性,因为受众是整个网络,而一旦通信的双方主机字节序不同,就会造成通信时的数据二义,所以需要确保字节序相同,就需要在通信时将主机字节序转换为通用的网络字节序。
在arpa/inet.h
这个头文件中,也为我们提供了一套字节序的转换接口。
#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong);//将无符号长整型的主机字节序转换为网络字节序uint16_t htons(uint16_t hostshort);//将无符号短整型的主机字节序转换为网络字节序uint32_t ntohl(uint32_t netlong);//将无符号长整型的网络字节序转换为主机字节序uint16_t ntohs(uint16_t netshort);//将无符号短整型的网络字节序转换为主机字节序//h代表主机字节序,n代表网络字节序,l代表长整型,s代表短整型
地址转换
同样的,我们输入进去的ip地址一般都是点分十进制的ip地址,而通信时需要的是网络字节序的整数ip地址。
#include <arpa/inet.h>char *inet_ntoa(struct in_addr in);//将网络字节序的整数ip地址转换为点分十进制的字符串ip地址in_addr_t inet_addr(const char *cp);//将点分十进制的字符串ip地址转换为网络字节序的整数ip地址int inet_aton(const char *cp, struct in_addr *inp);//将点分十进制的字符串ip地址转换为网络字节序的整数ip地址(与addr的区别它会认为如255.255.255.255这类特殊地址有效)in_addr_t inet_network(const char *cp); //将点分十进制的字符串ip地址转换为主机字节序的整数ip地址struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);in_addr_t inet_lnaof(struct in_addr in);in_addr_t inet_netof(struct in_addr in);
常用套接字接口
// 创建 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);// 开始监听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);//断开连接
int close(int sockfd); //发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);//接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
UDP的通信流程
计算机网络 (三) 传输层 :一文搞懂UDP与TCP协议
在这篇博客中,我描述了UDP与TCP的特性以及通信流程,下面就根据特性来规划该如何通过Socket来实现UDP通信。
这里我就简单的画一个图。
因为UDP是无连接的,所以只需要再创建套接字后绑定地址信息,就可以直接进行通信。
这里有一点需要注意,就是客户端一般不会主动绑定地址信息。
原因是客户端用什么地址和端口接收数据都无所谓,只需要确保能够将数据发送出去即可。如果不绑定地址信息,系统会自动选择合适的地址端口进行绑定,而如果手动绑定,很可能会绑定到已使用或者将要使用的端口,此时就会产生端口的冲突,所以为了减少端口冲突,客户端一般不会主动绑定。
UDPSocket的封装
为了能够更方便的使用,一般都会根据不同协议和使用情景,来封装一套Socket接口,使用时就只需要根据协议特性来传递参数即可。
具体的思路我都写在了注释里面
#include<iostream>
#include<sys/socket.h>
#include<string>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<cstdio>
#include<unistd.h>//内联函数,用来检测当前操作是否出错
inline void CheckSafe(bool ret)
{if(ret == false){std::cerr << "Socket发生错误" << std::endl;exit(0);}
}class UdpSocket
{public:UdpSocket() : _socket_fd(-1){}//创建socketbool Socket(){_socket_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if(_socket_fd == -1){perror("socket create error");return false;}return true;}//绑定地址信息bool Bind(const std::string& ip, uint16_t port){struct sockaddr_in addr;addr.sin_family = AF_INET;//主机字节序转换成网络字节序,方便统一addr.sin_port = htons(port);//将字符串的ip地址转为网络字节序的二进制数据addr.sin_addr.s_addr = inet_addr(ip.c_str());socklen_t len = sizeof(struct sockaddr_in);//强转地址结构,使接口统一int ret = bind(_socket_fd, (struct sockaddr*)& addr, len);if(ret == -1){perror("socket bind error");return false;}return true;}bool Recv(std::string& buff, std::string* ip = NULL, uint16_t* port = NULL){//对端地址信息struct sockaddr_in peer_addr;socklen_t len = sizeof(struct sockaddr_in);//接收缓冲区char temp[1024] = {0};int ret = recvfrom(_socket_fd, temp, 1024, 0, (struct sockaddr*)&peer_addr, &len);if(ret == -1){perror("receive error");return false;}//将数据从缓冲区取出buff.assign(temp, ret);//获取对端地址信息if(port != NULL){*port = htons(peer_addr.sin_port);}if(ip != NULL){*ip = inet_ntoa(peer_addr.sin_addr);}return true;}bool Send(const std::string& data, const std::string& ip, const uint16_t& port){struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip.c_str());socklen_t len = sizeof(struct sockaddr_in);int ret = sendto(_socket_fd, data.c_str(), data.size(), 0, (sockaddr*)& addr, len);if(ret == -1){perror("send error");return false;}return true;}void Close(){close(_socket_fd);_socket_fd = -1;}private:int _socket_fd;
};
按照前面所化的流程以及封装的对应接口,来实现服务端
UDP服务器
#include<iostream>
#include"UdpSocket.hpp"using namespace std;int main (int argc, char *argv[])
{if(argc != 3){cerr << "正确输入方式: ./udp_srv.cpp ip port\n" << endl;return -1;}//获取命令行输入的ip地址和端口string ip = argv[1];uint16_t port = stoi(argv[2]);UdpSocket Socket;//创建套接字CheckSafe(Socket.Socket());//绑定地址信息CheckSafe(Socket.Bind(ip, port));while(1){string cli_ip;uint16_t cli_port;string message;//接受数据CheckSafe(Socket.Recv(message, &cli_ip, &cli_port)); cout << "cli[" << cli_ip << ":" << cli_port << "]:send message: " << message << endl;message.clear();cout << "srv send reply message: "; getline(cin, message);//给客户端回复数据CheckSafe(Socket.Send(message, cli_ip, cli_port));}//关闭套接字Socket.Close();return 0;
}
按照前面所化的流程以及封装的对应接口,来实现客户端
UDP客户端
#include<iostream>
#include"UdpSocket.hpp"using namespace std;int main (int argc, char *argv[])
{if(argc != 3){cerr << "正确输入方式: ./udp_cli.cpp ip port\n" << endl;return -1;}//获取命令行输入的ip地址和端口string ip = argv[1];uint16_t port = stoi(argv[2]);UdpSocket Socket;//创建套接字CheckSafe(Socket.Socket());//发送方不需要主动绑定地址信息,让系统自动选取即可,因为只需要保证能够发送数据,并且接收到数据即可,哪个地址端口都无所谓,这样还能减少端口冲突的概率//发送数据while(1){cout << "cli send message: ";string message;getline(cin, message);//如果输入quit则退出if(message == "quit")break;//发送数据CheckSafe(Socket.Send(message, ip, port));message.clear();//接受数据CheckSafe(Socket.Recv(message));cout << "srv reply message: " << message << endl;}//关闭套接字Socket.Close();return 0;
}
服务端
客户端
Linux网络编程 | Socket编程(一):Socket的介绍、UDPSocket的封装、UDP服务器/客户端的实现相关推荐
- UDP服务器客户端编程流程
UDP服务器客户端编程流程 UDP编程流程 UDP服务端代码实现 UDP客户端代码实现 UDP服务端客户端代码详解 UDP编程流程 UDP提供的是无连接.不可靠的.数据报服务 UDP是尽最大能力进行传 ...
- Linux网络原理与编程——第十一节 网络基础及套接字
目录 一.网络的层状划分结构 二.网络发展史 三.协议 四.OSI七层结构模型 五.TCP/IP四层(五层)协议结构模型 六.局域网中通信原理初识 封包.解包.分用.mac帧 七.跨网络通信原理初始 ...
- C++socket编程(八):8.2简单的基于UDP的客户端和服务端
UDP中的服务器端和客户端没有连接 UDP 不像 TCP,无需在连接状态下交换数据,因此基于 UDP 的服务器端和客户端也无需经过连接过程.也就是说,不必调用 listen() 和 accept() ...
- [转]linux网络协议栈(1)——socket入门(1)(2)
[转自 https://www.cnblogs.com/hustcat/archive/2009/09/17/1568738.html https://www.cnblogs.com/hustcat/ ...
- 【Java 网络编程】UDP 服务器 客户端 通信 ( DatagramSocket | DatagramPacket | UDP 发送数据包 | UDP 接收数据包 | 端口号分配使用机制 )
文章目录 I UDP 信息发送接收原理 II UDP 发送和接收端口相同 III UDP 发送信息代码示例 IV UDP 接收信息代码示例 V UDP 服务器端代码示例 VI UDP 客户端代码示例 ...
- linux网络编程之用select方法实现io复用(基于udp)
1.基本概念 IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程.IO多路复用适用如下场合: (1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/ ...
- Python核心编程(第3版)第2章网络编程中关于tcp/udp服务器和客户端实现代码的运行出错的修正
在Python核心编程(第3版)第2章网络编程中, 关于tcp/udp服务器和客户端实现代码的运行会出现 ['str' does not support the buffer interface]之类 ...
- Linux网络常用工具分类介绍
Linux网络命令较多,单纯的介绍网络命令的用法也没什么意思.本文将常见的网络命令进行分类,并做出思维导图,对每个分类的命令选择性的介绍其作用.常见选项和用法举例.BTW,不建议记住所有命令,了解一下 ...
- linux网络属性配置
linux网络属性配置分动态分配和静态指定 动态分配主要依靠DHCP服务器 静态指定需要命令手动分配 静态指定命令三家族 ifcfg家族 iproute2家族 nm家族 ifcfg家族: ifconf ...
最新文章
- python字符串的特点_python小白之路(特性语法三之字符串)
- spark多个kafka source采用同一个group id导致的消费堆积延迟
- 金融数据分析与挖掘实战练习2.5-2.9
- Spring Cloud Spring Boot mybatis分布式微服务云架构(五)构建RESTful API
- Unity的学习笔记(XLua的初学用法并在lua中使用unity周期函数)
- Python学习教程(Python学习路线):Python3你还未get到的隐藏技能
- html img 能显示psd吗_教育一体机迈向大尺寸化,小间距显示屏能进入教室吗?
- Spring Boot项目中使用 TrueLicense 生成和验证License(服务器许可)
- golang常用库之-pkg/errors包 第三方错误处理包 | golang如何打印错误(error)堆栈
- 爱立信发布人体通信技术:最高传输10Mbps
- 人脑能用计算机算法吗,电脑到人脑,计算机必不可少的4大思维
- 编写Makefile:编译当前文件夹以及子文件夹下所有的ccpp文件并生成可执行文件
- leetcode-838:推多米诺
- LSB利器-zsteg
- H5无插件实现实时海康、大华摄像头网页预览
- 基于CentOS搭建个人Leanote云笔记本
- 初学者如何学习logo设计?
- via ladder
- 相等(==)与 全等(===)
- 在Excel中按条件筛选数据并存入新的表