目录
[二、Echo Server](#二、Echo Server)
一、TCP相关的接口
- listen函数
功能:将套接字设置为"监听模式",使其能够接受客户端的连接请求。
原型:int listen(int sockfd, int backlog);
参数说明:
- sockfd:套接字的文件描述符。
- backlog:指定在拒绝新连接之前,系统允许的最大未接受连接数量(即连接队列的长度)。例如:backlog = 6,服务器最多可排队6个未处理的连接请求。
返回值:成功返回0,失败返回-1 并设置errno。
注意:
- 在TCP协议中,服务器需要先调用bind绑定地址和端口,然后调用listen开始监听。
- accept函数
功能:服务器端接受客户端的连接请求。
原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:
- sockfd:监听套接字的文件描述符(通过socket()创建且以调用listen()进入监听状态)
- addr:指向sockaddr结构体的指针,用于存储客户端地址信息(IP和端口)。设为NULL表示不获取地址。
- addrlen:是一个传入参数,传入的是调用者提供的,缓冲区addr的长度以及缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满缓冲区)。
返回值:成功返回新创建的通信套接字描述符(非负整数),用于后续数据传输;失败返回-1并设置errno。
注意:
- 必须在listen之后再调用accept函数。
- 当服务器调用accept函数时没有客户端的连接请求,就阻塞等待直到有客户端连接请求。
- connect函数
功能:用于客户端套接字发起连接请求,目标是与指定服务器建立可靠的TCP连接。
原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
- sockfd:套接字描述符
- addr:用于指定服务器的IP地址和端口号
- addrlen:表示addr结构体的长度
返回值:成功返回0,表示连接成功建立;失败返回-1并设置errno。
- recv函数
功能:用于从已连接的套接字接收数据,常用语TCP协议通信。
原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数说明:
- sockfd:套接字描述符
- buf:指向缓冲区的指针,用于存储接收到的数据。
- len:缓冲区大小,表示可以接收的最大字节数。
- flags:标志位,控制接收行为,常见值如0(默认行为)或MSG_WAITALL(等待所有数据到达)
返回值:成功返回接收到的字节数,失败返回-1并设置errno。
- send函数
功能:用于通过已连接的套接字发送数据(TCP协议)。
原型:ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数说明:
- sockfd:已建立连接的套接字描述符(由
socket()
创建且connect()
成功的套接字)- buf:指向待发送数据缓冲区的指针
- len:待发送数据的字节长度
- flags:控制发送行为的标志位(常用值):0:默认阻塞模式;MSG_DONTWAIT:非阻塞发送;MSG_OOB:发送带外数据(紧急数据);MSG_NOSIGNAL:禁止生成SIGPIPE信号
返回值:成功返回实际发送的字节数(可能小于len),失败返回-1并设置errno。
二、Echo Server
cpp
// Comm.hpp
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
enum{
Usage_Err = 1,
Socket_Err,
Bind_Err,
Listen_Err
};
#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)
cpp
// nocopy.hpp
#pragma once
class nocopy
{
public:
nocopy(){}
nocopy(const nocopy&) = delete;
nocopy& operator=(const nocopy&) = delete;
~nocopy(){}
};
cpp
// TcpServer.hpp
#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <cstdlib>
#include "Log.hpp"
#include "nocopy.hpp"
#include "Comm.hpp"
using namespace LogModule;
const static int default_backlog = 6;
const static int default_listenfd = -1;
class TcpServer : public nocopy
{
public:
TcpServer(uint16_t port, int backlog = default_backlog)
:_port(port), _listenfd(default_listenfd), _backlog(default_backlog), _running(false){}
void Init() {
// 创建socket
_listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(_listenfd < 0) {
LOG(LogLevel::ERROR) << "create socket error, errno: " << errno << ", error string: " << strerror(errno);
exit(Socket_Err);
}
LOG(LogLevel::INFO) << "create socket success, sockfd: " << _listenfd;
// bind
struct sockaddr_in local;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
ssize_t n = bind(_listenfd, CONV(&local), sizeof(local));
if(n < 0) {
LOG(LogLevel::ERROR) << "socket bind error, errno: " << errno << ", error string: " << strerror(errno);
exit(Bind_Err);
}
LOG(LogLevel::INFO) << "bind socket success!!!";
// 设置 socket 为监听状态
n = listen(_listenfd, _backlog);
if(n < 0) {
LOG(LogLevel::ERROR) << "listen socket error, errno: " << errno << ", error string: " << strerror(errno);
exit(Listen_Err);
}
LOG(LogLevel::INFO) << "listen socket success!!!";
}
void Start() {
_running = true;
while(_running) {
// 获取连接
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listenfd, CONV(&peer), &len);
if(sockfd < 0) {
LOG(LogLevel::WARNING) << "accept socket error, errno: " << errno << ", error string: " << strerror(errno);
continue;
}
LOG(LogLevel::INFO) << "accept socket success, sockfd: " << sockfd;
// 提供服务
Server(sockfd);
close(sockfd);
}
}
private:
void Server(int sockfd) {
char buffer[4096];
// 一直进行IO操作
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 = "server echo# ";
echo += buffer;
write(sockfd, echo.c_str(), echo.size());
}
else if(n == 0) { // 说明读到结尾,关闭连接
LOG(LogLevel::INFO) << "client quit...";
break;
}
else { // 文件读取出现错误
LOG(LogLevel::ERROR) << "read sockfd error, errno: " << errno << ", error string: " << strerror(errno);
break;
}
}
}
private:
uint16_t _port;
int _listenfd;
int _backlog;
bool _running;
};
cpp
// TcpServer.cc
#include "TcpServer.hpp"
int main(int argc, char* argv[])
{
if(argc != 2) {
std::cerr << "Usage: " << argv[0] << " server_port" << std::endl;
return 1;
}
uint16_t port = atoi(argv[1]);
TcpServer ts(port);
ts.Init();
ts.Start();
return 0;
}
cpp
// TcpClient.cc
#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include "Comm.hpp"
int main(int argc, char* argv[])
{
if(argc != 3) {
std::cout << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
uint16_t port = std::atoi(argv[2]);
std::string ip = argv[1];
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) {
std::cerr << "create socket error" << std::endl;
return 2;
}
// 跟UDP一样,需要bind,但不需要用户显示bind,client系统随机端口
// 发起连接时,client会被系统自动进行本地bind
// connect
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &server.sin_addr);
int n = connect(sockfd, CONV(&server), sizeof(server)); // 自动bind
if(n < 0) {
std::cerr << "connect error" << std::endl;
return 3;
}
while(true) {
std::string inbuffer;
std::cout << "Please Enter: ";
std::getline(std::cin, inbuffer);
ssize_t n = write(sockfd, inbuffer.c_str(), inbuffer.size());
if(n > 0) {
char buffer[4096];
ssize_t m = read(sockfd, buffer, sizeof(buffer) - 1);
if(m > 0) {
buffer[m] = 0;
std::cout << "get a echo message: " << buffer << std::endl;
}
else break;
}
else break;
}
return 0;
}


由于客户端不需要固定的端口号,因此不用调用 bind 函数,客户端端口由内核自动分配。
注意:
- 客户端不是不允许调用bind,只是没有必要显示调用bind固定一个端口号。否则在同一台机器上启动多个客户端,机会出现端口号被占用导致无法正确建立连接。
- 服务器也不是必须调用bind,但如果服务器不调用bind,内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。
测试多个连接的情况

我们在启动一个客户端,尝试连接服务器,发现第二个客户端不能正确的和服务器进行通信。
原因:是因为我们 accept 一个请求后,就一直在 while 中循环尝试 read,没有继续调用 accept,导致无法接受新的请求。
我们当前的TCP服务器只能与一个客户端通信,这是不科学的。
多进程版
对于每个请求,创建子进程的方式来支持多连接。
cpp
// TcpServer.hpp
#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/wait.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "Comm.hpp"
using namespace LogModule;
const static int default_backlog = 6;
const static int default_listenfd = -1;
class TcpServer : public nocopy
{
public:
TcpServer(uint16_t port, int backlog = default_backlog)
:_port(port), _listenfd(default_listenfd), _backlog(default_backlog), _running(false){}
void Init() {
// 创建socket
_listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(_listenfd < 0) {
LOG(LogLevel::ERROR) << "create socket error, errno: " << errno << ", error string: " << strerror(errno);
exit(Socket_Err);
}
LOG(LogLevel::INFO) << "create socket success, sockfd: " << _listenfd;
// bind
struct sockaddr_in local;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
ssize_t n = bind(_listenfd, CONV(&local), sizeof(local));
if(n < 0) {
LOG(LogLevel::ERROR) << "socket bind error, errno: " << errno << ", error string: " << strerror(errno);
exit(Bind_Err);
}
LOG(LogLevel::INFO) << "bind socket success!!!";
// 设置 socket 为监听状态
n = listen(_listenfd, _backlog);
if(n < 0) {
LOG(LogLevel::ERROR) << "listen socket error, errno: " << errno << ", error string: " << strerror(errno);
exit(Listen_Err);
}
LOG(LogLevel::INFO) << "listen socket success!!!";
}
void Start() {
_running = true;
while(_running) {
// 获取连接
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listenfd, CONV(&peer), &len);
if(sockfd < 0) {
LOG(LogLevel::WARNING) << "accept socket error, errno: " << errno << ", error string: " << strerror(errno);
continue;
}
LOG(LogLevel::INFO) << "accept socket success, sockfd: " << sockfd;
ProcessConnect(sockfd);
}
}
private:
void ProcessConnect(int sockfd) {
int id = fork();
if(id < 0) {
LOG(LogLevel::ERROR) << "fork error";
close(sockfd);
return;
}
else if(id == 0) {
// 子进程
close(_listenfd);
if(fork() > 0) exit(0); // 子进程创建一个子进程,并退出,让创建出的子进程变为孤儿进程,由系统管理
// 孤儿进程
// 提供服务
Server(sockfd);
close(sockfd);
exit(0);
}
else {
close(sockfd);
waitpid(id, nullptr, 0);
}
}
void Server(int sockfd) {
char buffer[4096];
// 一直进行IO操作
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 = "server echo# ";
echo += buffer;
write(sockfd, echo.c_str(), echo.size());
}
else if(n == 0) { // 说明读到结尾,关闭连接
LOG(LogLevel::INFO) << "client quit...";
break;
}
else { // 文件读取出现错误
LOG(LogLevel::ERROR) << "read sockfd error, errno: " << errno << ", error string: " << strerror(errno);
break;
}
}
}
private:
uint16_t _port;
int _listenfd;
int _backlog;
bool _running;
};



多线程版
cpp
// TcpServer.hpp
#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/wait.h>
#include <pthread.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "Comm.hpp"
using namespace LogModule;
const static int default_backlog = 6;
const static int default_listenfd = -1;
class TcpServer : public nocopy
{
public:
TcpServer(uint16_t port, int backlog = default_backlog)
:_port(port), _listenfd(default_listenfd), _backlog(default_backlog), _running(false){}
void Init() {
// 创建socket
_listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(_listenfd < 0) {
LOG(LogLevel::ERROR) << "create socket error, errno: " << errno << ", error string: " << strerror(errno);
exit(Socket_Err);
}
LOG(LogLevel::INFO) << "create socket success, sockfd: " << _listenfd;
// bind
struct sockaddr_in local;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
ssize_t n = bind(_listenfd, CONV(&local), sizeof(local));
if(n < 0) {
LOG(LogLevel::ERROR) << "socket bind error, errno: " << errno << ", error string: " << strerror(errno);
exit(Bind_Err);
}
LOG(LogLevel::INFO) << "bind socket success!!!";
// 设置 socket 为监听状态
n = listen(_listenfd, _backlog);
if(n < 0) {
LOG(LogLevel::ERROR) << "listen socket error, errno: " << errno << ", error string: " << strerror(errno);
exit(Listen_Err);
}
LOG(LogLevel::INFO) << "listen socket success!!!";
}
void Start() {
_running = true;
while(_running) {
// 获取连接
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listenfd, CONV(&peer), &len);
if(sockfd < 0) {
LOG(LogLevel::WARNING) << "accept socket error, errno: " << errno << ", error string: " << strerror(errno);
continue;
}
LOG(LogLevel::INFO) << "accept socket success, sockfd: " << sockfd;
pthread_t tid;
pthread_create(&tid, nullptr, ProcessConnect, (void*)&sockfd);
}
}
static void Server(int sockfd) {
char buffer[4096];
// 一直进行IO操作
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 = "server echo# ";
echo += buffer;
write(sockfd, echo.c_str(), echo.size());
}
else if(n == 0) { // 说明读到结尾,关闭连接
LOG(LogLevel::INFO) << "client quit...";
break;
}
else { // 文件读取出现错误
LOG(LogLevel::ERROR) << "read sockfd error, errno: " << errno << ", error string: " << strerror(errno);
break;
}
}
}
private:
static void* ProcessConnect(void* arg) {
pthread_detach(pthread_self());
int* sockptr = static_cast<int*>(arg);
TcpServer::Server(*sockptr);
close(*sockptr);
delete sockptr;
return nullptr;
}
private:
uint16_t _port;
int _listenfd;
int _backlog;
bool _running;
};



线程池版
cpp
#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/wait.h>
#include <pthread.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "Comm.hpp"
#include "ThreadPool.hpp"
using namespace LogModule;
const static int default_backlog = 6;
const static int default_listenfd = -1;
class TcpServer : public nocopy
{
public:
TcpServer(uint16_t port, int backlog = default_backlog)
:_port(port), _listenfd(default_listenfd), _backlog(default_backlog), _running(false){}
void Init() {
// 创建socket
_listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(_listenfd < 0) {
LOG(LogLevel::ERROR) << "create socket error, errno: " << errno << ", error string: " << strerror(errno);
exit(Socket_Err);
}
LOG(LogLevel::INFO) << "create socket success, sockfd: " << _listenfd;
// bind
struct sockaddr_in local;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
ssize_t n = bind(_listenfd, CONV(&local), sizeof(local));
if(n < 0) {
LOG(LogLevel::ERROR) << "socket bind error, errno: " << errno << ", error string: " << strerror(errno);
exit(Bind_Err);
}
LOG(LogLevel::INFO) << "bind socket success!!!";
// 设置 socket 为监听状态
n = listen(_listenfd, _backlog);
if(n < 0) {
LOG(LogLevel::ERROR) << "listen socket error, errno: " << errno << ", error string: " << strerror(errno);
exit(Listen_Err);
}
LOG(LogLevel::INFO) << "listen socket success!!!";
}
void Start() {
using task_t = std::function<void()>;
_running = true;
while(_running) {
// 获取连接
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listenfd, CONV(&peer), &len);
if(sockfd < 0) {
LOG(LogLevel::WARNING) << "accept socket error, errno: " << errno << ", error string: " << strerror(errno);
continue;
}
LOG(LogLevel::INFO) << "accept socket success, sockfd: " << sockfd;
task_t t = std::bind(&TcpServer::Server, sockfd);
ThreadPool<task_t>::GetInstance()->Push(t);
}
}
static void Server(int sockfd) {
char buffer[4096];
// 一直进行IO操作
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 = "server echo# ";
echo += buffer;
write(sockfd, echo.c_str(), echo.size());
}
else if(n == 0) { // 说明读到结尾,关闭连接
LOG(LogLevel::INFO) << "client quit...";
break;
}
else { // 文件读取出现错误
LOG(LogLevel::ERROR) << "read sockfd error, errno: " << errno << ", error string: " << strerror(errno);
break;
}
}
}
private:
uint16_t _port;
int _listenfd;
int _backlog;
bool _running;
};



三、多线程远程命令执行
cpp
// Command.hpp
#pragma once
#include <iostream>
#include <string>
#include <unordered_set>
#include <unistd.h>
#include <sys/socket.h>
class Command
{
public:
Command(int sockfd):_sockfd(sockfd){
// 只允许少量命令执行,只是做一个简单的示范,有兴趣可以和之前实现的简单的shell程序结合
_hash_com.insert("ls");
_hash_com.insert("ls -l");
_hash_com.insert("pwd");
_hash_com.insert("whoami");
}
bool IsExist(std::string command) {
return _hash_com.find(command) != _hash_com.end();
}
std::string Excute(std::string &command) {
if(!IsExist(command)) return std::string();
FILE* fp = popen(command.c_str(), "r");
if(fp == nullptr) return std::string();
char buffer[1024];
std::string res;
while(fgets(buffer, sizeof(buffer), fp))
res += buffer;
pclose(fp);
return res;
}
std::string RecvCommand() {
char command[1024];
ssize_t n = recv(_sockfd, command, sizeof(command) - 1, 0);
if(n > 0) {
command[n] = 0;
return command;
}
else return std::string();
}
void SendCommand(std::string res) {
if(res.empty()) res = "Empty";
send(_sockfd, res.c_str(), res.size(), 0);
}
private:
int _sockfd;
std::string _command;
std::unordered_set<std::string> _hash_com;
};
cpp
// TcpServer.hpp
#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/wait.h>
#include <pthread.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "Comm.hpp"
#include "ThreadPool.hpp"
#include "Command.hpp"
using namespace LogModule;
const static int default_backlog = 6;
const static int default_listenfd = -1;
class TcpServer : public nocopy
{
public:
TcpServer(uint16_t port, int backlog = default_backlog)
:_port(port), _listenfd(default_listenfd), _backlog(default_backlog), _running(false){}
void Init() {
// 创建socket
_listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(_listenfd < 0) {
LOG(LogLevel::ERROR) << "create socket error, errno: " << errno << ", error string: " << strerror(errno);
exit(Socket_Err);
}
LOG(LogLevel::INFO) << "create socket success, sockfd: " << _listenfd;
// bind
struct sockaddr_in local;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
ssize_t n = bind(_listenfd, CONV(&local), sizeof(local));
if(n < 0) {
LOG(LogLevel::ERROR) << "socket bind error, errno: " << errno << ", error string: " << strerror(errno);
exit(Bind_Err);
}
LOG(LogLevel::INFO) << "bind socket success!!!";
// 设置 socket 为监听状态
n = listen(_listenfd, _backlog);
if(n < 0) {
LOG(LogLevel::ERROR) << "listen socket error, errno: " << errno << ", error string: " << strerror(errno);
exit(Listen_Err);
}
LOG(LogLevel::INFO) << "listen socket success!!!";
}
void Start() {
using task_t = std::function<void()>;
_running = true;
while(_running) {
// 获取连接
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listenfd, CONV(&peer), &len);
if(sockfd < 0) {
LOG(LogLevel::WARNING) << "accept socket error, errno: " << errno << ", error string: " << strerror(errno);
continue;
}
LOG(LogLevel::INFO) << "accept socket success, sockfd: " << sockfd;
task_t t = std::bind(&TcpServer::Server, sockfd);
ThreadPool<task_t>::GetInstance()->Push(t);
}
}
static void Server(int sockfd) {
while(true) {
Command com(sockfd);
std::string req = com.RecvCommand();
if(req.empty()) break;
std::string resp = com.Excute(req);
com.SendCommand(resp);
}
// char buffer[4096];
// // 一直进行IO操作
// 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 = "server echo# ";
// echo += buffer;
// write(sockfd, echo.c_str(), echo.size());
// }
// else if(n == 0) { // 说明读到结尾,关闭连接
// LOG(LogLevel::INFO) << "client quit...";
// break;
// }
// else { // 文件读取出现错误
// LOG(LogLevel::ERROR) << "read sockfd error, errno: " << errno << ", error string: " << strerror(errno);
// break;
// }
// }
}
private:
uint16_t _port;
int _listenfd;
int _backlog;
bool _running;
};


