TCP特有的相关接口
listen接口
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd,int backlog);
返回值
成功 0被返回
失败 -1被返回
accept接口
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
返回值
返回文件描述符
connect接口
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
返回值
成功 0被返回。
失败 -1被返回。
并且在connect的时候返回成功自定bind。
普通版本代码
服务端
套接字的创建
// 1. 创建socket文件套接字对象
_listensock = socket(AF_INET, SOCK_STREAM, 0);
if (_listensock < 0)
{
logMessage(FATAL, "create socket error");
exit(SOCKET_ERR);
}
logMessage(NORMAL, "create socket success");
bind连接
// 2.bind绑定自己的网络信息
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port); // 主机转网络
local.sin_addr.s_addr = INADDR_ANY;
if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL, "bind socket success");
listen监听接口
TCP是面向连接的所以需要将套接字设置为监听状态
// 3. 设置socket 为监听状态
if (listen(_listensock, gbacklog) < 0) // TODO
{
logMessage(FATAL, "listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL, "listen socket success");
客户端在何时连接?
客户端连接服务端的时候,服务端需要就可以accept到客户端发来的信息。
accept接口
// 4. server 获取新链接
// sock 和客户端进行通信的fd
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
if (sock < 0)
{
logMessage(ERROR, "accept error, next");
cout << "sock: " << sock << endl;
continue;
}
logMessage(NORMAL, "accept a new link success,get new sock: %d", sock);
serviceIO 服务端的服务
先读后写
void serviceIO(int sock)
{
char buffer[1024];
while (true)
{
size_t n = read(sock, buffer, sizeof(buffer) - 1);
if (n > 0)
{
// 目前我们把读到的数据当成字符串,截至目前
buffer[n] = 0;
std::cout << "recv message: " << buffer << std::endl;
std::string outbuffer = buffer;
outbuffer += " server[echo]";
write(sock, outbuffer.c_str(), outbuffer.size()); // 多路转接
}
else if (n == 0)
{
// 代表client退出
logMessage(NORMAL, "client quit , me too!");
break;
}
}
close(sock);
}
客户端代码
创建套接字
// 1. 创建socket
_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_sock < 0)
{
cerr << "socket create error" << endl;
}
// 2. tcp的客户端要不要bind? 要的 要不要显示的bind?
// 不要! 这里尤其是client port要让OS自定随机自定!
// 3. 要不要listen?不要!只有服务端需要!
// 4. 要不要accept?不要!只有服务端需要!
// 5. 要什么呢??要发起链接!
链接 发送数据
void start()
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(_serverport);
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
int con = connect(_sock, (struct sockaddr *)&server, sizeof(server));
if (con != 0)
{
cerr << "socket connect error" << endl;
}
else
{
string msg;
while (true)
{
cout << "Enter# ";
getline(cin, msg);
write(_sock, msg.c_str(), msg.size());
char buffer[NUM];
int n = read(_sock, buffer, sizeof(buffer) - 1);
if (n > 0)
{
// 目前我们把读到的数据当字符串,截至目前
buffer[n] = 0;
cout << "Server回显# " << buffer << endl;
buffer[0] = 0;
}
else
{
break;
}
}
}
}
普通版本完整代码
makefile
cc=g++
.PHONY:all
all:tcpclient tcpserver
tcpclient:tcpClient.cc
$(cc) -o $@ $^ -std=c++11
tcpserver:tcpServer.cc
$(cc) -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:
rm -f tcpclient tcpserver
tcpserver.hpp
#pragma once
#include <iostream>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "log.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
using namespace std;
namespace server
{
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR
};
static const uint16_t gport = 8080;
static const int gbacklog = 5;
class TcpServer;
class ThreadData
{
public:
ThreadData(TcpServer *self, int sock)
: _self(self), _sock(sock)
{
}
~ThreadData()
{
}
TcpServer *_self;
int _sock;
};
class TcpServer
{
public:
TcpServer(const uint16_t &port = gport) : _listensock(-1), _port(port)
{
}
void initServer()
{
// 1. 创建socket文件套接字对象
_listensock = socket(AF_INET, SOCK_STREAM, 0);
if (_listensock < 0)
{
logMessage(FATAL, "create socket error");
exit(SOCKET_ERR);
}
logMessage(NORMAL, "create socket success");
// 2.bind绑定自己的网络信息
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port); // 主机转网络
local.sin_addr.s_addr = INADDR_ANY;
if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL, "bind socket success");
// 3. 设置socket 为监听状态
if (listen(_listensock, gbacklog) < 0) // TODO
{
logMessage(FATAL, "listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL, "listen socket success");
}
void start()
{
for (;;)
{
// 4. server 获取新链接
// sock 和客户端进行通信的fd
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
if (sock < 0)
{
logMessage(ERROR, "accept error, next");
cout << "sock: " << sock << endl;
continue;
}
// logMessage(NORMAL, "accept a new link success,get new sock: %d", sock);
logMessage(NORMAL, "accept a new link success");
cout << "sock: " << sock << endl;
// 5. 这里就是一个sock,未来通信我们就用这个sock,面向字节流的,后续全部都是文件操作!
// version 1
serviceIO(sock);
close(sock); // 对一个就已经使用完毕的sock,我们要关闭这个sock
// 要不然会导致。
// version 多进程版 多线程版 线程池版
}
}
void serviceIO(int sock)
{
char buffer[1024];
while (true)
{
ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
if (n > 0)
{
// 目前我们把读的数据当成字符串,截止目前
buffer[n] = 0;
cout << "recv message: " << buffer << endl;
string outbuffer = buffer;
outbuffer += "server[echo]";
write(sock, outbuffer.c_str(), outbuffer.size());
}
else if (n == 0)
{
// 代表客户端退出
logMessage(NORMAL, "client quit,me too!");
break;
}
}
}
~TcpServer()
{
}
private:
int _listensock; // 不是用来进行数据通信的,它是用来监听链接到来,获取新链接的!
uint16_t _port;
};
}
tcpserver.cc
#include "tcpServer.hpp"
#include "daemon.hpp"
#include <memory>
using namespace server;
using namespace std;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}
// tcp服务器,启动上和udp server一模一样
// ./tcpServer local_port
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port=atoi(argv[1]); // 字符串转整型
unique_ptr<TcpServer> tsvr(new TcpServer(port));
tsvr->initServer();
// daemonSelf();
tsvr->start();
return 0;
}
tcpclient.hpp
#pragma once
#include <iostream>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
using namespace std;
#define NUM 1024
class TcpClient
{
public:
TcpClient(const string &serverip, const uint16_t &serverport)
: _sock(-1), _serverip(serverip), _serverport(serverport)
{
}
void initClient()
{
// 1. 创建socket
_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_sock < 0)
{
cerr << "socket create error" << endl;
}
// 2. tcp的客户端要不要bind? 要的 要不要显示的bind?
// 不要! 这里尤其是client port要让OS自定随机自定!
// 3. 要不要listen?不要!只有服务端需要!
// 4. 要不要accept?不要!只有服务端需要!
// 5. 要什么呢??要发起链接!
}
void start()
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(_serverport);
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
int con = connect(_sock, (struct sockaddr *)&server, sizeof(server));
if (con != 0)
{
cerr << "socket connect error" << endl;
}
else
{
string msg;
while (true)
{
cout << "Enter# ";
getline(cin, msg);
write(_sock, msg.c_str(), msg.size());
char buffer[NUM];
int n = read(_sock, buffer, sizeof(buffer) - 1);
if (n > 0)
{
// 目前我们把读到的数据当字符串,截至目前
buffer[n] = 0;
cout << "Server回显# " << buffer << endl;
buffer[0] = 0;
}
else
{
break;
}
}
}
}
~TcpClient()
{
if (_sock >= 0)
{
close(_sock);
}
}
private:
int _sock;
string _serverip;
uint16_t _serverport;
};
tcpclient.cc
#include "tcpClient.hpp"
#include <memory>
using namespace std;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " serverip serverport\n\n";
}
// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
unique_ptr<TcpClient> tcli(new TcpClient(serverip, serverport));
tcli->initClient();
tcli->start();
return 0;
}
// log.hpp
#pragma once
#include <iostream>
#include <string>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
using namespace std;
void logMessage(int level, const char *message)
{
cout << message << endl;
}
多进程版本
版本一
孙子进程进行提供服务
// 多进程版本1
// version 2
pid_t id = fork();
if (id == 0) // child
{
close(_listensock); // 子进程中不需要监听因此关闭监听的文件描述符
if (fork() > 0)
exit(0);
serviceIO(sock);
close(sock);
exit(0);
}
// father
pid_t ret = waitpid(id, nullptr, 0);
if (ret > 0)
{
cout << "waitsuccess: " << ret << endl;
}
版本二
通过屏蔽信号SIGCHLD来防止僵尸进程
// 多进程版本2
signal(SIGCHLD, SIG_IGN);
pid_t id = fork();
if (id == 0) // child
{
close(_listensock);
// if(fork()>0) exit(0);
serviceIO(sock);
close(sock);
exit(0);
}
close(sock);
多线程版本
static void *threadRoutine(void *args)
{
pthread_detach(pthread_self()); // 线程分离 互不干扰
ThreadData *td = static_cast<ThreadData *>(args);
td->_self->serviceIO(td->_sock);
delete td;
close(td->_sock);
return nullptr;
}
pthread_t tid;
ThreadData *td = new ThreadData(this, sock);
pthread_create(&tid, nullptr, threadRoutine, td);
线程池版本
线程池初始化
ThreadPool<Task>::getInstance()->run();
线程池
ThreadPool<Task>::getInstance()->push(Task(sock, serviceIO));
####################################
Task.hpp
####################################
#pragma once
#include <iostream>
#include <functional>
#include <string>
#include <cstdio>
void serviceIO(int sock)
{
char buffer[1024];
while (true)
{
size_t n = read(sock, buffer, sizeof(buffer) - 1);
if (n > 0)
{
// 目前我们把读到的数据当成字符串,截至目前
buffer[n] = 0;
std::cout << "recv message: " << buffer << std::endl;
std::string outbuffer = buffer;
outbuffer += " server[echo]";
write(sock, outbuffer.c_str(), outbuffer.size()); // 多路转接
}
else if (n == 0)
{
// 代表client退出
logMessage(NORMAL, "client quit , me too!");
break;
}
}
close(sock);
}
// 计算数据的类
class Task
{
using func_t = std::function<void(int)>;
public:
Task()
{
}
Task(int sock, func_t func)
: _sock(sock), _callback(func)
{
}
// 该函数是消费任务的打印信息
void operator()()
{
_callback(_sock);
}
private:
int _sock;
func_t _callback;
};
####################################
ThreadPool.hpp
####################################
#pragma once
#include "Thread.hpp"
#include "LockGuard.hpp"
#include "log.hpp"
#include <vector>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <mutex>
using namespace ThreadNs;
const int gnum = 10;
template <class T>
class ThreadPool;
template <class T>
class ThreadData
{
public:
ThreadPool<T> *threadpool;
std::string name;
public:
ThreadData(ThreadPool<T> *tp, const std::string &n)
: threadpool(tp), name(n)
{
}
};
template <class T>
class ThreadPool
{
private:
static void *handlerTask(void *args)
{
ThreadData<T> *td = (ThreadData<T> *)args;
while (true)
{
T t;
{
LockGuard lockguard(td->threadpool->mutex());
while (td->threadpool->isQueueEmpty())
{
td->threadpool->threadWait();
}
// pop的本质,是将任务从公共的队列当中,拿到当前线程自己独立的栈中
t = td->threadpool->pop();
}
t();
}
delete td;
return nullptr;
}
// 单例模式:构造函数设置为私有
ThreadPool(const int &num = gnum) : _num(num)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
for (int i = 0; i < _num; i++)
{
_threads.push_back(new Thread());
}
}
void operator=(const ThreadPool &) = delete;
ThreadPool(const ThreadPool &) = delete;
// 为了解决static handlerTask
public:
void lockQueue()
{
pthread_mutex_lock(&_mutex);
}
void unlockQueue()
{
pthread_mutex_unlock(&_mutex);
}
bool isQueueEmpty()
{
return _task_queue.empty();
}
void threadWait()
{
pthread_cond_wait(&_cond, &_mutex);
}
T pop()
{
T t = _task_queue.front();
_task_queue.pop();
return t;
}
pthread_mutex_t *mutex()
{
return &_mutex;
}
public:
void run()
{
for (const auto &t : _threads)
{
ThreadData<T> *td = new ThreadData<T>(this, t->threadname());
t->start(handlerTask, td);
std::cout << t->threadname() << " start ..." << std::endl;
// logMessage(DEBUG, "%s start ...", t->threadname().c_str());
}
}
void push(const T &in)
{
LockGuard lockguard(&_mutex);
_task_queue.push(in);
pthread_cond_signal(&_cond);
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
for (const auto &t : _threads)
delete t;
}
static ThreadPool<T> *getInstance()
{
// 最外边这个if的作用是:
// 如果tp已经被创建,那么就不用创建了,因为是单例模型
if (nullptr == tp)
{
_singlock.lock();
if (nullptr == tp)
{
tp = new ThreadPool<T>();
}
_singlock.unlock();
}
return tp;
}
private:
int _num;
std::vector<Thread *> _threads; // 线程集合
std::queue<T> _task_queue; // 任务队列
pthread_mutex_t _mutex;
pthread_cond_t _cond;
static ThreadPool<T> *tp;
static std::mutex _singlock;
};
template <class T>
ThreadPool<T> *ThreadPool<T>::tp = nullptr;
template <class T>
std::mutex ThreadPool<T>::_singlock;
日志
#pragma once
#include <iostream>
#include <string>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
using namespace std;
#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"
#define DEBUG 0
#define NORMAL 1
#define WRNING 2
#define ERROR 3
#define FATAL 4
// 根据不同的日志等级来返回不同的日志信息
const char *to_levelstr(int level)
{
switch (level)
{
case DEBUG:
return "DEBUG";
case NORMAL:
return "NORMAL";
case WRNING:
return "WRNING";
case ERROR:
return "ERROR";
case FATAL:
return "FATAL";
default:
return nullptr;
}
}
// [日志等级] [时间戳/时间] [pid] [message]
// [WARNING] [2023-6-29 8:25:08] [123] [创建socket失败]
// va_list start;
// start 指针
// va_start(start); -- 把start指向第一个
// va_arg(start,float); 让这个指针向后移动指定类型的大小
// va_end(start); 让start这个指针设为NULL
// while (*p)
// {
// switch (*p)
// {
// case '%':
// p++;
// if (*p == 'f')
// arg = va_arg(start, float);
// // ...
// }
// }
// va_end(start);
// void logMessage(DEBUG, "hello %f, %d, %c",3.14,10,'C');
void logMessage(int level, const char *format, ...)
{
#define NUM 1024
char logprefix[NUM];
// logprefix
// [日志等级] [时间戳/时间] [pid]
snprintf(logprefix, sizeof(logprefix), "[%s][%ld][pid: %d]",
to_levelstr(level), (long int)time(nullptr), getpid());
char logcontent[NUM];
va_list arg;
va_start(arg, format);
// [message]
vsnprintf(logcontent, sizeof(logcontent), format, arg);
cout << logprefix << logcontent << endl;
FILE *log = fopen(LOG_NORMAL, "a");
FILE *err = fopen(LOG_ERR, "a");
if (log != nullptr && err != nullptr)
{
FILE *curr = nullptr;
if (level == DEBUG || level == NORMAL || level == WRNING)
curr = log;
if (level == ERROR || level == FATAL)
curr = err;
if (curr)
fprintf(curr, "%s%s\n", logprefix, logcontent);
fclose(log);
fclose(err);
}
}
守护进程
解析
不受用户登录注销的进程。
setsid 接口
#include <unistd.h>
pid_t setsid(void);