当前位置: 首页 > news >正文

计算机网络:【socket】【UDP】【地址转换函数】【TCP】

一.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_INETPF_INET 可互换)。
  • 错误处理
    若参数无效(如不支持的协议族),socket() 会返回 -1,并设置 errnoEAFNOSUPPORT

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,表示根据domaintype自动选择默认协议。

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结构

socketAPI 是一层抽象的网络编程接口 , 适用于各种底层网络协议 , IPv4 IPv6, 以及后面的UNIXDomainSocket. 然而 , 各种网络协议的地址格式并不相同 .

• 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.还是创建socketint 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# "; // 1std::getline(std::cin, input); // 0int 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. 创建socketsockfd = 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.设置监听状态,listenif (::listen(_listensock, 5) < 0){LOG(LogLevel::FATAL) << "listen error...";exit(LISTEN_ERR);}LOG(LogLevel::DEBUG) << "listen success";}void Start(){_isruning = true;while (_isruning){// 4.获取链接,acceptstruct 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;
};

http://www.lqws.cn/news/591067.html

相关文章:

  • 【文件解析】json.load(fp)
  • 借助工具给外语视频加双语字幕的实用指南​
  • 赋能城市安全韧性|众智鸿图总裁扈震受邀出席智慧城市大会发表主题报告
  • 【锂电池剩余寿命预测】GRU门控循环单元锂电池剩余寿命预测(Pytorch完整源码和数据)
  • 【机器学习深度学习】模型微调的基本概念与流程
  • OpenGL 3D编程大师基础之路:从几何体到物理引擎
  • 组合模式在SSO搜索和关键词重叠法中的优化应用
  • 用java,把12.25.pdf从最后一个点分割,得到pdf
  • 大模型及agent开发5 OpenAI Assistant API 进阶应用
  • 浏览器F12开发者工具的使用
  • 隔离网络(JAVA)
  • Ansys Speos | Speos Camera 传感器机器视觉示例
  • iOS 越狱插件 主动调用C函数和OC函数
  • no module named ultralytics
  • Spring Boot WebSocket方案终极指南:Netty与官方Starter对比与实践
  • 【团队开发】git 操作流程
  • Electron 沙箱模式深度解析:构建更安全的桌面应用
  • c++学习(八、函数指针和线程)
  • idea maven自动导包 自动清除无用的依赖包
  • 怎么查看Android设备中安装的某个apk包名和启动页activity
  • 设计模式-模板模式
  • Linux驱动学习day12(mmap)
  • 道可云人工智能每日资讯|浦东启动人工智能创新应用竞赛
  • 业界优秀的零信任安全管理系统产品介绍
  • 从0开始学习R语言--Day35--核密度动态估计
  • ABB PPD 113 B03-23-100110 3 bhe 023584 r 2334 AC 800 pec控制系统
  • 腾讯 iOA 零信任产品:安全远程访问的革新者
  • ASP.NET代码审计 MVC架构 SQL注入漏洞
  • LINUX2.6设备注册与GPIO相关的API
  • 将N8N配置为服务【ubuntu】