目录
1. 常用接口
2. 服务器和客户端的简单流程
3. C/S 回声通信
4. 创建子进程完成 C/S 回声通信
5. 创建孙子进程完成 C/S 回声通信
6. 创建线程完成 C/S 回声通信
7. 使用线程池完成 C/S 回声通信
Linux网络编程在✨ 本篇博文的代码虽然多,但都是修改一点点tcp_server.cc代码。tcp_client.cc、makefile代码是没有改动的,全部写出来是为了保证代码的完整性,还请大家耐心看下去!
socket:创建套接字:
// 创建 socket 文件描述符
int socket(int domain, int type, int protocol);
返回值:
参数:
struct sockaddr_in 结构体:
struct sockaddr_in当中的成员如下:
sin_family:表示通信机制(本地/网络)。
sin_port:表示端口号,是一个16位的整数。
sin_addr.s_addr:表示IP地址,是一个32位的整数。
bind:绑定端口号:
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
返回值:
参数:
建立连接:(TCP,客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值说明:
参数说明:
监听套接字:(TCP,服务器)
int listen(int sockfd, int backlog);
返回值:
参数:
接收请求:(TCP,服务器)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
返回值:
参数说明:
客户端:
服务器端:
结果演示:
源代码:
.PHONY:all
all:tcp_server tcp_client
tcp_server:tcp_server.cc
g++ -o $@ $^ -std=c++11
tcp_client:tcp_client.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f tcp_server tcp_client
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//使用手册
void Usage(char* proc)
{
cout<<"Usage:\n\t"<0)
{
buffer[s-1]='\0';
cout<<"client say # "< : ip:["<
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//使用手册
void Usage(char* proc)
{
cout<<"Usage:\n\t"<
文件描述符泄露问题:
总结:上述是一种客户和服务器一对一的业务通信,在平常生活中不会使用到;
结果演示:
源代码:
.PHONY:all
all:tcp_server tcp_client
tcp_server:tcp_server.cc
g++ -o $@ $^ -std=c++11
tcp_client:tcp_client.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f tcp_server tcp_client
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//使用手册
void Usage(char* proc)
{
cout<<"Usage:\n\t"<0)
{
buffer[s-1]='\0';
cout<<"client say # "< : ip:["<
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//使用手册
void Usage(char* proc)
{
cout<<"Usage:\n\t"<
不回收资源造成僵尸进程的问题:
总结:
客户端发起链接请求;
服务端接收链接请求,创建子进程完成业务逻辑 ,捕捉SIGCHLD信号,不用等待子进程;
达到了一个服务器服务多个客户的目的;
结果演示:
源代码:
.PHONY:all
all:tcp_server tcp_client
tcp_server:tcp_server.cc
g++ -o $@ $^ -std=c++11
tcp_client:tcp_client.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f tcp_server tcp_client
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//使用手册
void Usage(char* proc)
{
cout<<"Usage:\n\t"<0)
{
buffer[s-1]='\0';
cout<<"client say # "< : ip:["<0)
{
close(new_sock);
//退出的是子进程
exit(0);
}
//向后走的是孙子进程
//孙子进程会被OS进程领养
//资源由OS回收
service(new_sock);
}
//parent
close(new_sock);
//虽然父进程对子进程进行了等待,但子进程完成创建孙子进程后直接退出了,不会造成阻塞
waitpid(pid, nullptr, 0);
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//使用手册
void Usage(char* proc)
{
cout<<"Usage:\n\t"<
总结:
与上个模型一样都达到了一个服务器服务多个客户的目的;
只不过这个是通过创建孙子进程完成的;
结果演示:
源代码:
.PHONY:all
all:tcp_server tcp_client
tcp_server:tcp_server.cc
g++ -o $@ $^ -std=c++11 -lpthread
tcp_client:tcp_client.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f tcp_server tcp_client
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//使用手册
void Usage(char* proc)
{
cout<<"Usage:\n\t"<0)
{
buffer[s-1]='\0';
cout<<"client say # "< : ip:["<
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//使用手册
void Usage(char* proc)
{
cout<<"Usage:\n\t"<
总结:
创建子进程可以完成业务逻辑,创建线程也可以(创建成本比创建子进程少的多),采用线程分离方式,主线程不用等待创建出来的线程;
创建线程也可完成上述业务逻辑,但每当有客户端需要通信时,才创建线程,来一个客户创建一个线程,创建线程的消耗也是蛮大的,在之前我们学习过线程池,我们可以一次创建多个线程,每当客户端需要通信时,分配线程,减少了创建线程的消耗;
结果演示:
源代码:
.PHONY:all
all:tcp_server tcp_client
tcp_server:tcp_server.cc
g++ -o $@ $^ -std=c++11 -lpthread
tcp_client:tcp_client.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f tcp_server tcp_client
#pragma once
#include
#include
#include
namespace ns_task
{
class Task
{
private:
int sock;
public:
Task() : sock(-1) {}
Task(int _sock) : sock(_sock)
{
}
int Run()
{
//提供服务,我们是一个死循环
// while (true)
// {
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0; //将获取的内容当成字符串
std::cout << "client# " << buffer << std::endl;
//拉取逻辑
std::string echo_string = ">>>server<<<, ";
echo_string += buffer;
write(sock, echo_string.c_str(), echo_string.size());
}
else if (s == 0)
{
std::cout << "client quit ..." << std::endl;
// break;
}
else
{
std::cerr << "read error" << std::endl;
// break;
}
// }
close(sock);
}
~Task() {}
};
}
#pragma once
#include
#include
#include
#include
#include
namespace ns_threadpool
{
const int g_num = 5;
template
class ThreadPool
{
private:
int num_;
std::queue task_queue_; //该成员是一个临界资源
pthread_mutex_t mtx_;
pthread_cond_t cond_;
static ThreadPool *ins;
private:
// 构造函数必须得实现,但是必须的私有化
ThreadPool(int num = g_num) : num_(num)
{
pthread_mutex_init(&mtx_, nullptr);
pthread_cond_init(&cond_, nullptr);
}
ThreadPool(const ThreadPool &tp) = delete;
//赋值语句
ThreadPool &operator=(ThreadPool &tp) = delete;
public:
static ThreadPool *GetInstance()
{
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
// 当前单例对象还没有被创建
if (ins == nullptr) //双判定,减少锁的争用,提高获取单例的效率!
{
pthread_mutex_lock(&lock);
if (ins == nullptr)
{
ins = new ThreadPool();
ins->InitThreadPool();
std::cout << "首次加载对象" << std::endl;
}
pthread_mutex_unlock(&lock);
}
return ins;
}
void Lock()
{
pthread_mutex_lock(&mtx_);
}
void Unlock()
{
pthread_mutex_unlock(&mtx_);
}
void Wait()
{
pthread_cond_wait(&cond_, &mtx_);
}
void Wakeup()
{
pthread_cond_signal(&cond_);
}
bool IsEmpey()
{
return task_queue_.empty();
}
public:
// 在类中要让线程执行类内成员方法,是不可行的!
// 必须让线程执行静态方法
static void *Rountine(void *args)
{
pthread_detach(pthread_self());
ThreadPool *tp = (ThreadPool *)args;
while (true)
{
tp->Lock();
while (tp->IsEmpey())
{
//任务队列为空,线程该做什么呢??
tp->Wait();
}
//该任务队列中一定有任务了
T t;
tp->PopTask(&t);
tp->Unlock();
t.Run();
}
}
void InitThreadPool()
{
pthread_t tid;
for (int i = 0; i < num_; i++)
{
pthread_create(&tid, nullptr, Rountine, (void *)this /*?*/);
}
}
void PushTask(const T &in)
{
Lock();
task_queue_.push(in);
Unlock();
Wakeup();
}
void PopTask(T *out)
{
*out = task_queue_.front();
task_queue_.pop();
}
~ThreadPool()
{
pthread_mutex_destroy(&mtx_);
pthread_cond_destroy(&cond_);
}
};
template
ThreadPool *ThreadPool::ins = nullptr;
} // namespace ns_threadpool
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Task.hpp"
#include "thread_pool.hpp"
using namespace std;
using namespace ns_threadpool;
using namespace ns_task;
//使用手册
void Usage(char* proc)
{
cout<<"Usage:\n\t"< : ip:["<::GetInstance()->PushTask(t);
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//使用手册
void Usage(char* proc)
{
cout<<"Usage:\n\t"<
如果上述文章对您有所帮助的话,还请点赞,收藏,关注