一.socket
1.1socket接口
它返回的是一个文件描述符。创建socket文件描述符(TCP/UDP,客户端+服务器)
• socket()打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描 述符;
• 应用程序可以像读写文件一样用 read/write 在网络上收发数据;
• 如果 socket()调用出错则返回-1;
• 对于 IPv4, family 参数指定为 AF_INET;
• 对于 UDP 协议,type 参数指定为 SOCK_DGRAM, 表示面向数据报的传输协议
• protocol 参数的介绍从略,指定为 0 即可。
Socket的第一个参数:domain(域/协议族)
Socket编程中,socket()
函数的第一个参数指定通信的协议族(Protocol Family),决定了 socket 的底层通信协议类型。常见的协议族包括:
AF_INET
IPv4协议族,用于基于IPv4的网络通信(如互联网或本地网络)。这是最常用的选项。int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
AF_INET6
IPv6协议族,支持IPv6地址格式的通信。int socket_fd = socket(AF_INET6, SOCK_STREAM, 0);
AF_UNIX/AF_LOCAL
本地通信协议族,用于同一台主机上的进程间通信(通过文件系统路径)。int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
AF_PACKET
底层数据包接口(如直接访问网络层数据包,常用于网络工具开发)。
参数意义
该参数定义了 socket 的地址类型,直接影响后续绑定(bind()
)或连接(connect()
)时使用的地址结构体。例如:
- 使用
AF_INET
时,地址结构体为struct sockaddr_in
(包含IPv4地址和端口)。 - 使用
AF_UNIX
时,地址结构体为struct sockaddr_un
(包含文件路径)。
注意事项
- AF_ vs PF_:
历史上有AF_
(地址族)和PF_
(协议族)两种前缀,但在现代系统中两者通常等价(如AF_INET
和PF_INET
可互换)。 - 错误处理:
若参数无效(如不支持的协议族),socket()
会返回-1
,并设置errno
为EAFNOSUPPORT
。
socket 的第二个参数详解
socket 的第二个参数指定套接字的类型,决定了数据传输的方式和协议特性。以下是常见的套接字类型及其用途:
SOCK_STREAM
- 面向连接的字节流套接字,使用 TCP 协议。
- 提供可靠、有序、双向的数据传输。
- 适用于需要数据完整性的场景,如 HTTP、FTP。
SOCK_DGRAM
- 无连接的数据报套接字,使用 UDP 协议。
- 传输速度快但不可靠,可能丢包或乱序。
- 适用于实时性要求高的场景,如视频流、DNS 查询。
SOCK_RAW
- 原始套接字,允许直接访问底层协议(如 IP、ICMP)。
- 需要管理员权限,常用于网络探测或自定义协议开发。
SOCK_SEQPACKET
- 提供有序、可靠、基于消息的传输(如 SCTP 协议)。
- 结合了流式和数据报的特性,适用于电信领域。
SOCK_RDM
- 可靠的数据报套接字,保证数据不丢失但可能乱序。
- 较少使用,特定于某些协议(如 RDS)
注意事项
- 第二个参数需与第一个参数(地址族,如
AF_INET
)兼容。 - 某些类型(如
SOCK_RAW
)可能需要特殊权限。 - 具体支持的类型取决于操作系统和协议栈。
第三个参数
指定具体的传输协议。通常设置为0
,表示根据domain
和type
自动选择默认协议。
1.2bind接口
绑定端口号(TCP/UDP,服务器)
• 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服 务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用 bind 绑定一 个固定的网络地址和端口号;
• bind()成功返回 0,失败返回-1。
• bind()的作用是将参数 sockfd 和 myaddr 绑定在一起, 使 sockfd 这个用于网络 通讯的文件描述符监听 myaddr 所描述的地址和端口号;
• struct sockaddr *是一个通用指针类型,myaddr 参数实际上可以接受 多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数 addrlen 指定结构体的长度;
1.3listen接口
开始监听 socket (TCP, 服务器)
• listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接 等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是 5)
• listen()成功返回 0,失败返回-1;
1.4accept接口
接收请求 (TCP, 服务器)
它返回的也是一个文件描述符,跟listensocket配合着使用
• 三次握手完成后, 服务器调用 accept()接受连接;
• 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端 连接上来;
• addr 是一个传出参数,accept()返回时传出客户端的地址和端口号;
• 如果给 addr 参数传 NULL,表示不关心客户端的地址;
• addrlen 参数是一个传入传出参数(value-result argument), 传入的是调用者提 供的, 缓冲区 addr 的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);
1.5connect接口
建立连接 (TCP, 客户端)
• 客户端需要调用 connect()连接服务器;
• connect 和 bind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而 connect 的参数是对方的地址;
• connect()成功返回 0,出错返回-1;
1.6sockeaddr结构

• 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 结构体指针做为参数;
虽然 socket api 的接口是 sockaddr, 但是我们真正在基于 IPv4 编程时, 使用的数据结 构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP 地址.
in_addr 用来表示一个 IPv4 的 IP 地址. 其实就是一个 32 位的整数;
通常这样初始化
网络地址为 INADDR_ANY, 这个宏表示本地的任意 IP 地址,因为服务器可能有 多个网卡,每个网卡也可能绑定多个 IP 地址, 这样设置可以在所有的 IP 地址上监听, 直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址
二.UDP
2.1简单的接口代码(demo)
InetAddr.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
class InetAddr
{
public:
InetAddr(struct sockaddr_in &addr):_addr(addr)
{
_port = ntohs(addr.sin_port);
_ip = inet_ntoa(addr.sin_addr);
}
std::string GetIp()
{
return _ip;
}
uint16_t GetPort()
{
return _port;
}
private:
std::string _ip;//点分十进制
uint16_t _port;
struct sockaddr_in _addr;
};
NoCopy.hpp
#pragma once
#include<iostream>
class NoCopy
{
public:
NoCopy()
{}
NoCopy(const NoCopy&) = delete;
const NoCopy& operator=(const NoCopy&) = delete;
~NoCopy()
{}
};
Common.hpp
#pragma once
enum ExitCode
{
OK = 0,
SOCKET_ERR,
BIND_ERR
};
UdpServer.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
using namespace LogModule;
const static int defaultport = 8888;
const static int defaultsockfd = -1;
const static int defaultsize = 1024;
// 禁止拷贝和赋值
class UdpServer : public NoCopy
{
public:
UdpServer(int port = defaultport) : _port(port), _sockfd(defaultsockfd)
{
}
void Init()
{
// 1.创建socket
_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error...";
exit(SOCKET_ERR);
}
LOG(LogLevel::DEBUG) << "sockfd success: " << _sockfd;
// 2.初始化结构体
struct sockaddr_in local;
bzero(&local,sizeof(local));
//memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;//注意这里不要写特定的ip值
local.sin_port = htons(_port);
// 3.bind,结构体填完,还要设置到内核中
int n = ::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n!=0)
{
LOG(LogLevel::FATAL) << "bind error...";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG) << "bind success...";
}
void Start()
{
while(true)
{
char buffer[defaultsize];
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)
{
InetAddr addr(peer);
buffer[n] = 0;
std::cout << "client: [" << addr.GetIp() << " "<<addr.GetPort()<<" say# ]" << buffer <<std::endl;
sendto(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,len);
}
}
}
~UdpServer()
{
}
private:
//std::string _ip;
uint16_t _port;
int _sockfd;
};
UdpClient.cc
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <cstdlib>
#include <string.h>
#include <memory>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
using namespace LogModule;
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
// 1.还是创建socket
int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error...";
return 2;
}
LOG(LogLevel::DEBUG) << "sockfd success: " << sockfd;
// 2.clent 也要进行bind,但是不要跟server一样显示的进行bind,因为服务器的port是众所周知的,而client的port可能会非常多
// 导致端口号冲突,bind的操作OS会随机给我们分配端口号
// 3.填充server信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_addr.s_addr = inet_addr(server_ip.c_str()); // ip地址是点分十进制转换为网络序列的32为整数
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
while (true)
{
std::string in;
std::cout << "please enter# ";
std::getline(std::cin, in);
// std::cin>>in;
// std::cout<< 1 <<std::endl;
int n = sendto(sockfd, in.c_str(), in.size(), 0, (struct sockaddr *)&server, sizeof(server));
if (n < 0)
{
LOG(LogLevel::ERROR) << "sendto failed";
continue;
}
char buffer[1024];
struct sockaddr_in peer;
socklen_t peer_len = sizeof(peer);
int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &peer_len);
if (m > 0)
{
buffer[m] = 0;
std::cout << buffer << std::endl;
}
}
return 0;
}
UdpServer.cc
#include"UdpServer.hpp"
int main()
{
Enable_Console_Log_Strtegy();
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>();
usvr->Init();
usvr->Start();
return 0;
}
运行结果:
服务器:
客户端:
2.2代码小升级,网络字典查询
其实也就是在UdpServer端加上了一个上层调用的函数,让我们能够实现单词之间的转化
UdpServer.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
using namespace LogModule;
const static int defaultport = 8888;
const static int defaultsockfd = -1;
const static int defaultsize = 1024;
using func_t = std::function<std::string(const std::string&,InetAddr& cli)>;
// 禁止拷贝和赋值
class UdpServer : public NoCopy
{
public:
UdpServer(func_t func,int port = defaultport) : _port(port), _sockfd(defaultsockfd),_func(func)
{
}
void Init()
{
// 1.创建socket
_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error...";
exit(SOCKET_ERR);
}
LOG(LogLevel::DEBUG) << "sockfd success: " << _sockfd;
// 2.初始化结构体
struct sockaddr_in local;
bzero(&local,sizeof(local));
//memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;//注意这里不要写特定的ip值
local.sin_port = htons(_port);
// 3.bind,结构体填完,还要设置到内核中
int n = ::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n!=0)
{
LOG(LogLevel::FATAL) << "bind error...";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG) << "bind success...";
}
void Start()
{
while(true)
{
char buffer[defaultsize];
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)
{
InetAddr addr(peer);
buffer[n] = 0;
std::string result = _func(buffer,addr);
std::cout << "client: [" << addr.GetIp() << " "<<addr.GetPort()<<" say# ]" << buffer <<std::endl;
sendto(_sockfd,result.c_str(),result.size(),0,(struct sockaddr*)&peer,len);
}
}
}
~UdpServer()
{
}
private:
//std::string _ip;
uint16_t _port;
int _sockfd;
func_t _func;
};
Dict.hpp
#include<unordered_map>
#include<fstream>
#include"UdpServer.hpp"
const static std::string defaultdict = "./dictionary.txt";
const static std::string sep = ":";
class Dict
{
public:
Dict(const std::string &path = defaultdict):_dict_path(path)
{}
bool LoadWord()
{
std::ifstream in(_dict_path);
if(!in.is_open())
{
LOG(LogLevel::FATAL) << "open file error...";
return false;
}
std::string line;
while(std::getline(in,line))//注意是从文件流里去读取,一次读一行
{
auto pos = line.find(sep);
if(pos == std::string::npos)
{
LOG(LogLevel::FATAL) << "解析文件失败";
continue;
}
std::string english = line.substr(0,pos);
std::string chinese = line.substr(pos + sep.size());
if(english.empty()||chinese.empty())
{
LOG(LogLevel::FATAL) << "没有有效内容";
continue;//返回继续进行解析文件
}
_dict[english] = chinese;
LOG(LogLevel::DEBUG) << "加载" << line << "成功";
}
in.close();
return true;
}
std::string Transact(std::string word,InetAddr &client)
{
auto pos = _dict.find(word);
if(pos == _dict.end())
{
LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.GetIp() << " : " << client.GetPort() << "]# " << word << "->None";
return "None";
}
LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.GetIp() << " : " << client.GetPort() << "]# " << word << "->" << pos->second;
return pos->second;
}
~Dict()
{}
private:
std::string _dict_path;
std::unordered_map<std::string,std::string> _dict;
};
UdpServer.cc
#include"UdpServer.hpp"
#include"Dict.hpp"
int main(int argc,char* argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
return 1;
}
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strtegy();
Dict dict;
dict.LoadWord();
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>([&dict](const std::string& word,InetAddr& cli)->std::string{
return dict.Transact(word,cli);
},port);
usvr->Init();
usvr->Start();
return 0;
}
dictionary.txt:
实现效果:
2.3多线程下简单聊天室
Route.hpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Log.hpp"
using namespace LogModule;
class Route
{
bool IsExist(InetAddr& client)
{
for (auto &user : _online_user)
{
if (user == client)
{
return true;
}
}
return false;
}
void AddUser(InetAddr &client)
{
LOG(LogLevel::INFO) << "新增一个在线用户: " << client.StringAddr();
_online_user.push_back(client);
}
void DeleteUser(InetAddr &peer)
{
for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++)
{
if (*iter == peer)
{
_online_user.erase(iter);
LOG(LogLevel::INFO) << "删除一个在线用户:" << peer.StringAddr() << "成功";
break;
}
}
}
public:
Route()
{
}
void MessageRoute(int sockfd, std::string &message, InetAddr &peer)
{
if(!IsExist(peer))
{
AddUser(peer);
}
std::string send_message = peer.StringAddr() + "# " + message; // 127.0.0.1:8080# 你好
for(auto &user : _online_user)
{
sendto(sockfd,send_message.c_str(),send_message.size(),0,user.GetAddr(),sizeof(*user.GetAddr()));
}
// 这个用户一定已经在线了
if (message == "QUIT")
{
LOG(LogLevel::INFO) << "删除一个在线用户: " << peer.StringAddr();
DeleteUser(peer);
}
}
~Route()
{
}
private:
std::vector<InetAddr> _online_user;
};
UdpServer.cc
#include"UdpServer.hpp"
#include"Route.hpp"
#include"ThreadPool.hpp"
using namespace ThreadPoolModule;
using task_t = std::function<void()>;
int main(int argc,char* argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
return 1;
}
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strtegy();
//1.路由服务
Route r;
//2.线程池
auto tp = ThreadPool<task_t>::GetInstance();
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>([&r,&tp](int sockfd,const std::string& message,InetAddr& cli){
task_t t = std::bind(&Route::MessageRoute,&r,sockfd,message,cli);
tp->Enqueue(t);
},port);
usvr->Init();
usvr->Start();
return 0;
}
UdpServer.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
using namespace LogModule;
const static int defaultport = 8888;
const static int defaultsockfd = -1;
const static int defaultsize = 1024;
using func_tt = std::function<void(int sockfd,const std::string&,InetAddr&)>;
// 禁止拷贝和赋值
class UdpServer : public NoCopy
{
public:
UdpServer(func_tt func,uint16_t port = defaultport) : _port(port), _sockfd(defaultsockfd),_func(func)
{
}
void Init()
{
// 1.创建socket
_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error...";
exit(SOCKET_ERR);
}
LOG(LogLevel::DEBUG) << "sockfd success: " << _sockfd;
// 2.初始化结构体
struct sockaddr_in local;
bzero(&local,sizeof(local));
//memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;//注意这里不要写特定的ip值
local.sin_port = htons(_port);
// 3.bind,结构体填完,还要设置到内核中
int n = ::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n!=0)
{
LOG(LogLevel::FATAL) << "bind error...";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG) << "bind success...";
}
void Start()
{
while(true)
{
char buffer[defaultsize];
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)
{
InetAddr addr(peer);
buffer[n] = 0;
_func(_sockfd,buffer,addr);//路由功能,外部传函数
}
}
}
~UdpServer()
{
}
private:
//std::string _ip;
uint16_t _port;
int _sockfd;
func_tt _func;
};
客户端也进行多线程修改
UdpClient.cc
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Thread.hpp"
int sockfd = 0;
std::string server_ip;
uint16_t server_port = 0;
pthread_t id;
using namespace ThreadModule;
void Recv()
{
while (true)
{
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (m > 0)
{
buffer[m] = 0;
std::cerr << buffer << std::endl; // 2
}
}
}
void Send()
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
const std::string online = "inline";
sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));
while (true)
{
std::string input;
//std::cout << "Please Enter# "; // 1
std::getline(std::cin, input); // 0
int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
(void)n;
if (input == "QUIT")
{
pthread_cancel(id);
break;
}
}
}
// client 我们也要做多线程改造
// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
server_ip = argv[1];
server_port = std::stoi(argv[2]);
// 1. 创建socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return 2;
}
// 2. 创建线程
Thread recver(Recv);
Thread sender(Send);
recver.Start();
sender.Start();
recver.Join();
sender.Join();
// 2. 本地的ip和端口是什么?要不要和上面的“文件”关联呢?
// 问题:client要不要bind?需要bind.
// client要不要显式的bind?不要!!首次发送消息,OS会自动给client进行bind,OS知道IP,端口号采用随机端口号的方式
// 为什么?一个端口号,只能被一个进程bind,为了避免client端口冲突
// client端的端口号是几,不重要,只要是唯一的就行!
// 填写服务器信息
return 0;
}
三.地址转换函数
sockaddr_in 中的成员 struct in_addr sin_addr 表示 32 位 的 IP 地址
但是我们通常用点分十进制的字符串表示 IP 地址,以下函数可以在字符串表示 和 in_addr 表示之间转换;
字符串转 in_addr 的函数:
in_addr 转字符串的函数:
其中 inet_pton 和 inet_ntop 不仅可以转换 IPv4 的 in_addr,还可以转换 IPv6 的 in6_addr,因此函数接口是 void *addrptr。
inet_ntoa
inet_ntoa 这个函数返回了一个 char*, 很显然是这个函数自己在内部为我们申请了一块 内存来保存 ip 的结果. 那么是否需要调用者手动释放呢?
man 手册上说, inet_ntoa 函数, 是把这个返回结果放到了静态存储区. 这个时候不需要 我们手动进行释放. 那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:
因为 inet_ntoa 把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆 盖掉上一次的结果.
在多线程环境下, 推荐使用 inet_ntop, 这个函数由调用者提供一个缓冲区保存 结果, 可以规避线程安全问题;
四.TCP
多线程版本,多进程版本,线程池版本
TcpServer.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include <sys/types.h>
#include <sys/wait.h>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
#include "Command.hpp"
#include "ThreadPool.hpp"
using namespace LogModule;
using namespace ThreadPoolModule;
using func_tt = std::function<void()>;
class TcpServer : public NoCopy
{
public:
TcpServer(uint16_t port) : _port(port), _isruning(false)
{
}
void Init()
{
// 1.创建listensocket
_listensock = ::socket(AF_INET, SOCK_STREAM, 0);
if (_listensock < 0)
{
LOG(LogLevel::FATAL) << "socket error...";
exit(SOCKET_ERR);
}
LOG(LogLevel::DEBUG) << "socket success: " << _listensock;
// 2.bind
// struct sockaddr_in server;
// bzero(&server, sizeof(server));
// server.sin_addr.s_addr = INADDR_ANY;
// server.sin_family = AF_INET;
// server.sin_port = htons(_port);
InetAddr server(_port);
int n = ::bind(_listensock, CONV(&server.NetAddr()), server.GetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error...";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG) << "bind success";
// 3.设置监听状态,listen
if (::listen(_listensock, 5) < 0)
{
LOG(LogLevel::FATAL) << "listen error...";
exit(LISTEN_ERR);
}
LOG(LogLevel::DEBUG) << "listen success";
}
void Start()
{
_isruning = true;
while (_isruning)
{
// 4.获取链接,accept
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = ::accept(_listensock, CONV(&peer), &len);
if (sockfd < 0)
{
LOG(LogLevel::FATAL) << "accept error...";
continue;
}
InetAddr client(peer);
LOG(LogLevel::DEBUG) << "accept success ,name: " << client.StringAddr();
// 5.提供服务
// Service(sockfd);
// close(sockfd);
// ProcessConnection(sockfd,peer);
//ThreadConnection(sockfd, peer);
ThreadPoolConnection(sockfd,client);
}
}
void Service(int sockfd,InetAddr& peer)
{
char buffer[1024];
while (true)
{
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = 0;
std::cout << "client say# " << buffer << std::endl;
std::string echo_string = "server echo# ";
echo_string += buffer;
write(sockfd, echo_string.c_str(), echo_string.size());
}
else if (n == 0) // read 如果返回值是 0,表示读到了文件结尾(对端关闭了连接!)
{
LOG(LogLevel::FATAL) << peer.StringAddr() << "client quit";
break;
}
else
{
LOG(LogLevel::FATAL)<< peer.StringAddr() << "read error...";
break;
}
}
}
// void Service(int sockfd,InetAddr& peer)
// {
// Command com;
// char buffer[1024];
// while(true)
// {
// // 1. 先读取数据
// // a. n>0: 读取成功
// // b. n<0: 读取失败
// // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
// ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
// if (n > 0)
// {
// // buffer是一个英文单词 or 是一个命令字符串
// buffer[n] = 0; // 设置为C风格字符串, n<= sizeof(buffer)-1
// std::string echo_string = com.Execute(buffer, peer);
// write(sockfd, echo_string.c_str(), echo_string.size());
// }
// else if (n == 0)
// {
// LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
// close(sockfd);
// break;
// }
// else
// {
// LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
// close(sockfd);
// break;
// }
// }
// }
class ThreadData
{
public:
ThreadData(int sockfd, InetAddr &ie, TcpServer *t) : _sockfd(sockfd), _client(ie), _tsv(t)
{
}
~ThreadData()
{
}
int _sockfd;
InetAddr _client;
TcpServer *_tsv;
};
//线程池版本
void ThreadPoolConnection(int sockfd,InetAddr& peer)
{
ThreadPool<func_tt>::GetInstance()->Enqueue([this,sockfd,&peer](){
this->Service(sockfd,peer);
});
}
// 多线程服务
static void *Routine(void *args)
{
pthread_detach(pthread_self());
ThreadData *tdv = static_cast<ThreadData *>(args);
tdv->_tsv->Service(tdv->_sockfd,tdv->_client);
close(tdv->_sockfd);
delete tdv;
return nullptr;
}
void ThreadConnection(int sockfd, struct sockaddr_in &peer)
{
InetAddr client(peer);
pthread_t tid;
// std::shared_ptr<ThreadData> tdsv = std::make_shared<ThreadData>(sockfd,client,this);
ThreadData *tdv = new ThreadData(sockfd, client, this);
pthread_create(&tid, nullptr, Routine, (void *)tdv);
}
// 多进程服务
void ProcessConnection(int sockfd, struct sockaddr_in &peer)
{
pid_t id = fork();
if (id < 0)
{
close(sockfd);
return;
}
else if (id == 0)
{
// 子进程再去创建子进程,孙子进程
// 子进程,子进程除了看到sockfd,能看到listensockfd吗??
// 我们不想让子进程访问listensock!
close(_listensock);
if (fork() > 0)
{
exit(0); // 大于0,是子进程
}
// 到这里是孙子进程,是孤儿进程,系统去回收
InetAddr client(peer);
LOG(LogLevel::DEBUG) << "process connection: " << client.StringAddr();
Service(sockfd,client);
close(sockfd);
exit(0);
}
else
{
// 父进程去关闭创建的子进程
close(sockfd);
// 这里要等待子进程
pid_t rid = waitpid(id, nullptr, 0);
(void)rid;
}
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listensock;
bool _isruning;
};
TcpClient.cc
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
using namespace LogModule;
void Usage(const std::string &process)
{
std::cout << "Usage: " << process << " server_ip server_port" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 1;
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
// 1.建立套接字
int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error...";
exit(SOCKET_ERR);
}
LOG(LogLevel::DEBUG) << "socket success: " << sockfd;
// 2.不需要用户bind
// 3.连接
InetAddr local(server_ip, server_port);
int n = ::connect(sockfd, local.GetAddr(), local.GetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "connect error...";
exit(CONNECT_ERR);
}
LOG(LogLevel::DEBUG) << "connect success: " << sockfd;
// 4.链接成功,通信
while (true)
{
std::string client_buffer;
std::cout << "Please Enter# ";
std::getline(std::cin, client_buffer);
ssize_t n = write(sockfd, client_buffer.c_str(), client_buffer.size());
char server_buffer[1024];
ssize_t size = read(sockfd, server_buffer, sizeof(server_buffer)-1);
if(size > 0)
{
server_buffer[size] = 0;
std::cout << server_buffer << std::endl;
}
}
close(sockfd);
return 0;
}
实现一个简单的远程命令
Command.hpp
#pragma once
#include <iostream>
#include <string>
#include <set>
#include <unistd.h>
#include <fstream>
#include "InetAddr.hpp"
#include "Log.hpp"
using namespace LogModule;
class Command
{
public:
Command()
{
// 严格匹配
_WhiteListCommands.insert("ls");
_WhiteListCommands.insert("pwd");
_WhiteListCommands.insert("ls -l");
_WhiteListCommands.insert("touch haha.txt");
_WhiteListCommands.insert("who");
_WhiteListCommands.insert("whoami");
}
bool IsSafeCommand(const std::string &cmd)
{
auto iter = _WhiteListCommands.find(cmd);
return iter != _WhiteListCommands.end();
}
std::string Execute(const std::string &cmd, InetAddr &addr)
{
// 1. 属于白名单命令
if(!IsSafeCommand(cmd))
{
return std::string("坏人");
}
std::string who = addr.StringAddr();
// 2. 执行命令
// std::ifstream in;
// in.open(cmd.c_str());
FILE *fp = popen(cmd.c_str(), "r");
if(nullptr == fp)
{
return std::string("你要执行的命令不存在: ") + cmd;
}
std::string res;
char line[1024];
while(fgets(line, sizeof(line), fp))
{
res += line;
}
pclose(fp);
std::string result = who + "execute done, result is: \n" + res;
LOG(LogLevel::DEBUG) << result;
return result;
}
~Command()
{}
private:
std::set<std::string> _WhiteListCommands;
};