当前位置: 首页 > news >正文

网络套接字补充——TCP网络编程

六、TCP网络编程

6.1IP地址字符串和整数之间的转换接口

//字符串转整数接口
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
int inet_pton(int af, const char *strptr, void *addrptr);//注意dst指的是in_addr *的地址
in_addr_t inet_addr(const char *cp);//将字符串转32位并且是网络序列的;
//整数转字符串
char *inet_ntoa(struct in_addr in);//将整数转为字符串并且将网络字节序转为主机字节序
const char *inet_ntop(int af, const void *addrptr,char *strptr, socklen_t size);

​ 需要注意的是inet_ntoa函数这个函数返回的是一个静态变量地址,使用时有覆盖问题和线程安全问题;最好是使用inet_ntop;

6.2补充知识

1.将网络套接字进行封装

​ 构造函数之中最好少做一些有风险的事情,这样可以保证最起码对象是没有问题的;其他如打开文件之类的操作就交给其他函数去完成;

2.获取新连接会产生多个文件描述符

​ 服务器本地的文件描述符用来进行监听连接,获取新连接,真正进行IO通信的文件描述符是后生成的;这样既提高了服务器的并发度;

3.telnet的使用

​ 默认使用的就是TCP;

​ 使用ctrl+]进入,回车后进行输入(会自动在输入的文本后面加\r\n)会回显数据,q退出;

4.TCP连接中的异常问题

​ 当客户端直接退出时,服务端就会读到0,此时需要关闭为客户端打开的文件描述符;

5.网络抖动断开连接,客户端自动发起连接请求设计

6.tcp服务器重连

​ 服务器断开后不能直接连接,一般要等待120s左右;

6.3使用接口

6.3.1创建套接字

​ 和udp使用是一样的;

6.3.2绑定套接字

​ 和udp使用是一样的;

6.3.3设置监听

​ 由于TCP是面向连接的,所以在通信前要建立连接,将套接字设置为监听状态;

#include <sys/types.h>         
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//第二个参数表示的是全连接的队列的长度,一般不能设置的太大;
6.3.4获取新连接

​ 此处包括以上接口都是阻塞的;

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//返回值,成功返回一个文件描述符,失败返回-1,错误码被设置;

​ 获取新连接成功后要根据客户端的套接字信息提供服务;

6.3.5客户端发起连接请求

​ 客户端需要绑定但是不需要显式进行绑定,系统会在客户端发起连接请求的时候自动绑定

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

6.4查看网络状态

netstat -nltp
#n显示成数字,l表示listen,表示tcp

6.5单进程版echo服务器

​ 缺陷是同时最多只能有一个客户端进行访问;UDP所有的客户端用的是一个sockfd,一个文件,可以同时读写,而TCP每个客户端对应一个sockfd,一个文件;单进程下对一个文件读写时,服务器因为处理消息是循环处理,必须读完退出循环服务,才能继续获取新连接,此时另一个客户端已经想打开的sockfd文件写入很多数据,当服务端接收连接请求时,会将发送过来的一大批数据处理后返回,这样就无法实现正常的服务器;

char buff[4096];
while (true)
{// 数据读取ssize_t n = read(sockfd, buff, sizeof(buff) - 1);if (n > 0){buff[n] = '\0';std::cout << "client say@ " << buff << std::endl;// 数据回显std::string echo_string;echo_string += "tcpserver say#";echo_string += buff;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, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);break;}
}

6.6多进程版echo服务器

​ 1.子进程可以看见listenfd_,所以要关闭无关的文件描述符;2.父进程不关心sockfd,要去接收新的连接,如果不关闭就会导致之后的很多文件描述符没有关闭,不断从新的下标打开文件描述符,而不是重新分配;

​ 2.子进程中继续fork(),然后子进程退出,被父进程阻塞等待回收,父进程继续获取新的连接,孙子进程被操作系统领养,执行服务部分;也可以使用信号异步等待的方式实现;

​ 3.也可以在循环执行获取连接和执行任务之前创建子进程,但是会存在数据不一致问题需要用信号量;

​ 4.多进程创建的成本过高,所以应该选择多线程;

//方式1
pid_t id = fork();
if (id < 0)
{std::cerr << "fork error" << std::endl;
}
else if (id == 0)
{close(listensockfd_);if (fork() > 0){exit(0);}// 此处执行代码的是孙子进程,会被做系统领养service(sockfd, clientip, clientport);close(sockfd);exit(0);
}
close(sockfd);
pid_t rid = waitpid(id, nullptr, 0);
(void)rid;
//方式2
signal(SIGCHLD, SIG_IGN);
pid_t id = fork();
if (id == 0)
{close(listensockfd_);service(sockfd, clientip, clientport);close(sockfd);exit(0);
}
close(sockfd);

6.7多线程版本的echo服务器

​ 1.因为线程中大部分资源都是共享的所以不可以关闭文件描述符,否则会出错;

​ 2.线程没有退出时会有峰值的,服务器此时压力很大,所以长服务是不合理的;

​ 3.创建线程也是有成本的,即系统调用的成本,所以应该用线程池;

struct threaddata
{threaddata(const int sockfd, const std::string &clientip, const uint16_t &clientport, tcpserver *t) : sockfd_(sockfd), clientport_(clientport),clientip_(clientip), t_(t){}int sockfd_;uint16_t clientport_;std::string clientip_;tcpserver *t_;
};pthread_t tid;
threaddata *td = new threaddata(sockfd, clientip, clientport, this);
pthread_create(&tid, nullptr, routine, (void *)td);static void *routine(void *args)
{threaddata *td = static_cast<threaddata *>(args);pthread_detach(pthread_self());td->t_->service(td->sockfd_, td->clientip_, td->clientport_);delete td;return nullptr;
}

6.8线程池版本的echo服务器

​ 1.线程池里不可以执行长时间的服务;2.服务器关闭了客户端套接字,客户端继续写入,会触发服务器异常,返回一个RST消息,然后客户端操作系统发送SIGPIPE信号杀死客户端进程;

class Task
{public:Task(const int &sockfd, const std::string &clientip, const uint16_t &clientport): sockfd_(sockfd), clientport_(clientport), clientip_(clientip) {}void run(){char buff[4096];// 数据读取ssize_t n = read(sockfd_, buff, sizeof(buff) - 1);if (n > 0){buff[n] = '\0';std::cout << "client say@ " << buff << std::endl;// 数据回显std::string echo_string;echo_string += "tcpserver say#";echo_string += buff;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_);}else{lg(Warning, "read error, sockfd: %d, clientip: %s, clientport: %d", sockfd_, clientip_.c_str(), clientport_);}close(sockfd_);}void operator()(){run();}~Task(){}private:int sockfd_;uint16_t clientport_;std::string clientip_;
};Task t(sockfd, clientip, clientport);
ThreadPool<Task>::GetInstance()->Push(t);#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include "Task.hpp"
#include <unistd.h>struct ThreadInfo
{pthread_t tid;std::string name;
};static const int defaultnum = 5;template <class T>class ThreadPool{private: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 e : _threads){if (e.tid == tid){return e.name;}}return "None";}public:T Pop(){T t = _tasks.front();_tasks.pop();return t;}void Push(const T &t){Lock();_tasks.push(t);Wakeup();UnLock();}static void *HandlerTask(void *args) // 类内函数默认都有一个this指针,静态成员函数无法直接看到成员属性{ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while (true){tp->Lock();while (tp->IsQueueEmpty()){tp->ThreadSleep();}Task 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);}}static ThreadPool<T> *GetInstance(){pthread_mutex_lock(&_smutex);if (_tp == nullptr){std::cout << "log : singleton create done first!" << std::endl;_tp = new ThreadPool<T>();}pthread_mutex_unlock(&_smutex);return _tp;}private:ThreadPool(int num = defaultnum) : _threads(num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}ThreadPool(const ThreadPool<T> &tp) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> tp) = delete;~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}std::vector<ThreadInfo> _threads;std::queue<T> _tasks;pthread_mutex_t _mutex;pthread_cond_t _cond;static ThreadPool<T> *_tp;static pthread_mutex_t _smutex;};
template <class T>ThreadPool<T> *ThreadPool<T>::_tp = nullptr;
template <class T>pthread_mutex_t ThreadPool<T>::_smutex = PTHREAD_MUTEX_INITIALIZER;

6.9线程池版翻译服务器

​ 打开KV式的字符串文件,来比较进行翻译;

#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>
#include <cstring>
#include "log.hpp"extern Log lg;const std::string filename = "./dict.txt";// 打开字典并且自动将初始化dict
class init
{public:bool split(const std::string &line, std::string &part1, std::string &part2){auto pos = line.find(sep);if (pos == std::string::npos){return false;}part1 = line.substr(0, pos);part2 = line.substr(pos + sep.size());return true;}init(){// 1.打开文件std::ifstream in(filename); // 默认打开文件if (!in.is_open()){lg(Fatal, "open %s file error, errno: %d, strerror: %s", filename.c_str(), errno, strerror(errno));exit(4);}std::string line;// 2.对文件进行按行读取while (std::getline(in, line)){std::string part1, part2;split(line, part1, part2);dict_[part1] = part2;}// 3.关闭文件in.close();}std::string translation(const std::string &key){auto it = dict_.find(key);if (it != dict_.end()){return dict_[key];}else{return "unknown";}}private:std::unordered_map<std::string, std::string> dict_;static const std::string sep;
};
const std::string init::sep = ": ";
// 处理任务
buff[n] = '\0'; // 当作字符串使用
std::string echo_string;
echo_string += it.translation(buff);
write(sockfd_, echo_string.c_str(), echo_string.size());

http://www.lryc.cn/news/328139.html

相关文章:

  • Nginx-记
  • JS面试题:call,apply,bind区别
  • Charles抓包配置代理手机连接
  • NA555、NE555、SA555和SE555系列精密定时器
  • 黑马鸿蒙笔记2
  • 微信小程序uniapp+vue3+ts+pinia的环境搭建
  • MongoDB聚合运算符:$let
  • HarmonyOS像素转换-如何使用像素单位设置组件的尺寸。
  • 【前端面试3+1】05v-if和v-show的区别、v-if和v-for能同时使用吗、Vuex是什么?【合并两个有序链表】
  • Unity WebRequest 变得简单
  • vue 窗口内容滚动到底部
  • 代码随想录算法训练营Day38|LC509 斐波那契数列LC70 爬楼梯LC746 使用最小花费爬楼梯
  • Qt5.14.2 大神的拖放艺术,优雅而强大的交互体验
  • python3将exe 转支持库错误 AssertionError: None does not smell like code
  • [EFI]Dell Inspiron 15 5567 电脑 Hackintosh 黑苹果efi引导文件
  • 大学 Python 程序设计实验报告:判断密码是否符合要求
  • 基于SpringBoot的农产品直卖平台
  • DevSecOps平台架构系列-微软云Azure DevSecOps平台架构
  • 操作系统:管程与进程通信机制解析
  • inno setup 卸载程序 删除整个安装目录
  • 【Vue3源码学习】— CH2.5 reactiveEffect.ts:Vue 3响应式系统的核心
  • K8S的mountPath和subPath
  • notepad++里安装32位和64位的16进制编辑器Hex-Editor
  • Python类的基本结构
  • 利用HIVE的窗口函数进行SQL查询中出现的问题记录
  • 更改chatglm认知
  • WPF 界面命令绑定(MVVM结构)
  • 常见手撕项目C++
  • 创建一个批处理作业来处理大量数据,例如从数据库中读取数据并进行处理
  • LeetCode 2.两数相加