int socket(int domain, int type, int protocol);
domain
:指定协议族,如AF_INET
(IPv4)或AF_INET6
(IPv6)。
type
:指定套接字类型,如SOCK_DGRAM
(UDP)或SOCK_STREAM
(TCP)。
protocol
:通常设置为0,表示使用默认协议。
返回值:成功时返回一个套接字描述符,失败时返回-1。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
:由socket()
返回的套接字描述符。
addr
:指向sockaddr
结构的指针,包含要绑定的地址信息。
addrlen
:addr
的长度。
返回值:成功时返回0,失败时返回-1。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
sockfd
:套接字描述符。
buf
:用于存储接收到的数据的缓冲区。
len
:缓冲区的大小。
flags
:通常设置为0。
src_addr
:指向sockaddr
结构的指针,用于存储发送方的地址信息。
addrlen
:指向socklen_t
的指针,用于指定src_addr
的长度。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd
:套接字描述符。
buf
:要发送的数据的缓冲区。
len
:要发送的数据的长度。
flags
:通常设置为0。
dest_addr
:指向sockaddr
结构的指针,包含目标地址信息。
addrlen
:dest_addr
的长度。
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd
:套接字描述符。
level
:选项级别,如SOL_SOCKET
。
optname
:选项名称,如SO_REUSEADDR
或SO_REUSEPORT
。
optval
:指向选项值的指针。
optlen
:选项值的长度。
返回值:成功时返回0,失败时返回-1。
char *inet_ntoa(struct in_addr in);
hostshort
:主机字节序的无符号短整数
返回值:网络字节序的无符号短整数。
uint16_t htons(uint16_t hostshort);
hostshort
:主机字节序的无符号短整数。
返回值:网络字节序的无符号短整数。
uint16_t ntohs(uint16_t netshort);
netshort
:网络字节序的无符号短整数。
etstat
命令是一个功能强大的网络工具,用于查看和分析系统的网络状态。以下是netstat
命令的一些常用选项和示例,帮助你查看网络连接状态:
常用选项
-a
:显示所有活动的网络连接,包括监听和非监听状态。
-t
:仅显示TCP协议相关的连接。
-u
:仅显示UDP协议相关的连接。
-n
:以数字形式显示地址和端口号,避免进行DNS解析。
-l
:仅显示监听状态的连接。
-p
:显示与网络连接相关联的进程ID和程序名称。
-r
:显示路由表信息。
-i
:显示网络接口统计信息。
类比理解:
bind
就像「分配电话号码」假设你的程序是一个「电话」,网络通信需要两件事:
- 电话号码(IP地址 + 端口):别人通过这个号码找到你。
- 电话机(套接字):用来接听和拨打电话的工具。
bind
的作用:
把你的「电话机」绑定到一个「电话号码」上。这样,别人(其他程序)才能通过这个号码联系到你。
要接收一个端口号,以及一个处理信息的函数,然后isrunning是一个判断执行状态,true表示运行中,就可以执行主体代码。
UdpServer(uint16_t port,func_t func)
:_sockfd(defaultfd),
_port(port),
_isrunning(false),
_func(func)
{}
创建端口号,设定为UDP模式(虽然第三参数为0,但是前两个参数就决定了模式是UDP的),setsockopt函数用来这个原因是,当服务器主动关闭连接的时候,套接字会进入TIME_WAIT状态,这个原因是,当服务器主动关闭连接的时候,套接字会进入TIME_WAIT状态,可以设置一下地址端口重用,不然就会打印出日志bind error信息。bero函数是吧给定的空间进行初始化为0,接着就是初始化协议族为四字节,然后端口号要转成网络字节序,然后设置套接字绑定的ip地址为INADDR_ANY,表示该套接字将监听所有可用的网络接口上的链接请求,INADDR_ANY是一个常量,,表示任何可用的网络接口,这样子设置是为了把所有的客户端信息接收了,客户端可能是各种网络的,所以设置这个可用全部接收,接着就是绑定套接字了,因为是在栈区上创建的,是在用户层面上,需要到内核态里,就需要bind函数来进行绑定,绑定后的套接字才是有用的。
void Init()
{
_sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd<0)
{
LOG(LogLevel::FATAL)<<"socket error";
exit(1);
}
int opt = 1;
setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
LOG(LogLevel::INFO)<<"socket success, sockfd:"<<_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;
int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
LOG(LogLevel::FATAL)<<"bind error";
exit(2);
}
LOG(LogLevel::INFO)<<"bind success,sockfd:"<<_sockfd;
}
进入这个函数就可以把运行状态设置为true,然后进入循环,设置buffer用来接收消息,创建sockaddr_in类型peer表示客户端的消息(如端口号和IP地址),因为recvfrom函数需要这个参数,这个函数是在UDP套接字接收数据,peer就是一个输出型参数,执行完后,可以从peer里知道是谁发的,就像信封,上面有发件人的信息,需要注意的是参数需要强转成sockaddr类型的,接收成功就进入判断,从peer结构体里可以得到客户端信息(访问成员变量),然后用inet_ntoa将网络字节序转成点分十进制,调用回调函数,把buffer传过去,得到的信息处理再返回,最后执行sendto函数把信息进行发送回去,告诉客户端已经接收到了这个信息。
在使用
recvfrom
函数时,需要将sockaddr_in
强制转换为sockaddr
类型,原因如下:兼容性
sockaddr
是一个通用的套接字地址结构体,用于支持多种协议族。sockaddr_in
是internet环境下套接字的地址形式,专门用于IPv4地址。套接字函数如
bind
、connect
、recvfrom
等设计为使用通用的sockaddr
结构体作为参数,以便能够支持不同的地址族。通过将sockaddr_in
强制转换为sockaddr
,可以确保这些函数能够接受IPv4地址信息,同时保持接口的一致性。灵活性
强制转换允许程序员在需要时传递特定的地址结构体,同时保持函数接口的通用性。这意味着,如果将来需要支持其他类型的地址(如IPv6地址
sockaddr_in6
),只需更改地址结构体的类型和相应的转换,而无需修改函数调用的代码。统一性
sockaddr
和sockaddr_in
的长度都是16个字节,因此可以互相转换。这种统一的内存布局使得在不同地址类型之间进行转换成为可能,而不会导致数据损坏或访问错误的内存区域。
void Start()
{
_isrunning=true;
while(_isrunning)
{
char buffer[1024];
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
std::cout << "服务器开始接收数据: " << std::endl;
ssize_t s=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(s>0)
{
std::cout<<"进入判断"<
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
using namespace LogModule;
using func_t =std::function;
const int defaultfd=-1;
class UdpServer
{
public:
UdpServer(uint16_t port,func_t func)
:_sockfd(defaultfd),
_port(port),
_isrunning(false),
_func(func)
{}
void Init()
{
_sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd<0)
{
LOG(LogLevel::FATAL)<<"socket error";
exit(1);
}
int opt = 1;
setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
LOG(LogLevel::INFO)<<"socket success, sockfd:"<<_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;
int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
LOG(LogLevel::FATAL)<<"bind error";
exit(2);
}
LOG(LogLevel::INFO)<<"bind success,sockfd:"<<_sockfd;
}
void Start()
{
_isrunning=true;
while(_isrunning)
{
char buffer[1024];
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
std::cout << "服务器开始接收数据: " << std::endl;
ssize_t s=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(s>0)
{
std::cout<<"进入判断"<
main要写命令行参数,要接收端口号,不用IP地址是因为前面设置0地址为IP地址,就不需要接收这个参数了,创建一个port接收传入的端口号,这里的uint_16是一个重定义的变量,设置日志类型向控状态打印信息,std::unique_ptr是一个智能指针,用于管理动态分布的对象,它所管理的对象只能有一个所有者,std::make_unique是C++14引入的一个工厂函数,用于简化unique_ptr的创建过程,会调用new操作符来分配空间,返回一个unique_ptr对象。创建对象执行,这里不是.而是->是因为这是一个智能指针,所以需要箭头调用函数。
#include
#include
#include "UdpServer.hpp"
#include
std::string defaulthandler(const std::string& message)
{
std::cout<<"打印"< usvr=std::make_unique(port, defaulthandler);
usvr->Init();
usvr->Start();
return 0;
}
客服端的话需要传入地址和端口号,创建套接字,以及sockaddr_in类型变量,初始化创建的sockaddr_in结构体的值,进入循环创建string类型的input,要发送的信息,调用getline函数从cin输入流中获取信息放到input中,调用sendto函数向服务端发送消息,套接字,以及发送内容以及大小,还有向谁发送信息,以及信息大小这些参数,然后就是创建buffer来接收消息,向服务端发送消息,服务端反馈后,客服端接收,接收到就打印出接收的消息。
为什么需要传递
sizeof(server)
指定地址结构体的大小:
addrlen
参数用于告诉sendto
函数目标地址结构体的大小。这对于底层网络库正确解析地址信息至关重要。兼容性:
sockaddr
是一个通用的地址结构体,而sockaddr_in
是专门用于IPv4的地址结构体。通过传递sizeof(server)
,可以确保sendto
函数知道它正在处理的是sockaddr_in
结构体的大小。
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char* argv[])
{
if(argc!=3)
{
std::cerr<<"Usage:"<0)
{
buffer[m]=0;
std::cout<
.PHONY:all
all:udpclient udpserver
udpclient:UdpClient.cc
g++ -o $@ $^ -std=c++17
udpserver:UdpServer.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -f udpclient udpserver