目录
1、TCP socket API详解
socket():
bind():
listen():
accept():
connect():
2、简易的TCP网络程序
TcpServer.hpp
TcpClient.cc
Main.cc
Log.hpp
ThreadPool.hpp
Task.hpp
Init.hpp
Daemon.hpp
dict.txt
Makefile
1、TCP socket API详解
下面介绍程序中用到的socket API,这些函数都在sys/socket.h中。
socket():
socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;
应用程序可以像读写文件一样用read/write在网络上收发数据;
如果socket()调用出错则返回-1;
对于IPv4, family参数指定为AF_INET;
对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议;
protocol参数的介绍从略,指定为0即可。
bind():
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后 就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号;
bind()成功返回0,失败返回-1。
bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听 myaddr所描述的地址和端口号;
前面讲过,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结 构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;
我们的程序中对于myaddr参数是这样初始化的:
1. 将整个结构体清零;
2. 设置地址类型为AF_INET;
3. 网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑 定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用 哪个IP 地址;
4. 端口号为port_
listen():
listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多 的连接请求就忽略, 这里设置不会太大(一般是5);
listen()成功返回0,失败返回-1;
accept():
三次握手完成后, 服务器调用accept()接受连接;
如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
addr是一个传出参数,accept()返回时传出客户端的地址和端口号;
如果给addr 参数传NULL,表示不关心客户端的地址;
addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度 以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)
connect():
客户端需要调用connect()连接服务器;
connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
connect()成功返回0,出错返回-1
2、简易的TCP网络程序
TcpServer.hpp
#pragma once#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <signal.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"
#include "Daemon.hpp"const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 但是一般不要设置的太大
extern Log lg;enum
{UsageError = 1,SocketError,BindError,ListenError,
};class TcpServer;class ThreadData
{
public:ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t): sockfd(fd), clientip(ip), clientport(p), tsvr(t){}
public:int sockfd;std::string clientip;uint16_t clientport;TcpServer *tsvr;
};class TcpServer
{
public:TcpServer(const uint16_t &port, const std::string &ip = defaultip) : listensock_(defaultfd), port_(port), ip_(ip){}void InitServer(){listensock_ = socket(AF_INET, SOCK_STREAM, 0);if (listensock_ < 0){lg(Fatal, "create socket, errno: %d, errstring: %s", errno, strerror(errno));exit(SocketError);}lg(Info, "create socket success, listensock_: %d", listensock_);int opt = 1;setsockopt(listensock_, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt)); // 防止偶发性的服务器无法进行立即重启(tcp协议的时候再说)struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_);inet_aton(ip_.c_str(), &(local.sin_addr));// local.sin_addr.s_addr = INADDR_ANY;if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));exit(BindError);}lg(Info, "bind socket success, listensock_: %d", listensock_);// Tcp是面向连接的,服务器一般是比较“被动的”,服务器一直处于一种,一直在等待连接到来的状态if (listen(listensock_, backlog) < 0){lg(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));exit(ListenError);}lg(Info, "listen socket success, listensock_: %d", listensock_);}// static void *Routine(void *args)// {// pthread_detach(pthread_self());// ThreadData *td = static_cast<ThreadData *>(args);// td->tsvr->Service(td->sockfd, td->clientip, td->clientport);//???// delete td;// return nullptr;// }void Start(){Daemon();ThreadPool<Task>::GetInstance()->Start();// for fork();// signal(SIGCHLD, SIG_IGN);lg(Info, "tcpServer is running....");for (;;){// 1. 获取新连接struct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = accept(listensock_, (struct sockaddr *)&client, &len);if (sockfd < 0){lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?continue;}uint16_t clientport = ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));// 2. 根据新连接来进行通信lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, clientip, clientport);// std::cout << "hello world" << std::endl;// version 1 -- 单进程版// Service(sockfd, clientip, clientport);// close(sockfd);// version 2 -- 多进程版// pid_t id = fork();// if(id == 0)// {// // child// close(listensock_);// if(fork() > 0) exit(0);// Service(sockfd, clientip, clientport); //孙子进程, system 领养// close(sockfd);// exit(0);// }// close(sockfd);// // father// pid_t rid = waitpid(id, nullptr, 0);// (void)rid;// version 3 -- 多线程版本// ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);// pthread_t tid;// pthread_create(&tid, nullptr, Routine, td);// version 4 --- 线程池版本Task t(sockfd, clientip, clientport);ThreadPool<Task>::GetInstance()->Push(t);}}// void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)// {// // 测试代码// char buffer[4096];// while (true)// {// ssize_t n = read(sockfd, buffer, sizeof(buffer));// if (n > 0)// {// buffer[n] = 0;// std::cout << "client say# " << buffer << std::endl;// std::string echo_string = "tcpserver echo# ";// echo_string += buffer;// write(sockfd, echo_string.c_str(), echo_string.size());// }// else if (n == 0)// {// lg(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);// break;// }// else// {// lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);// break;// }// }// }~TcpServer() {}private:int listensock_;uint16_t port_;std::string ip_;
};
TcpClient.cc
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>void Usage(const std::string &proc)
{std::cout << "\n\rUsage: " << proc << " serverip serverport\n"<< std::endl;
}// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));while (true){int cnt = 5;int isreconnect = false;int sockfd = 0;sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;return 1;}do{// tcp客户端要不要bind?1 要不要显示的bind?0 系统进行bind,随机端口// 客户端发起connect的时候,进行自动随机bindint n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){isreconnect = true;cnt--;std::cerr << "connect error..., reconnect: " << cnt << std::endl;sleep(2);}else{break;}} while (cnt && isreconnect);if (cnt == 0){std::cerr << "user offline..." << std::endl;break;}// while (true)// {std::string message;std::cout << "Please Enter# ";std::getline(std::cin, message);int n = write(sockfd, message.c_str(), message.size());if (n < 0){std::cerr << "write error..." << std::endl;// break;}char inbuffer[4096];n = read(sockfd, inbuffer, sizeof(inbuffer));if (n > 0){inbuffer[n] = 0;std::cout << inbuffer << std::endl;}else{// break;}// }close(sockfd);}return 0;
}
Main.cc
#include "TcpServer.hpp"
#include <iostream>
#include <memory>void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}//./tcpserver 8080
int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);exit(UsageError);}uint16_t port = std::stoi(argv[1]);lg.Enable(Classfile);// std::unique_ptr<TcpServer> tcp_svr(new TcpServer(port, "127.0.0.1"));std::unique_ptr<TcpServer> tcp_svr(new TcpServer(port));tcp_svr->InitServer();tcp_svr->Start();return 0;
}
Log.hpp
#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}// void logmessage(int level, const char *format, ...)// {// time_t t = time(nullptr);// struct tm *ctime = localtime(&t);// char leftbuffer[SIZE];// snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),// ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,// ctime->tm_hour, ctime->tm_min, ctime->tm_sec);// // va_list s;// // va_start(s, format);// char rightbuffer[SIZE];// vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);// // va_end(s);// // 格式:默认部分+自定义部分// char logtxt[SIZE * 2];// snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);// // printf("%s", logtxt); // 暂时打印// printLog(level, logtxt);// }void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);// printf("%s", logtxt); // 暂时打印printLog(level, logtxt);}private:int printMethod;std::string path;
};Log lg;// int sum(int n, ...)
// {
// va_list s; // char*
// va_start(s, n);// int sum = 0;
// while(n)
// {
// sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
// n--;
// }// va_end(s); //s = NULL
// return sum;
// }
ThreadPool.hpp
#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>struct ThreadInfo
{pthread_t tid;std::string name;
};static const int defalutnum = 10;template <class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}bool IsQueueEmpty(){return tasks_.empty();}std::string GetThreadName(pthread_t tid){for (const auto &ti : threads_){if (ti.tid == tid)return ti.name;}return "None";}public:static void *HandlerTask(void *args){ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while (true){tp->Lock();while (tp->IsQueueEmpty()){tp->ThreadSleep();}T t = tp->Pop();tp->Unlock();t();}}void Start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name = "thread-" + std::to_string(i + 1);pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);}}T Pop(){T t = tasks_.front();tasks_.pop();return t;}void Push(const T &t){Lock();tasks_.push(t);Wakeup();Unlock();}static ThreadPool<T> *GetInstance(){if (nullptr == tp_) // ???{pthread_mutex_lock(&lock_);if (nullptr == tp_){std::cout << "log: singleton create done first!" << std::endl;tp_ = new ThreadPool<T>();}pthread_mutex_unlock(&lock_);}return tp_;}private:ThreadPool(int num = defalutnum) : threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}ThreadPool(const ThreadPool<T> &) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:std::vector<ThreadInfo> threads_;std::queue<T> tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;static ThreadPool<T> *tp_;static pthread_mutex_t lock_;
};template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;
Task.hpp
#pragma once
#include <iostream>
#include <string>
#include "Log.hpp"
#include "Init.hpp"extern Log lg;
Init init;class Task
{
public:Task(int sockfd, const std::string &clientip, const uint16_t &clientport): sockfd_(sockfd), clientip_(clientip), clientport_(clientport){}Task(){}void run(){// 测试代码char buffer[4096];// Tcp是面向字节流的,你怎么保证,你读取上来的数据,是"一个" "完整" 的报文呢?ssize_t n = read(sockfd_, buffer, sizeof(buffer)); // BUG?if (n > 0){buffer[n] = 0;std::cout << "client key# " << buffer << std::endl;std::string echo_string = init.translation(buffer);// sleep(5);// // close(sockfd_);// lg(Warning, "close sockfd %d done", sockfd_);// sleep(2);n = write(sockfd_, echo_string.c_str(), echo_string.size()); // 100 fd 不存在if(n < 0){lg(Warning, "write error, errno : %d, errstring: %s", errno, strerror(errno));}}else if (n == 0){lg(Info, "%s:%d quit, server close sockfd: %d", clientip_.c_str(), clientport_, sockfd_);}else{lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd_, clientip_.c_str(), clientport_);}close(sockfd_);}void operator()(){run();}~Task(){}private:int sockfd_;std::string clientip_;uint16_t clientport_;
};
Init.hpp
#pragma once#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"const std::string dictname = "./dict.txt";
const std::string sep = ":";//yellow:黄色...
static bool Split(std::string &s, std::string *part1, std::string *part2)
{auto pos = s.find(sep);if(pos == std::string::npos) return false;*part1 = s.substr(0, pos);*part2 = s.substr(pos+1);return true;
}class Init
{
public:Init(){std::ifstream in(dictname);if(!in.is_open()){lg(Fatal, "ifstream open %s error", dictname.c_str());exit(1);}std::string line;while(std::getline(in, line)){std::string part1, part2;Split(line, &part1, &part2);dict.insert({part1, part2});}in.close();}std::string translation(const std::string &key){auto iter = dict.find(key);if(iter == dict.end()) return "Unknow";else return iter->second;}
private:std::unordered_map<std::string, std::string> dict;
};
Daemon.hpp
#pragma once#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>const std::string nullfile = "/dev/null";void Daemon(const std::string &cwd = "")
{// 1. 忽略其他异常信号signal(SIGCLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);signal(SIGSTOP, SIG_IGN);// 2. 将自己变成独立的会话if (fork() > 0)exit(0);setsid();// 3. 更改当前调用进程的工作目录if (!cwd.empty())chdir(cwd.c_str());// 4. 标准输入,标准输出,标准错误重定向至/dev/nullint fd = open(nullfile.c_str(), O_RDWR);if(fd > 0){dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);}
}
dict.txt
apple:苹果...
banana:香蕉...
red:红色...
yellow:黄色...
the: 这
be: 是
to: 朝向/给/对
and: 和
I: 我
in: 在...里
that: 那个
have: 有
will: 将
for: 为了
but: 但是
as: 像...一样
what: 什么
so: 因此
he: 他
her: 她
his: 他的
they: 他们
we: 我们
their: 他们的
his: 它的
with: 和...一起
she: 她
he: 他(宾格)
it: 它
Makefile
.PHONY:all
all:tcpserver tcpclienttcpserver:Main.ccg++ -o $@ $^ -std=c++11 -lpthread
tcpclient:TcpClient.cc g++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f tcpserver tcpclient