
前言: 上文我们讲到了UDP的Scket编程【Linux网络】基于UDP的Socket编程,实现简单聊天室-CSDN博客 本文我们来讲基于TCP的Socket编程,TCP与UDP大致上差不多,细节上注意区别。
socket()
int socket(int domain, int type, int protocol);
作用:创建一个套接字相当于买了一部手机,但还没插卡)
参数:
domain: 协议域,常用 AF_INET (IPv4)
type: 套接字类型,TCP 必选 SOCK_STREAM (面向字节流)
protocol: 协议,通常填 0 (自动选择 TCP)
返回值:成功返回文件描述符 (sockfd),失败返回 -1close()
int close(int fd);
作用:关闭连接,释放资源(相当于挂断电话)
注意:TCP 是全双工的,close 会引发四次挥手断开连接bind()
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用:将套接字与具体的 IP地址 和 端口号 绑定(相当于给手机插上 SIM 卡,固定号码)
参数:需要传入一个 sockaddr_in 结构体(强转为 sockaddr*),包含 IP 和 Port
注意:服务器通常绑定 INADDR_ANY,表示接收本机所有网卡的连接listen()
int listen(int sockfd, int backlog);
作用:将套接字设置为 监听状态(相当于打开手机铃声,准备接电话)
参数:backlog 指的是全连接队列的长度(即已经完成三次握手但还没被 accept 取走的连接数)
注意:调用后,TCP 状态机进入 LISTEN 状态accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
作用:从已完成连接队列中获取一个连接(相当于接起电话)
返回值:非常重要!!!
成功返回一个新的文件描述符(connfd),专门用于和这个特定的客户端通信
区别:
socket() 返回的 fd 是“门迎”,只负责揽客(监听)
accept() 返回的 fd 是“服务员”,只负责服务这一个客人(通信)connect()
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用:向服务器发起连接请求(相当于拨打电话)
流程:调用该函数会触发 TCP 的 三次握手
返回值:0 表示连接成功,-1 表示失败read() / recv()
ssize_t read(int fd, void *buf, size_t count);
作用:从套接字读取数据
返回值:
> 0: 实际读取到的字节数
= 0: 对端关闭了连接 (读到了 EOF,非常重要的判断条件)
< 0: 出错 (需检查 errno,如 EAGAIN 表示非阻塞模式下暂无数据)write() / send()
ssize_t write(int fd, const void *buf, size_t count);
作用:向套接字写入数据
注意:TCP 是流式协议,发送的数据可能会被拆包或粘包,需要应用层协议(如添加包头长度)来解决。TcpClient.cc
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
#include "Comment.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
void Usage(std::string s)
{
std::cerr << "Please use: " << s << " ip prot" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(TCPCLIENT_ERR);
}
// 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
LOG(LogLevel::ERROR) << "socket error";
exit(SOCK_ERR);
}
// 绑定信息,但由OS自动绑定,无需我们自己绑定
// 申请建立连接请求
uint16_t prot = stoi(argv[2]);
InetAddr addr(argv[1], prot);
int n = connect(sockfd, addr.Getaddr(), addr.Len());
if (n < 0)
{
LOG(LogLevel::ERROR) << "connect error";
exit(CONN_ERR);
}
while (true)
{
cout << "Please Enter@ ";
string str;
cin >> str;
// 向服务器发送信息
write(sockfd, str.c_str(), str.size());
// 接收服务器的信息
char buff[1024];
int n = read(sockfd, buff, sizeof(buff));
if (n > 0)
{
buff[n] = 0;
cout << "Eoch: " << buff << endl;
}
}
}TcpServer.cc
#include "TcpServer.hpp"
void usage(string str)
{
cerr << "Please use: " << str << " prot" << endl;
}
int main(int agrc, char *argv[])
{
if (agrc != 2)
{
usage(argv[0]);
exit(TCPSERVER_ERR);
}
uint16_t prot = stoi(argv[1]);
TcpServer tcp(prot);
tcp.Init();
tcp.Run();
}TcpServer.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <functional>
#include <unistd.h>
#include "Log.hpp"
#include "Comment.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
const static int backlog = 8;
string Func(string str, struct sockaddr_in addr)
{
string s = "hello";
return s + str;
}
class TcpServer : public NoCopy
{
private:
using func_t = std::function<string(string, struct sockaddr_in)>;
public:
TcpServer(uint16_t prot, func_t func = Func)
: _func(func),
_prot(prot)
{
}
void Init()
{
// 创建套接字:数据类似为流式
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::ERROR) << "socket error";
exit(SOCK_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3
// 绑定
InetAddr inetaddr(_prot);
int n = bind(_listensockfd, inetaddr.Getaddr(), inetaddr.Len());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // 3
// 将套接字设置为listen状态:TCP服务器处于listen状态下,才可以被连接
int m = listen(_listensockfd, backlog);
if (m < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // 3
}
void Run()
{
while (true)
{
// 获取连接,建立通信(若获取连接失败,则再次进行获取)
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// accept返回一个新的fd,由新的套接字提供服务
_sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len);
if (_sockfd < 0)
{
LOG(LogLevel::ERROR) << "accept error";
continue;
}
// 连接获取成功,执行对应方法
Service(peer);
// 任务执行完毕
close(_sockfd);
break;
}
}
void Service(struct sockaddr_in &peer)
{
while (true)
{
// 接收客户端信息
// a. n>0: 读取成功
// b. n<0: 读取失败
// c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
char buff[1024];
ssize_t n = read(_sockfd, buff, sizeof(buff) - 1);
if (n > 0)
{
buff[n] = 0;
cout << "tcpclient say: " << buff << endl;
// 调用方法
string ret = _func(string(buff), peer);
// 发送信息
write(_sockfd, ret.c_str(), ret.size());
}
else if (n == 0)
{
LOG(LogLevel::INFO) << "读取到了文件末尾~";
return;
}
else
{
LOG(LogLevel::ERROR) << "读取失败!";
return;
}
}
}
private:
int _listensockfd;
int _sockfd;
uint16_t _prot;
func_t _func;
};将服务器的代码逻辑由单进程,改进为多线程,让其可以同时服务于多个用户
#pragma once
#include <pthread.h>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <functional>
#include <unistd.h>
#include "Log.hpp"
#include "Comment.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
const static int backlog = 8;
string Func(string str)
{
string s = "hello";
return s + str;
}
class TcpServer : public NoCopy
{
private:
using func_t = std::function<string(string)>;
public:
TcpServer(uint16_t prot, func_t func = Func)
: _func(func),
_prot(prot)
{
}
void Init()
{
// 创建套接字:数据类似为流式
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::ERROR) << "socket error";
exit(SOCK_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3
// 绑定
InetAddr inetaddr(_prot);
int n = bind(_listensockfd, inetaddr.Getaddr(), inetaddr.Len());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // 3
// 将套接字设置为listen状态:TCP服务器处于listen状态下,才可以被连接
int m = listen(_listensockfd, backlog);
if (m < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // 3
}
// 作为参数传入线程执行函数中
class ThreadDate
{
public:
ThreadDate(sockaddr_in &addr, int sockfd, TcpServer *th)
: _addr(addr),
_th(th),
_sockfd(sockfd)
{
}
sockaddr_in _addr;
TcpServer *_th;
int _sockfd;
};
static void *route(void *args)
{
// 进行线程分离,避免当其他线程之间的互相影响
pthread_detach(pthread_self());
ThreadDate *date = static_cast<ThreadDate *>(args);
date->_th->Service(date->_addr, date->_sockfd);
return nullptr;
}
void Run()
{
while (true)
{
// 获取连接,建立通信(若获取连接失败,则再次进行获取)
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// accept返回一个新的fd,由新的套接字提供服务
_sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len);
if (_sockfd < 0)
{
LOG(LogLevel::ERROR) << "accept error";
continue;
}
// 多线程版本
pthread_t tid;
ThreadDate date(peer, _sockfd, this);
pthread_create(&tid, nullptr, route, &date);
}
}
// 实现翻译功能!
void Service(struct sockaddr_in &peer, int sockfd)
{
while (true)
{
// 接收客户端信息
// a. n>0: 读取成功
// b. n<0: 读取失败
// c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
char buff[1024];
ssize_t n = read(sockfd, buff, sizeof(buff) - 1);
if (n > 0)
{
buff[n] = 0;
cout << "tcpclient say: " << buff << endl;
// 调用方法
string ret = _func(string(buff));
// 发送信息
write(sockfd, ret.c_str(), ret.size());
}
else if (n == 0)
{
LOG(LogLevel::INFO) << "读取到了文件末尾~";
return;
}
else
{
LOG(LogLevel::ERROR) << "读取失败!";
return;
}
}
}
private:
int _listensockfd;
int _sockfd;
uint16_t _prot;
func_t _func;
};其次客户端的读取、发送,作为两个单独的进程(线程)运行,有效避免读取于发送串行阻塞的问题(既没有输入的情况下,就不能接收服务器发送的信息),但这个我们暂时不做处理!
popen()
#include <stdio.h>
FILE *popen(const char *command, const char *type);
作用:将字符串转化为命令,并将命令的执行结果返回
command:要执行的 Shell 命令字符串(如 "ls -l", "grep error")。它支持所有 Shell 语法(通配符、管道符等)
type:打开模式(二选一):
"r" (读模式):命令的标准输出 -> 你的程序(你可以读取命令打印的结果)
"w" (写模式):你的程序 -> 命令的标准输入(你可以把数据喂给命令)
返回值
成功:返回一个标准文件指针 FILE *。你可以直接用 fgets、fscanf 等文件操作函数来读写它
失败:返回 NULL配套关闭:pclose
popen返回的指针,使用完必须调用 pclose(FILE *stream) 来关闭!它不仅关闭文件流,还会等待命令执行完毕,回收子进程资源(防止僵尸进程)
cmd.hpp
可以根据自己的需要执行任意指令,我这里为了安全起见,只开放个别指令用于执行!
#pragma once
#include <iostream>
#include <vector>
#include <stdio.h>
#include <string>
#include "Log.hpp"
using namespace LogModule;
class Cmd
{
public:
// 初始化命令集
Cmd()
{
arr.push_back("ls");
arr.push_back("pwd");
arr.push_back("whoami");
arr.push_back("who");
arr.push_back("tree .");
}
bool Isin(std::string command)
{
for (auto &e : arr)
{
if (e == command)
return true;
}
return false;
}
string Run(std::string command)
{
// 判断是否在规则命令中
if (!Isin(command))
{
return "非法命令!";
}
// popen函数,将字符串解析为命令,并执行后返回结果
FILE *file = popen(command.c_str(), "r");
if (file == nullptr)
{
LOG(LogLevel::ERROR) << "该命令不存在";
}
// 获取执行后的结果
char ret[1024];
string str;
while (fgets(ret, sizeof(ret), file))
{
str += ret;
}
// 不再使用
pclose(file);
return "the result is: \n" + str;
}
private:
std::vector<std::string> arr;
};#pragma once
#include <pthread.h>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <functional>
#include <unistd.h>
#include "Log.hpp"
#include "Comment.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
const static int backlog = 8;
string Func(string str)
{
string s = "hello";
return s + str;
}
class TcpServer : public NoCopy
{
private:
using func_t = std::function<string(string)>;
public:
TcpServer(uint16_t prot, func_t func = Func)
: _func(func),
_prot(prot)
{
}
void Init()
{
// 创建套接字:数据类似为流式
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::ERROR) << "socket error";
exit(SOCK_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3
// 绑定
InetAddr inetaddr(_prot);
int n = bind(_listensockfd, inetaddr.Getaddr(), inetaddr.Len());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // 3
// 将套接字设置为listen状态:TCP服务器处于listen状态下,才可以被连接
int m = listen(_listensockfd, backlog);
if (m < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // 3
}
// 作为参数传入线程执行函数中
class ThreadDate
{
public:
ThreadDate(sockaddr_in &addr, int sockfd, TcpServer *th)
: _addr(addr),
_th(th),
_sockfd(sockfd)
{
}
sockaddr_in _addr;
TcpServer *_th;
int _sockfd;
};
static void *route(void *args)
{
// 进行线程分离,避免当其他线程之间的互相影响
pthread_detach(pthread_self());
ThreadDate *date = static_cast<ThreadDate *>(args);
date->_th->Service(date->_addr, date->_sockfd);
return nullptr;
}
void Run()
{
while (true)
{
// 获取连接,建立通信(若获取连接失败,则再次进行获取)
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// accept返回一个新的fd,由新的套接字提供服务
_sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len);
if (_sockfd < 0)
{
LOG(LogLevel::ERROR) << "accept error";
continue;
}
// 多线程版本
pthread_t tid;
ThreadDate date(peer, _sockfd, this);
pthread_create(&tid, nullptr, route, &date);
}
}
// 实现翻译功能!
void Service(struct sockaddr_in &peer, int sockfd)
{
while (true)
{
// 接收客户端信息
// a. n>0: 读取成功
// b. n<0: 读取失败
// c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
char buff[1024];
ssize_t n = read(sockfd, buff, sizeof(buff) - 1);
if (n > 0)
{
buff[n] = 0;
cout << "tcpclient say: " << buff << endl;
// 调用方法
string ret = _func(string(buff));
// 发送信息
write(sockfd, ret.c_str(), ret.size());
}
else if (n == 0)
{
LOG(LogLevel::INFO) << "读取到了文件末尾~";
return;
}
else
{
LOG(LogLevel::ERROR) << "读取失败!";
return;
}
}
}
private:
int _listensockfd;
int _sockfd;
uint16_t _prot;
func_t _func;
};#include "TcpServer.hpp"
#include "cmd.hpp"
void usage(string str)
{
cerr << "Please use: " << str << " prot" << endl;
}
int main(int agrc, char *argv[])
{
if (agrc != 2)
{
usage(argv[0]);
exit(TCPSERVER_ERR);
}
uint16_t prot = stoi(argv[1]);
Cmd cmd;
TcpServer tcp(prot, [&cmd](string command)
{ return cmd.Run(command); });
tcp.Init();
tcp.Run();
}// 实现日志模块
#pragma once
#include <iostream>
#include <sstream> // 包含stringstream类
#include <filesystem> //C++17文件操作接口库
#include <fstream>
#include <sys/types.h>
#include <unistd.h>
#include "Mutex.hpp"
using namespace std;
using namespace MutexModule;
// 补充:外部类只能通过内部类的实例化对象,来访问内部类中的方法与成员,且受修饰符限制
// 内部类可以直接访问外部类的方法以及成员,没有限制
namespace LogModule
{
const string end = "\r\n";
// 实现刷新策略:a.向显示器刷新 b.向指定文件刷新
// 利用多态机制实现
// 包含至少一个纯虚函数的类称为抽象类,不能实例化,只能被继承
class LogStrategy // 基类
{
public:
//"=0"声明为纯虚函数。纯虚函数强制派生类必须重写该函数
virtual void SyncLog(const string &message) = 0;
};
// 向显示器刷新:子类
class ConsoleLogStrategy : public LogStrategy
{
public:
void SyncLog(const string &message) override
{
// 加锁,访问显示器,显示器也是临界资源
LockGuard lockguard(_mutex);
cout << message << end;
}
private:
Mutex _mutex;
};
// 向指定文件刷新:子类
const string defaultpath = "./log";
const string defaultfile = "my.log";
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const string &path = defaultpath, const string &file = defaultfile)
: _path(path), _file(file)
{
LockGuard lockguard(_mutex);
// 判断路径是否存在,如果不存在就创建对应的路径
if (!(filesystem::exists(_path)))
filesystem::create_directories(_path);
}
void SyncLog(const string &message) override
{
// 合成最后路径
string Path = _path + (_path.back() == '/' ? "" : "/") + _file;
// 打开文件
ofstream out(Path, ios::app);
out << message << end;
}
private:
string _path;
string _file;
Mutex _mutex;
};
//
// 日志等级
// enum class:强类型枚举。1.必须通过域名访问枚举值 2.枚举值不能隐式类型转化为整型
enum class LogLevel
{
DEBUG, // 调试级
INFO, // 信息级
WARNING, // 警告级
ERROR, // 错误级
FATAL // 致命级
};
//
// 将等级转化为字符串
string LevelToStr(LogLevel level)
{
switch (level)
{
case LogLevel::DEBUG:
return "DEBUG";
case LogLevel::INFO:
return "DEBUG";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
default:
return "UNKOWN";
}
}
// 获取时间
string GetTime()
{
// time函数:获取当前系统的时间戳
// localtime_r函数:将时间戳转化为本地时间(可重入函数,localtime则是不可重入函数)
// struct tm结构体,会将转化之后的本地时间存储在结构体中
time_t curr = time(nullptr);
struct tm curr_time;
localtime_r(&curr, &curr_time);
char buffer[128];
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
curr_time.tm_year + 1900, // 年份是从1900开始计算的,需要加上1900才能得到正确的年份
curr_time.tm_mon + 1, // 月份了0~11,需要加上1才能得到正确的月份
curr_time.tm_mday, // 日
curr_time.tm_hour, // 时
curr_time.tm_min, // 分
curr_time.tm_sec); // 秒
return buffer;
}
//
// 实现日志信息,并选择刷新策略
class Logger
{
public:
Logger()
{
// 默认选择显示器刷新
Strategy = make_unique<ConsoleLogStrategy>();
}
void EnableConsoleLogStrategy()
{
Strategy = make_unique<ConsoleLogStrategy>();
}
void EnableFileLogStrategy()
{
Strategy = make_unique<FileLogStrategy>();
}
// 日志信息
class LogMessage
{
public:
LogMessage(const LogLevel &level, const string &name, const int &line, Logger &logger)
: _level(level),
_name(name),
_logger(logger),
_line_member(line)
{
_pid = getpid();
_time = GetTime();
// 合并:日志信息的左半部分
stringstream ss; // 创建输出流对象,stringstream可以将输入的所有数据全部转为为字符串
ss << "[" << _time << "] "
<< "[" << LevelToStr(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _name << "] "
<< "[" << _line_member << "] "
<< " - ";
// 返回ss中的字符串
_loginfo = ss.str();
}
// 日志文件的右半部分:可变参数,重载运算符<<
// e.g. <<"huang"<<123<<"dasd"<<24
template <class T>
LogMessage &operator<<(const T &message) // 引用返回可以让后续内容不断追加
{
stringstream ss;
ss << message;
_loginfo += ss.str();
// 返回对象!
return *this;
}
// 销毁时,将信息刷新
~LogMessage()
{
// 日志文件
_logger.Strategy->SyncLog(_loginfo);
}
private:
string _time;
LogLevel _level;
pid_t _pid;
string _name;
int _line_member;
string _loginfo; // 合并之后的一条完整信息
// 日志对象
Logger &_logger;
};
// 重载运算符(),便于创建LogMessage对象
// 这里返回临时对象:当临时对象销毁时,调用对应的析构函数,自动对象中创建好的日志信息进行刷新!
// 其次局部对象也不能传引用返回!
LogMessage operator()(const LogLevel &level, const string &name, const int &line)
{
return LogMessage(level, name, line, *this);
}
private:
unique_ptr<LogStrategy> Strategy;
};
// 为了用户使用更方便,我们使用宏封装一下
Logger logger;
// 切换刷新策略
#define Enable_Console_LogStrategy() logger.EnableConsoleLogStrategy();
#define Enable_File_LogStrategy() logger.EnableFileLogStrategy();
// 创建日志,并刷新
//__FILE__ 和 __LINE__ 是编译器预定义的宏,作用是获取当前代码所在的文件名、行号
#define LOG(level) logger(level, __FILE__, __LINE__) // 细节:不加;
};#pragma once
#include <iostream>
#include <vector>
#include <stdio.h>
#include <string>
#include "Log.hpp"
using namespace LogModule;
class Cmd
{
public:
// 初始化命令集
Cmd()
{
arr.push_back("ls");
arr.push_back("pwd");
arr.push_back("whoami");
arr.push_back("who");
arr.push_back("tree .");
}
bool Isin(std::string command)
{
for (auto &e : arr)
{
if (e == command)
return true;
}
return false;
}
string Run(std::string command)
{
// 判断是否在规则命令中
if (!Isin(command))
{
return "非法命令!";
}
// popen函数,将字符串解析为命令,并执行后返回结果
FILE *file = popen(command.c_str(), "r");
if (file == nullptr)
{
LOG(LogLevel::ERROR) << "该命令不存在";
}
// 获取执行后的结果
char ret[1024];
string str;
while (fgets(ret, sizeof(ret), file))
{
str += ret;
}
// 不再使用
pclose(file);
return "the result is: \n" + str;
}
private:
std::vector<std::string> arr;
};#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
#include "Comment.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
void Usage(std::string s)
{
std::cerr << "Please use: " << s << " ip prot" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(TCPCLIENT_ERR);
}
// 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
LOG(LogLevel::ERROR) << "socket error";
exit(SOCK_ERR);
}
// 绑定信息,但由OS自动绑定,无需我们自己绑定
// 申请建立连接请求
uint16_t prot = stoi(argv[2]);
InetAddr addr(argv[1], prot);
int n = connect(sockfd, addr.Getaddr(), addr.Len());
if (n < 0)
{
LOG(LogLevel::ERROR) << "connect error";
exit(CONN_ERR);
}
while (true)
{
cout << "Please Enter@ ";
string str;
cin >> str;
// 向服务器发送信息
write(sockfd, str.c_str(), str.size());
// 接收服务器的信息
char buff[1024];
int n = read(sockfd, buff, sizeof(buff));
if (n > 0)
{
buff[n] = 0;
cout << "Eoch: " << buff << endl;
}
}
}#pragma once
// 禁止拷贝类
class NoCopy
{
public:
NoCopy() {};
// 拷贝构造
NoCopy(const NoCopy &nocopy) = delete;
// 赋值重载
const NoCopy &operator=(const NoCopy &nocopy) = delete;
};
enum EXIT
{
OK = 0,
SOCK_ERR,
BIND_ERR,
LISTEN_ERR,
ACCP_ERR,
TCPSERVER_ERR,
TCPCLIENT_ERR,
CONN_ERR
};#pragma once
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <cstring>
// 实现网络地址与主机地址的转换
class InetAddr
{
public:
// 网络转主机
InetAddr(struct sockaddr_in &addr)
: _addr(addr)
{
_prot = ntohs(_addr.sin_port); // 网络地址转主机地址
char buff[1024];
inet_ntop(AF_INET, &addr.sin_addr, buff, sizeof(buff)); // 将4字节网络风格的IP -> 点分十进制的字符串风格的IP
_ip = std::string(buff);
}
// 主机转网络(客户端)
InetAddr(std::string ip, uint16_t prot)
: _ip(ip),
_prot(prot)
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
//&addr.sin_addr 是一个指向 struct in_addr 的指针,其内存地址等价于 &(addr.sin_addr.s_addr)(因为结构体的起始地址就是第一个成员的起始地址)
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
_addr.sin_port = htons(_prot);
}
// 主机转网络(服务端)
InetAddr(uint16_t prot)
: _prot(prot)
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = htons(prot);
_addr.sin_addr.s_addr = INADDR_ANY; // 任意ip地址
}
// 直接获取sockaddr_in
struct sockaddr *Getaddr()
{
return (struct sockaddr *)&_addr;
}
int Len()
{
return sizeof(_addr);
}
uint16_t prot()
{
return _prot;
}
std::string ip()
{
return _ip;
}
// 运算符重载
bool operator==(InetAddr &addr)
{
return _prot == addr._prot && _ip == addr._ip;
}
std::string Getname()
{
return _ip + ':' + std::to_string(_prot);
}
private:
struct sockaddr_in _addr;
uint16_t _prot;
std::string _ip;
};// 封装锁接口
#pragma once
#include <pthread.h>
namespace MutexModule
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&mutex, nullptr);
}
~Mutex()
{
pthread_mutex_destroy(&mutex);
}
void Lock()
{
pthread_mutex_lock(&mutex);
}
void Unlock()
{
pthread_mutex_unlock(&mutex);
}
pthread_mutex_t *Get()
{
return &mutex;
}
private:
pthread_mutex_t mutex;
};
class LockGuard
{
public:
LockGuard(Mutex &mutex)
: _Mutex(mutex)
{
_Mutex.Lock();
}
~LockGuard()
{
_Mutex.Unlock();
}
private:
Mutex &_Mutex;
};
}