计算机网络:【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_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.还是创建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;
};