网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
服务端操作流程:
- 创建套接字端口:在内核中创建socket结构体,关联进程与网卡之间的联系
- 为套接字绑定地址信息:网络通信中的数据都必须带有源端IP、源端端口、对端IP、对端端口、协议。在内核创建的socket结构体中描述IP地址端口以及协议,(必须主动绑定,告诉客户端自己的地址信息,如果不绑定客户端就不知道该发往哪个服务端了)为了告诉操作系统发往哪个IP地址,哪个端口的数据是交给我来处理的
- 接收数据:告诉操作系统发往哪个地址和端口的数据应该交给我处理,操作系统一旦接收到发往这个地址和端口的数据,就会将这条数据放到对应的socket的接收缓冲区中,然后服务端从对应的socket的接收缓冲区中取出数据。
- 发送数据:将数据写入内核中的socket发送缓冲区中,操作系统选择合适的时候将数据封装发送出去
- 关闭套接字:释放资源
客户端操作流程:第2,3步与服务端不同
- 创建套接字:在内核中创建socket结构体,关联进程与网卡之间的联系
- 为套接字绑定地址信息:描述在内核中创建的socket结构体的源端地址信息;发送的数据中源端地址信息就是绑定的地址信息(不推荐主动绑定地址,降低端口冲突的概率,从而确保数据发送的安全性)
- 发送数据:将数据放到socket的发送缓冲区中,操作系统选择合适时候封装数据并发送数据。若socket发送数据的时候还没绑定地址,则操作系统会选择合适的地址进行绑定。
- 接收数据:将数据写入内核中的socket发送缓冲区中,操作系统选择合适的时候将数据封装发送出去
- 关闭套接字:释放资源
socket接口介绍
1、创建套接字int socket(int domain, int type, int protocol)
参数内容(domian:地址域(本地通信-AF_LOCAL
、IPv4-AF_INET
、IPv6-AF_INET6
等)确定本次socket通信使用哪种协议版本的地址结构,不同的协议版本有不同的地址结构;type:套接字类型(流式套接字-SOCK_STREAM
、数据报套接字-SOCK_DGRAM
等);protocol:协议类型(TCP-IPPROTO_TCP
、UDP-IPPROTO_UDP
) ,默认为0-流式默认TCP,数据报默认UDP)
返回值:文件描述符-非负整数, 套接字所有其他接口的操作句柄,失败返回-1
2、为套接字绑定地址信息int bind(int sockfd, struct sockaddr *addr, socklen_t len)
参数内容(sockfd:创建套接字返回的操作句柄;addr:要绑定的地址信息;len:要绑定的地址信息长度)
中间的参数结构体有很多种
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
struct sockaddr_in
{
sa_family sin_family;//地址域
in_port_t sin_port;//端口号
struct in_addr sin_addr;//IP地址
};
struct sockaddr_in6
{
sa_family sin_family;//地址域
in_port_t sin6_port;//端口号
uint32_t sin6_flowinfo;
struct in6_addr, sin6_addr;//IP地址
unit32_t sin6_scope_id;
}
struct sockaddr_un
{
__SOCKADDR_COMMON (sun_);
char sun_path[108];
};
不同的地址结构,有统一的一个信息:前两个字节是地址域类型,bind可以绑定不同的地址结构,为了实现接口统一,因此用户定义地址结构的时候,需要定义自己需要的地址结构,例如IPv4就使用struct sockaddr_in,但是进行绑定的时候,统一类型强转成为sockaddr*类型
简单解析bind接口的实现
bind(fd,addr, len)
{
if(addr->sa_family==AF_INET)
{
//绑定IPv4地址信息,这个结构体按照sockaddr\_in进行解析
}
else if (addr->sa_family==AF_INET6)
{
//绑定IPv6地址信息,这个结构体按照sockaddr\_in6进行解析
}
else if ...
}
3、接收数据,接收发送者地址便于回复ssize_t recvfrom(int sockfd, char *buf, int len, int flag, struct sockaddr *peer_addr, socklen *addrlen)
参数内容(sockfd:创建套接字返回的操作句柄;buf:一块缓冲区,用于接收从接收缓冲区中取出数据;len:想要接收的数据长度;flag:操作选项标志,默认为0,表示阻塞操作;peer_addr:发送方的地址信息;addrlen:想要获取的地址信息长度以及返回实际长度)
返回值:成功返回实际接收到的数据字节长度,失败返回-1
4、发送数据ssize_t sendto(int sockfd, char *data, int len, int flag, struct sickaddr *peer_addr, socklen_t addrlen)
参数内容(socket:socket操作句柄;data:要发送的数据地址;len:要发送数据长度;flag:默认为0,表示阻塞操作;peer_addr:接收方的地址信息;addrlen:地址信息长度)
返回值:成功返回实际发送的数据字节长度,失败返回-1
5、关闭套接字int close(int fd)
网络字节序的转换接口
uint32_t htonl(uint32_t hostlong)
主机字节序到网络字节序的转换
uint16_t htons(uint16_t hostshort)
uint32_t ntohl(uint32_t netlong)
网络字节序到主机字节序的转换
uint16_t ntohs(uint16_t netshort)
in_addr_t inet_addr(const char *cp)
将字符串的点分十进制IP地址转换成为网络字节序的整数IP地址
char *inet_ntoa(struct in_addr in)
将网络字节序的整数IP地址转化为字符串点分十进制IP地址
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size)
将网络字节序的整数IP地址转化为字符串IP地址-兼容IPv4和IPv6
int inet_pton(int af, const char *src, void *dst)
将字符串的IP地址转换成为网络字节序的整数IP地址-兼容IPv4和IPv6
udp客户服务端代码实现
使用c++封装UdpSocket类,实例化的每一个对象都是一个udp通信套接字,并且能够通过成员接口实现udp通信流程
udpsocket.hpp
//udpsocket.hpp
#include <cstdio>
#include <string>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;
class UdpSocket
{
public:
UdpSocket()
:\_sockfd(-1)
{}
//创建套接字
bool Socket()
{
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (_sockfd < 0)
{
perror("socket error");
return false;
}
return true;
}
//为套接字绑定地址信息
bool Bind(const string &ip, uint16_t port)
{
//定义IPv4地址结构体
struct sockaddr_in addr;
//地址信息赋值
addr.sin_family = AF_INET;
addr.sin_port = htons(port);//将主机字节序短整型型数据转化为网络字节序数据
addr.sin_addr.s_addr = inet\_addr(ip.c\_str());//将字符串IP地址转化为网络字节序IP地址
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd, (struct sockaddr\*)&addr, len);
if (ret < 0)
{
perror("bind error");
return false;
}
return true;
}
//接收数据,获取发送端地址信息
bool Recv(string \*buf, string \*ip=NULL, uint16_t \*port=NULL)
{
struct sockaddr_in peer_addr;//用于接收发送端的地址信息
socklen_t len = sizeof(struct sockaddr_in);
char tmp[4096] = {0};
int ret = recvfrom(_sockfd, tmp, 4096, 0, (struct sockaddr\*)&peer_addr, &len);
if (ret < 0)
{
perror("recvfrom error");
return false;
}
buf->assign(tmp, ret);//assign从指定字符串中截取指定长度的数据到buf中
if (port != NULL)
{
\*port = ntohs(peer_addr.sin_port);//网络字节序到主机字节序的转换
}
if (ip != NULL)
{
\*ip = inet\_ntoa(peer_addr.sin_addr);//网络字节序到字符串IP地址的转换
}
return true;
}
//发送数据
bool Send(const string &data, 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(_sockfd, data.c\_str(), data.size(), 0, (struct sockaddr\*)&addr, len);
if (ret < 0)
{
perror("sendto error");
return false;
}
return true;
}
//关闭套接字
bool Close()
{
if (_sockfd > 0)
{
close(_sockfd);
_sockfd = -1;
}
return true;
}
private:
int _sockfd;
};
udp_srv.cpp
#include <iostream>
#include <string>
#include "udpsocket.hpp"
using namespace std;
#define CHECK\_RET(q) if((q)==false){return false;}
int main(int argc, char \*argv[])
{
//运行时有三个参数 udp\_src 192.168.73.29 4096
if (argc != 3)
{
cout << "Usage: ./udp\_srv ip prot\n";
return -1;
![img](https://img-blog.csdnimg.cn/img_convert/f51548d34a394c6a8fc5e41b244e6d69.png)
![img](https://img-blog.csdnimg.cn/img_convert/b091d741840469825b43b2b303e1c8aa.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
86931559)]
[外链图片转存中...(img-DSPRQO1f-1715886931559)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**