目录
[socket 函数(创建套接字)](#socket 函数(创建套接字))
[bind 函数(绑定套接字和本地地址)](#bind 函数(绑定套接字和本地地址))
[recvfrom 函数(接收数据报)](#recvfrom 函数(接收数据报))
[sendto 函数(发送数据报)](#sendto 函数(发送数据报))
[Log.hpp / LockGuard.hpp](#Log.hpp / LockGuard.hpp)
[makefile(一次生成 2 个可执行程序)](#makefile(一次生成 2 个可执行程序))
socket 函数(创建套接字)
网络编程中用于创建套接字(socket)的一个关键系统调用。它返回一个描述符,这个描述符可以用来识别新创建的套接字,并且在后续的操作中使用,比如绑定地址、监听连接请求、接受连接、发送和接收数据等。
cpp
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:指定通信域,也称为协议族(protocol family),指定了所使用的寻址方案。常见的值有:
AF_INET
或PF_INET
:IPv4互联网协议。
AF_INET6
或PF_INET6
:IPv6互联网协议。
AF_UNIX
或PF_UNIX
:本地通信(同一主机内的进程间通信)。
type:指定套接字的类型,决定了通信语义。主要的套接字类型包括:
SOCK_STREAM
:提供面向连接的、可靠的、双向传输服务,通常对应于TCP协议。
SOCK_DGRAM
:提供无连接的、不可靠的数据报文服务,通常对应于UDP协议。
SOCK_RAW
:提供对低层协议的直接访问,如ICMP或IGMP。
protocol:指定具体的协议。通常设置为0,表示使用默认协议。例如,对于
SOCK_STREAM
类型的套接字,默认协议是TCP;对于SOCK_DGRAM
类型的套接字,默认协议是UDP。如果需要明确指定某个协议,可以使用
IPPROTO_TCP
、IPPROTO_UDP
等常量。
- 函数成功时返回一个新的文件描述符,该描述符代表了新创建的套接字;
- 如果发生错误,则返回**-1**,并将
errno
设置为适当的错误码。
bind 函数(绑定套接字和本地地址)
用于将特定的本地地址(包括IP地址和端口号)分配给一个未绑定的套接字。这一步骤对于服务器应用程序尤其重要,因为它们需要监听某个特定的端口以接收来自客户端的连接请求或数据报文。
cpp
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:这是通过
socket()
系统调用创建的套接字描述符。
addr:指向包含要绑定到套接字上的地址信息的结构体的指针。这个结构体通常是一个**struct sockaddr_in
或者struct sockaddr_in6
(对于IPv6),但在传递给bind()
时必须被强制转换为struct sockaddr *
类型**。
addrlen:表示addr
参数所指向的地址结构体的大小,通常是**sizeof(struct sockaddr_in)
**或sizeof(struct sockaddr_in6)
。
返回值
- 如果成功,
bind()
返回 0。- 如果失败,返回 -1,并设置全局变量
errno
来指示错误的原因。若要绑定的端口已经被其他服务占用,bind()
调用将会失败。
recvfrom 函数(接收数据报)
用于接收来自无连接套接字(例如UDP)的数据报文的系统调用。它不仅可以接收数据,还可以获取发送方的地址信息。
cpp
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
sockfd:这是你要从中接收数据的套接字描述符。
buf:指向一个缓冲区,用于存储接收到的数据。是输出型参数。
len:指定buf
缓冲区的最大长度。
flags:提供额外的选项来影响接收操作的行为。常见的标志包括:
MSG_PEEK
:窥探消息,即读取数据但不将其从输入队列中移除。
MSG_WAITALL
:等待直到读取到请求的所有数据。
MSG_DONTWAIT
:非阻塞操作;如果数据不可用,则立即返回。
src_addr:指向一个struct sockaddr
结构体的指针,该结构体将被填充以包含发送方的地址信息 。如果你不需要发送方的地址信息,可以传递NULL
。是输出型参数。
addrlen:这是一个指向socklen_t
类型变量的指针,初始时应设置为src_addr
的大小。函数执行后,它会被更新为实际填充到src_addr
中的地址长度。是输出型参数。
返回值如果成功 ,
recvfrom()
返回接收到的字节数。如果到达文件末尾(仅适用于某些类型的套接字),则返回0。
如果发生错误,则返回 -1,并设置全局变量
errno
以指示错误的原因。
sendto 函数(发送数据报)
用于向无连接套接字(例如UDP)发送数据报文的系统调用。
cpp
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd:这是你要用来发送数据的套接字描述符。
buf:指向包含要发送的数据的缓冲区。是输入型参数。
len:指定buf
中数据的长度(以字节为单位)。
flags:提供额外的选项来影响发送操作的行为。常见的标志包括:
MSG_CONFIRM
:请求确认路径MTU是否仍然有效。
MSG_DONTROUTE
:跳过路由表,直接将数据发送到目标网络接口。
MSG_DONTWAIT
:使调用变为非阻塞模式。
MSG_EOR
:指示消息的结束(仅对某些协议有意义)。
MSG_NOSIGNAL
:抑制SIGPIPE信号(当写入一个已关闭写入的管道或套接字时产生的信号)。
dest_addr:指向struct sockaddr
结构体的指针,该结构体包含了接收方的地址信息。如果你之前已经通过connect()
将套接字连接到了某个地址,可以传递NULL
和0作为dest_addr
和addrlen
。
addrlen:指定dest_addr
的大小,通常是sizeof(struct sockaddr_in)
或sizeof(struct sockaddr_in6)
。
返回值如果成功,
sendto()
返回实际发送的字节数。如果发生错误,则返回 -1,并设置全局变量
errno
以指示错误的原因。
主要代码
Log.hpp / LockGuard.hpp
makefile(一次生成 2 个可执行程序)
cpp
.PHONY:all
all:udpserver udpclient
udpserver:Main.cc
g++ -o $@ $^ -std=c++14
udpclient:UdpClient.cc
g++ -o $@ $^ -std=c++14
.PHONY:clean
clean:
rm -f udpclient udpserver
InetAddr.hpp
cpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
// 从结构体中获得IP和端口号
class InetAddr
{
private:
void GetAddress(std::string *ip, uint16_t *port)
{
*port = ntohs(_addr.sin_port);
*ip = inet_ntoa(_addr.sin_addr);
}
public:
InetAddr(const struct sockaddr_in &addr)
: _addr(addr)
{
GetAddress(&_ip, &_port);
}
std::string Ip()
{
return _ip;
}
uint16_t Port()
{
return _port;
}
~InetAddr()
{
}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
UdpServer.hpp
创建套接字、填充结构体 并 调用 bind 函数,这部分的代码是常见的写法套路,需要熟练掌握。
cpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "InetAddr.hpp"
#include "Log.hpp"
enum
{
SOCKET_ERROR = 1, // 套接字创建失败
BIND_ERROR, // 绑定失败
USAGE_ERROR // 用法错误
};
const static int defaultfd = -1; // 默认描述符的值为-1
class UdpServer
{
public:
// 外界只需要传端口号
UdpServer(uint16_t port)
: _port(port), _sockfd(defaultfd), _isrunning(false)
{
}
~UdpServer()
{
}
void InitServer()
{
// 创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0) // 创建失败
{
LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);
exit(SOCKET_ERROR);
}
LOG(INFO, "socket create success, sockfd: %d\n", _sockfd);
// 填 sockaddr_in
struct sockaddr_in local; // 在栈上开辟空间
bzero(&local, sizeof(local)); // 将内存设置为全0
local.sin_family = AF_INET; // 协议族(用哪个协议)
local.sin_port = htons(_port); // 端口号,转为网络序列(大端)
local.sin_addr.s_addr = INADDR_ANY; // IP地址
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
// 绑定失败
if (n < 0)
{
LOG(FATAL, "bind error,%s,%d\n", strerror(errno), errno);
exit(BIND_ERROR);
}
}
// 启动服务器
void Start()
{
_isrunning = true;
while (true)
{
char buffer[1024];
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)
{
buffer[n] = 0;
InetAddr addr(peer);//发送方的IP和端口号
LOG(DEBUG, "get message from [%s:%d]:%s\n", addr.Ip().c_str(), addr.Port(), buffer);
// 发送数据报,将我们收到的数据报发回给对方
sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
}
_isrunning = false;
}
}
private:
int _sockfd;
uint16_t _port;
bool _isrunning;
};
Main.cc
cpp
#include<iostream>
#include<memory>
#include"UdpServer.hpp"
void Usage(std::string proc)
{
std::cout<<"Usage:\n\t"<<proc<<" local_port\n"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(USAGE_ERROR);
}
EnableScreen();
uint16_t port=std::stoi(argv[1]);
std::unique_ptr<UdpServer> usvr=std::make_unique<UdpServer>(port);
usvr->InitServer();
usvr->Start();
return 0;
}
UdpClient.cc
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
<< std::endl;
}
int main(int argc, char *argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(1);
}
std::string serverip=argv[1];//获得IP
uint16_t serverport=std::stoi(argv[2]);;//获得端口号
int sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
std::cerr<<"socket error"<<std::endl;
}
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(serverport);
server.sin_addr.s_addr=inet_addr(serverip.c_str());
//客户端不需要调用bind函数
std::string message;//消息
while(true)
{
std::cout<<"Please Enter# ";
std::getline(std::cin,message);//输入消息
//发送数据报
sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
char buffer[1024];
//接收数据报
ssize_t n=recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);
if(n>0)
{
buffer[n]=0;
std::cout<<"server echo# "<<buffer<<std::endl;
}
}
return 0;
}