前言:本篇博客利用Socket API
实现一个简单通用的Tcp及Udp
服务器及客户端。并且将Tcp
版本的服务器改为多进程版本
和多线程版本
以及引入线程池
。
网络编程套接字:https://blog.csdn.net/hansionz/article/details/85226345
udp
的socket
接口//udp_socket.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
class UdpSockect{
public:
UdpSockect()
:_fd(-1)
{}
//创建套接字
void Sockect()
{
//AF_INET代表IPV4协议族
//SOCK_DGRAM代表数据报
_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(_fd < 0)
{
perror("use socket");
return;
}
}
//关闭套接字,相当于关闭文件
void Close()
{
close(_fd);
}
//绑定端口号(服务器)
void Bind(uint16_t port)
{
sockaddr_in addr;//IPv4的地址类型
addr.sin_family = AF_INET;
addr.sin_port = htons(port);//主机字节序转网络字节序
//inet_addr可以将点分十进制的字符串转为in_addrIP地址
//INADDR_ANY代表0.0.0.0
addr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(_fd, (sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("use bind");
return;
}
}
//接收消息(客户端、服务器)
void ReceFrom(std::string* buf, std::string* ip = NULL, uint16_t* port = NULL)
{
char tmp[1024] = {0};
sockaddr_in addr;
socklen_t len = sizeof(addr);
//返回值为实际接收到的字符个数
ssize_t rs = recvfrom(_fd, tmp, sizeof(tmp)-1, 0, (sockaddr*)&addr, &len);
if(rs < 0)
{
perror("use recefrom");
return;
}
//assign是使用c类指针tmp的前rs个字符重新赋值buf
buf->assign(tmp, rs);
//如果外部要使用addr中的ip地址和端口号,则赋值,否则直接跳过
if(ip != NULL)
{
*ip = inet_ntoa(addr.sin_addr);
}
if(port != NULL)
{
*port = ntohs(addr.sin_port);
}
}
//发送消息(客户端、服务器)
void SendTo(const std::string& buf, const std::string& ip, uint16_t port)
{
sockaddr_in addr;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
addr.sin_family = AF_INET;
ssize_t ws = sendto(_fd, buf.data(), buf.size(), 0, (sockaddr*)&addr, sizeof(addr));
if(ws < 0)
{
perror("use sendto");
return;
}
}
private:
int _fd;
};
注:网络地址为INADDR_ANY
, 这个宏表示本地的任意IP地址
,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址
, 这样设置可以在所有的IP地址上监听
,直到与某个客户端建立了连接时
才确定下来到底用哪个IP 地址
,当然这是针对TCP的。
udp
服务器#pragma once
#include "udp_sockect.hpp"
//函数指针
typedef void (*Handler)(const std::string& req, std::string* res);
// C++ 11 式写法
//#include
//typedef std::function Handler;
class UdpServer{
public:
UdpServer()
{
//创建套接字
_sock.Sockect();
}
/* 1.创建套接字
* 2.绑定端口号
* 3.开始循环读取客户端请求
* 4.读取到请求后做请求处理
* 5.得到响应
* 6.向客户端发回响应
**/
//开启服务器
void Start( uint16_t port, Handler handler)
{
_sock.Bind( port);
while(1)
{
//不断读取客户端的请求
std::string req;
std::string re_ip;//获取远程主机的ip和端口号
uint16_t re_port;
_sock.ReceFrom(&req, &re_ip, &re_port);
//处理请求得到响应结果
std::string res;
handler(req, &res);
//返回响应给客户端
_sock.SendTo(res, re_ip, re_port);//re_ip代表目的ip和port
std::cout << "ip: " << re_ip << "port: "<< re_port << " | req: " << req << " res: " << res <<std::endl;
}
_sock.Close();
}
private:
UdpSockect _sock;
};
udp
客户端#pragma once
#include "udp_sockect.hpp"
class UdpClient{
public:
UdpClient(std::string& ip, uint16_t port)
:_ip(ip)
,_port(port)
{
//创建套接字
_sock.Sockect();
}
~UdpClient()
{
_sock.Close();
}
void RecvFrom(std::string* buf)
{
_sock.ReceFrom(buf);
}
void SendTo(std::string& buf)
{
_sock.SendTo(buf, _ip, _port);
}
private:
UdpSockect _sock;
std::string _ip;
uint16_t _port;
};
以上述的通用服务器和客户端
实现一个英译汉词典
:给出英文,发送给服务器,然后服务器返回该英文单词的汉语意思。
英译汉
词典服务器#include "udp_server.hpp"
#include
std::unordered_map<std::string, std::string> dict;
void Translate(const std::string& req, std::string* res)
{
//std::cout << "translate" << std::endl;
auto it = dict.find(req);
if(it == dict.end())
{
*res = "词典中没有该词汇";
return;
}
*res = it->second;
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
std::cout << "Usage:./dict_server[port]" << std::endl;
return 1;
}
//向词典中插入一些键值对
dict.insert(std::make_pair("sport","运动"));
dict.insert(std::make_pair("computer","电脑"));
dict.insert(std::make_pair("english","英语"));
dict.insert(std::make_pair("math","数学"));
UdpServer server;
server.Start(atoi(argv[1]), Translate);
return 0;
}
英译汉
词典客户端#include "udp_client.hpp"
int main(int argc, char* argv[])
{
if(argc != 3)
{
std::cout << "Usage:./dict_client[ip][port]" << std::endl;
return 1;
}
std::string ip(argv[1]);
UdpClient client(ip, atoi(argv[2]));
while(1)
{
std::string word;
std::cout <<"请输入要查的单词:";
std::cin >> word;
if(word == "quit")
{
std::cout << "good bye" << std::endl;
break;
}
client.SendTo(word);
std::string res;
client.RecvFrom(&res);
std::cout << res << std::endl;
}
return 0;
}
代码github
链接:https://github.com/hansionz/Linux_Code/tree/master/Socket/UdpCode
Tcp sockect
接口//tcp_socket.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
class TcpSocket
{
public:
TcpSocket(){}
TcpSocket(int fd)
{
_fd = fd;
}
//创建套接字
void Socket()
{
_fd = socket(AF_INET, SOCK_STREAM, 0);
if(_fd < 0){
perror("use socket");
return;
}
}
//关闭套接字
void Close() const
{
close(_fd);
}
//绑定ip和端口号
void Bind(const string& ip, uint16_t port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
if(bind(_fd, (sockaddr*)&addr, sizeof(addr)) < 0){
perror("use bind");
return;
}
}
//监听
void Listen(int backlog)
{
if(listen(_fd, backlog) < 0){
perror("use listen");
return;
}
}
//服务器接受连接
void Accept(TcpSocket* obj, string* ip = NULL, uint16_t* port = NULL )
{
sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(_fd, (sockaddr*)&peer, &len);
if(new_sock < 0){
perror("use accept");
return;
}
obj->_fd = new_sock;
if(ip != NULL)
{
*ip = inet_ntoa(peer.sin_addr);
}
if(port != NULL)
{
*port = ntohs(peer.sin_port);
}
}
//接受消息
void Recv(string* buf)
{
buf->clear();
char peer[1024];
ssize_t read_size = read(_fd, peer, sizeof(peer) - 1);
if(read_size < 0){
perror("use read");
return;
}
buf->assign(peer, read_size);
}
//发送消息
void Send(const string& buf)
{
ssize_t write_size = write(_fd, buf.c_str(), buf.size());
if(write_size < 0){
perror("use wirte");
return;
}
}
//客户端建立连接
void Connect(string& ip, uint16_t port)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
if(connect(_fd, (sockaddr*)&addr, sizeof(addr)) < 0)
{
perror("use connect");
return;
}
}
int Getfd() const
{
return _fd;
}
private:
int _fd = -1;
};
Tcp
服务器#pragma once
#include "tcp_socket.hpp"
typedef void (*Handler)(string& req, string* res);
class TcpServer
{
public:
TcpServer(string ip, uint16_t port)
:_ip(ip)
,_port(port)
{}
void Start(Handler handler)
{
//1.创建socket
listen_sock.Socket();
//2.绑定ip和端口号
listen_sock.Bind(_ip, _port);
//3.监听
listen_sock.Listen(5);
while(1)
{
TcpSocket new_sock;
string ip;
uint16_t port;
//4.接收连接
listen_sock.Accept(&new_sock, &ip, &port);
cout <<"client:" << ip.c_str() << " connect" << endl;
while(1)
{
//5.连接成功读取客户端请求
string req;
new_sock.Recv(&req);
//6.处理请求
string res;
handler(req, &res);
//写回处理结果
new_sock.Send(res);
cout << "客户:" << ip.c_str() << " REQ:" << req << ". RES:" << res << endl;
}
}
}
private:
TcpSocket listen_sock;
string _ip;
uint16_t _port;
};
Tcp
客户端#pragma once
#include "tcp_socket.hpp"
class TcpClient
{
public:
TcpClient(string ip, uint16_t port)
:_ip(ip)
,_port(port)
{
sock.Socket();
}
void Connect()
{
sock.Connect(_ip, _port);
}
void Recv(string* buf)
{
sock.Recv(buf);
}
void Send(const string& buf)
{
sock.Send(buf);
}
~TcpClient()
{
sock.Close();
}
private:
TcpSocket sock;
string _ip;
uint16_t _port;
};
以上面通用服务器和客户端为基础实现一个简单网络翻译器
。客户端输入要查询的单词,发送给服务器然后服务器经过查询项客户端。
#include "tcp_proc_server.hpp"
#include
unordered_map<string, string> dict;
void Translate(string& req, string* res)
{
auto it = dict.find(req);
if(it == dict.end())
{
*res = "词典中没有该单词";
return;
}
*res = it->second;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
cout << "Usage: ./tcp_server [ip][port]" << endl;
return 1;
}
//初始化词典
dict.insert(make_pair("book","书"));
dict.insert(make_pair("tree", "树"));
dict.insert(make_pair("dream","梦想"));
string ip(argv[1]);
TcpProcServer server(ip,atoi(argv[2]));
server.Start(Translate);
return 0;
}
#include "tcp_client.hpp"
int main(int argc, char* argv[])
{
if(argc != 3)
{
cout << "Usage: ./dict_client [ip][port]" << endl;
return 1;
}
TcpClient client(argv[1], atoi(argv[2]));
client.Connect();
while(1)
{
cout << "请输入查询的单词:" << endl;
string word;
cin >> word;
if(word == "quit")
break;
client.Send(word);
string res;
client.Recv(&res);
cout << "意思是:" << res << endl;
}
return 0;
}
.PHONY:all
all:dict_client dict_server
dict_client:dict_client.cc tcp_client.hpp tcp_socket.hpp
g++ -o $@ -std=c++11 $^
dict_server:dict_server.cc tcp_server.hpp tcp_socket.hpp
g++ -o $@ -std=c++11 $^
.PHONY:clean
clean:
rm -f dict_client dict_server
代码github链接:https://github.com/hansionz/Linux_Code/tree/master/Socket
#pragma once
#include "tcp_socket.hpp"
#include
#include
typedef void (*Handler)(string& req, string* res);
class TcpProcServer
{
public:
TcpProcServer(string& ip, uint16_t port)
:_ip(ip)
,_port(port)
{
signal(SIGCHLD, SIG_IGN);
}
void ProcConnect(TcpSocket& newsock, string& ip, uint16_t port, Handler hanler)
{
pid_t id = fork();
if(id < 0)//fork失败
{
perror("use fork");
exit(1);
}
else if(id == 0){//child
while(1){
string req;
newsock.Recv(&req);
string res;
hanler(req, &res);
newsock.Send(res);
cout << ip << "-" << port << " req:" << req << " | res:" << res <<endl;
}
}
else{//father
//父进程直接结束即可,不需要wait子进程,因为已经将SIGCHILD信号捕捉为忽略
//父进程也不能wait,如果wait不能再次快速调用accept
//父进程需要关闭newsock
newsock.Close();
return;
}
}
void Start(Handler handler)
{
listen_sock.Socket();
listen_sock.Bind(_ip, _port);
listen_sock.Listen(5);
while(1)
{
TcpSocket newsock;
string ip;
uint16_t port;
listen_sock.Accept(&newsock, &ip, &port);
cout << "client:" << ip.c_str() << "-" << port << " connect" << endl;
ProcConnect(newsock, ip, port, handler);
}
}
private:
TcpSocket listen_sock;
string _ip;
uint16_t _port;
};
#pragma once
#include "tcp_socket.hpp"
#include
#include
typedef void (*Handler)(string& req, string* res);
typedef struct ThreadArg
{
TcpSocket new_sock;
string ip;
uint16_t port;
Handler handler;
}ThreadArg;
class TcpPthServer
{
public:
TcpPthServer(string& ip, uint16_t port)
:_ip(ip)
,_port(port)
{}
//这个函数也必须是静态函数,才能被Routine函数调用
static void SingleDeal(ThreadArg* arg)
{
while(1)
{
string req;
arg->new_sock.Recv(&req);
string res;
arg->handler(req, &res);
arg->new_sock.Send(res);
cout << "client:" << arg->ip << "--" << arg->port << "req:" << req << " .res:" << res << endl;
}
}
//这个函数必须是静态函数,负责会多出一个this参数
static void* Routine(void* arg)
{
pthread_detach(pthread_self());
//reinterpret_cast是C++中的强制类型转换
ThreadArg* ptr = reinterpret_cast<ThreadArg*>(arg);
SingleDeal(ptr);
//当处理完时,要释放套接字描述符
ptr->new_sock.Close();
//传入的arg参数是new出来的
delete ptr;
ptr = NULL;
}
void Start(Handler handler)
{
listen_sock.Socket();
listen_sock.Bind(_ip, _port);
listen_sock.Listen(5);
while(1)
{
ThreadArg* arg = new ThreadArg();
arg->handler = handler;
listen_sock.Accept(&arg->new_sock, &arg->ip, &arg->port);
cout << "client:" << arg->ip.c_str() << "--" << arg->port << " connect" << endl;
pthread_t tid;
pthread_create(&tid, NULL, Routine, (void*)arg);
}
}
private:
TcpSocket listen_sock;
string _ip;
uint16_t _port;
};
#include "tcp_socket.hpp"
#include
#include
using namespace std;
typedef void (*Handler_t)(TcpSocket);
class Task
{
public:
Task(TcpSocket newsock, Handler_t handler = NULL)
:_sock(newsock)
,_handler(handler)
{}
void Run()
{
_handler(_sock);
}
private:
TcpSocket _sock;
Handler_t _handler;
};
class ThreadPool
{
public:
ThreadPool(int nums)
:_nums(nums)
,idle_nums(0)
{
pthread_cond_init(&_cond, NULL);
pthread_mutex_init(&_mutex, NULL);
}
void LockQueue()
{
pthread_mutex_lock(&_mutex);
}
void UnlockQueue()
{
pthread_mutex_unlock(&_mutex);
}
void IdlePthread()
{
pthread_cond_wait(&_cond, &_mutex);
}
void NotiyPthread()
{
pthread_cond_signal(&_cond);
}
bool IsEmpty()
{
return q.size() == 0;
}
Task PopTask()
{
Task t = q.front();
q.pop();
return t;
}
static void* ThreadRoutine(void* arg)
{
ThreadPool* tp = reinterpret_cast<ThreadPool*>(arg);
pthread_detach(pthread_self());//分离线程
while(1)
{
tp->LockQueue();
while(tp->IsEmpty())//使用while轮询防止假唤醒
{
tp->IdlePthread();
}
Task t = tp->PopTask();
tp->UnlockQueue();
t.Run();
}
}
void InitPool()
{
pthread_t pid;
for(int i = 0; i < _nums; i++)
{
pthread_create(&pid, NULL, ThreadRoutine, this);
}
}
void AddTask(const Task& t)
{
LockQueue();
q.push(t);
NotiyPthread();
UnlockQueue();
}
~ThreadPool()
{
pthread_cond_destroy(&_cond);
pthread_mutex_destroy(&_mutex);
}
private:
TcpSocket _sock;
int _nums;
int idle_nums;
queue<Task> q;
pthread_cond_t _cond;
pthread_mutex_t _mutex;
};
#pragma once
#include "tcp_socket.hpp"
#include "threadpool.hpp"
#include
unordered_map<string, string> dict;
void Translate(string& req, string* res)
{
auto it = dict.find(req);
if(it == dict.end())
{
*res = "词典中没有该单词";
return;
}
*res = it->second;
}
//typedef void (*Handler)(string& req, string* res);
class TcpServer
{
public:
TcpServer(string ip, uint16_t port)
:_pool(5)
,_ip(ip)
,_port(port)
{}
static void service(TcpSocket sock)
{
//初始化词典
dict.insert(make_pair("book","书"));
dict.insert(make_pair("tree", "树"));
dict.insert(make_pair("dream","梦想"));
while(1)
{
string req;
sock.Recv(&req);
string res;
Translate(req, &res);
cout << req << "-" << res << endl;
sock.Send(res);
}
sock.Close();
}
void Start()
{
//1.创建socket
listen_sock.Socket();
//2.绑定ip和端口号
listen_sock.Bind(_ip, _port);
//3.监听
listen_sock.Listen(5);
//初始化线程池
_pool.InitPool();
while(1)
{
TcpSocket new_sock;
string ip;
uint16_t port;
//4.接收连接
listen_sock.Accept(&new_sock, &ip,&port);
cout <<"client:" << ip.c_str() << " connect" << endl;
Task t(new_sock, service);
_pool.AddTask(t);
}
}
private:
TcpSocket listen_sock;
ThreadPool _pool;
string _ip;
uint16_t _port;
};