计算机网络(3) --- 网络套接字TCP

计算机网络(2) --- 网络套接字UDP_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131977544?spm=1001.2014.3001.5501

目录

1.TCP

1.服务端接口介绍

1.listen状态

2.accept获取链接

2.客户端接口介绍

2.TCP的服务器和客户端接口实现

1.服务端

1.成员函数

2.接口

start()实现方式

1.单一执行流

2.多进程

3.多线程

4.线程池

3.main函数

2.客户端

1.成员函数

2.接口

3.mian函数

3.守护进程化

1.守护进程

2.代码


1.TCP

回顾一下UDP套接字,实现很简单,只需要初始化然后再send即可。但是TCP更加复杂

1.服务端接口介绍

1.listen状态

listen状态适用于获取先链接的,第二个参数不能填太大的int类型数据。

2.accept获取链接

 服务器只有先accept获取新链接(客户端传来的套接字),才能接受到套接字。

1.调用成功返回一个文件描述符, 失败返回-1。这个返回值其实对应的是一个套接字。

2.第一个sockfd其实是接收用的套接字,它不参与到具体的操作内;而返回值返回的套接字才是客户端返回的套接字,这个套接字是用于处理的。

3.由于tcp是面向字节流的,所以接受到的套接字就可以使用read和write进行操作了

4.如果该套接字使用完毕,一定要释放(close)套接字。因为文件描述符的本质是数组,而数组有一定的上限,我们不能只入而不释放,如果不释放会出现文件描述符泄漏。

2.客户端接口介绍

1.connect:发起链接

链接对应服务端的ip和port,表示自己要链接哪个服务端

2.TCP的服务器和客户端接口实现

1.服务端

namespace Server
{enum{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR,OPEN_ERR};static const uint16_t gport = 8080;static const int gbacklog = 5;class tcpServer{public:tcpServer(const uint16_t &port = gport): _listensock(-1), _port(port){}void initServer(){// 1.创建_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){logMessage(FATAL, "create listensock error");exit(SOCKET_ERR);}logMessage(NORMAL, "create listensock success");// 2.bindstruct 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 error");exit(BIND_ERR);}logMessage(NORMAL, "bind listensock success");// 3.设置socket,为监听状态if (listen(_listensock, gbacklog)){logMessage(FATAL, "listen error");exit(LISTEN_ERR);}logMessage(NORMAL, "listen listensock success");}void start(){for (;;){// 单线程版,只能有一个执行流,其他的无法进入执行,因为serviceIO死循环// 1.sever获取新链接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"); // accept失败不影响整体的代码,继续访问即可continue;}logMessage(NORMAL, "accept a new link success");std::cout << "sock: " << sock << std::endl;// sock是面向字节流的,后续全部都是文件操作serviceIO(sock);close(sock); //不释放会出现文件描述符泄漏}}void serviceIO(int sock){char buffer[1024];while (true){ssize_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;}}}~tcpServer(){}private:uint16_t _port;int _listensock; // 不是用来通信的,而是用于监听链接的};
}

1.成员函数

uint16_t _port:端口号

int _listensock:监听套接字

2.接口

1.initServer()创建套接字,先初始化监听套接字;生成对应的监听套接字;随后将当前的IP设置为任意IP,并且设置port最后绑定(bind)起来;随后还需要listen监听套接字

2.start()先定义新套接字,accept获取新套接字,执行套接字传输的任务。

start()实现方式

1.单一执行流
        void start(){for (;;){// 1.sever获取新链接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"); // accept失败不影响整体的代码,继续访问即可continue;}logMessage(NORMAL, "accept a new link success");std::cout << "sock: " << sock << std::endl;// sock是面向字节流的,后续全部都是文件操作serviceIO(sock);close(sock); //不释放会出现文件描述符泄漏}}

这样的缺点很明显:只有一个执行流操作服务端,也就意味着客户端只有一个能链接服务端。而且serviceIO()是一个死循环函数,那么只有客户端主动退出才能让其他客户端链接。这是串行的执行逻辑

2.多进程
1.信号版void start(){signal(SIGCHLD, SIG_IGN);for (;;){// 1.sever获取新链接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"); // accept失败不影响整体的代码,继续访问即可continue;}logMessage(NORMAL, "accept a new link success");std::cout << "sock: " << sock << std::endl;// 多进程pid_t id = fork();if (id == 0){// childclose(_listensock);serviceIO(sock);close(sock);exit(0);}close(sock);}}

1.直接忽略子进程阻塞的状态,这样操作系统会自动回收

2.只要子进程自动回收,那么父进程就不需要等待,直接不断的按照需求新建子进程,而子进程执行结束不需要管理也不会有内存泄漏问题

3.在父进程处close(sock)的做法是为了不让文件描述符泄漏,子进程的close不影响父进程,如果父进程不弄,子进程把自己的套接字关闭不代表父进程的套接字也被关闭了

2.waitpid版void start(){for (;;){// 1.sever获取新链接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"); // accept失败不影响整体的代码,继续访问即可continue;}logMessage(NORMAL, "accept a new link success");std::cout << "sock: " << sock << std::endl;// 多进程pid_t id = fork();if (id == 0){// childclose(_listensock);if (fork() > 0)exit;// 孙子进程变为孤儿进程serviceIO(sock);close(sock);exit(0);}close(sock);// fatherpid_t ret = waitpid(id, nullptr, 0); //子进程进去直接死亡回收,不需要管理if (ret > 0)std::cout << "wait success: " << ret << std::endl;}}

1.父进程先非阻塞等待

2.子进程中创建孙子进程,孙子进程执行接收的文件,子进程直接退出,让父进程回收。此时孙子进程变成孤儿进程托付给操作系统,操作系统管理孙子进程的结束。

多进程展现的问题就是消费太多资源,本来一个执行流能解决的事情却创造了调度执行流的基本单位进程来实现,而且拷贝需要时间,成本过大。

3.多线程
    class ThreadData{public:ThreadData(tcpServer *self, int sock): _self(self), _sock(sock){}public:tcpServer *_self;int _sock;};void start(){for (;;){// 1.sever获取新链接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"); // accept失败不影响整体的代码,继续访问即可continue;}logMessage(NORMAL, "accept a new link success");std::cout << "sock: " << sock << std::endl;// 多线程pthread_t tid;ThreadData *td = new ThreadData(this, sock);pthread_create(&tid, nullptr, threadRoutine, td);}}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;}

1.创造线程,让子线程进行接收执行。当然为了不需要串行执行,在threadRoutine()中,优先将当前线程进行线程分离。这样主线程就不需要回收子线程,子线程结束操作系统就会自动回收。

2.执行后需要将当前的文件描述符回收,由于函数是线程的公共资源,所以threadRoutine内就能影响到。

4.线程池
//线程池
using namespace ThreadNs;const int gnum = 10;template <class T>
class ThreadPool;template <class T>
class ThreadData
{
public:ThreadData(ThreadPool<T> *tp, const std::string &n): threadpool(tp), name(n){}public:ThreadPool<T> *threadpool;std::string name;
};template <class T>
class ThreadPool
{
private:static void *handlerTask(void *args){ThreadData<T> *td = static_cast<ThreadData<T> *>(args);while (true){T t;{// td->threadpool->lockQueue();LockGuard lockguard(td->threadpool->mutex());while (td->threadpool->isQueueEmpty()){td->threadpool->threadWait();}t = td->threadpool->pop();}// td->threadpool->unlockQueue();std::cout << td->name << "处理完了任务: " << t.toTaskString() << "并处理完成,结果是: " << t();}return nullptr;}ThreadPool(const int &_num = gnum): _num(gnum){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;public:static ThreadPool<T> *getInstance(){if (_tp == nullptr){_singleton_lock.lock();if (_tp == nullptr){_tp = new ThreadPool<Task>();}_singleton_lock.unlock();}return _tp;}// 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;}}void push(const T &in){// pthread_mutex_lock(&_mutex);LockGuard lockguard(&_mutex);_task_queue.push(in);pthread_cond_signal(&_cond);// pthread_mutex_unlock(&_mutex);}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);for (const auto &t : _threads)delete t;}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 _singleton_lock;
};template <class T>
ThreadPool<T> *ThreadPool<T>::_tp = nullptr;
static std::mutex _singleton_lock;
/void start(){// 线程池初始化ThreadPool<Task>::getInstance()->run();for (;;){// 1.sever获取新链接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"); // accept失败不影响整体的代码,继续访问即可continue;}logMessage(NORMAL, "accept a new link success");std::cout << "sock: " << sock << std::endl;// 4.线程池ThreadPool<Task>::getInstance()->push(Task(sock, serviceIO));}}

3.main函数

using namespace std;
using namespace Server;static void Usage(string proc)
{cout << "Usage:\n\t" << proc << " local_ip local_port\n\n";
}int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);std::unique_ptr<tcpServer> tsvr(new tcpServer(port));tsvr->initServer();tsvr->start();return 0;
}

1. 一旦启动,就会进入LISTEN状态

2.客户端

#define NUM 1024namespace Client
{class tcpClient{public:tcpClient(const std::string &serverip, const uint16_t &serverport): _serverip(serverip), _serverport(serverport), _sock(-1){}void initClient(){// 1.创建socket_sock = socket(AF_INET, SOCK_STREAM, 0);if (_sock < 0){std::cerr << "socket error: " << errno << " : " << strerror(errno) << std::endl;exit(2);}// 2.client要不要bind[必须要的],client要不要显示的bind,不需要// 3.客户端不需要listen// 4.也不需要accept}void start(){// 5.要发起链接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());if (connect(_sock, (struct sockaddr *)&server, sizeof server) != 0){std::cerr << "connect error: " << errno << " : " << strerror(errno) << std::endl;}else{std::string msg;while (true){std::cout << "Enter# ";std::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;std::cout << "server回显的信息: " << buffer << std::endl;}else{break;}}}}~tcpClient(){if (_sock >= 0)close(_sock);}private:int _sock;std::string _serverip;uint16_t _serverport;};
}

1.成员函数

int _sock:套接字
std::string _serverip:server的IP地址
uint16_t _serverport:server发PORT

2.接口

3.mian函数

static void Usage(string proc)
{cout << "Usage:\n\t" << proc << " server_ip server_port\n\n";
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}uint16_t serverport = atoi(argv[2]);std::string serverip = argv[1];std::unique_ptr<tcpClient> tcli(new tcpClient(serverip, serverport));tcli->initClient();tcli->start();return 0;
}

在同一个服务器运行客户端和服务端

1.ESTABLISHED:指的是客户端被服务端所接收了

2.tcp的Server查到的是服务器的链接

3.tcp的Client查到的是客户端的链接

4.两端都是全双工的

3.守护进程化

1.守护进程

1.xshell在操作系统下会生成一个会话,此时bash充当前台任务,其他都是后台任务

2.前台任务有且只能有一个,而后台任务能允许有多个

3.所有的作业可以前后台转换。先fg转换,再ctrl+Z切换后台暂停,最后bg使得暂停任务重新开始

4.这些任务可能受到用户的登录和注销的影响的,我们需要将作业自称独立会话,和终端设备无关,这样的进程为守护进程

1.setsid:将非组员的进程独立成一个会话,并且该进程变成此会话的组长

2.不能是原先就是组长的进程

/dev/null:黑洞文件,信息重定向到该文件默认数据全部清空丢弃。

2.代码

#define DEV "/dev/null"void daemonself(const char *currPath = nullptr)
{// 1.让调用进程忽略异常信号signal(SIGPIPE, SIG_IGN);// 2.如何让自己不是组长,setsidif (fork() > 0)exit(0);// 守护进程 -- 精灵进程,孤儿进程的一种pid_t n = setsid();assert(n != -1);// 3.守护进程脱离终端,关闭或者重定向以前进程默认打开文件 012int fd = open(DEV, O_RDWR);if (fd >= 0){dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);}else{close(0);close(1);close(2);}// 4.可选:进程执行路径发生变化if (currPath)chdir(currPath);
}

1.让调用进程忽略异常信号:不出现进程错误的问题使得守护过程终止

2.让自己不是组长(setsid):守护化的条件就是进程不能是会话的组长,要想不是,上面的处理方式是fork一个子进程,当前进程直接释放,使得fork的子进程变成孤儿进程,孤儿进程托付给操作系统,此时的组长是bash,那么就可以进行setsid了

3.守护进程脱离终端,关闭或者重定向以前进程默认打开文件 012:不直接关闭,将/dev/null这个黑洞文件重定向到012中。

4.可选:进程执行路径发生变化。将当前进程执行的路径换掉。

5.此时服务端的main函数逻辑为:

int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);std::unique_ptr<tcpServer> tsvr(new tcpServer(port));tsvr->initServer();// 守护进程化daemonself();tsvr->start();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/24356.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

召唤神龙打造自己的ChatGPT

在之前的两篇文章中&#xff0c;我介绍了GPT 1和2的模型&#xff0c;并分别用Tensorflow和Pytorch来实现了模型的训练。具体可以见以下文章链接&#xff1a; 1. 基于Tensorflow来重现GPT v1模型_gzroy的博客-CSDN博客 2. 花费7元训练自己的GPT 2模型_gzroy的博客-CSDN博客 有…

机器人状态估计:robot_localization 功能包使用方法

机器人状态估计&#xff1a;robot_localization 功能包基本使用 前言功能包简介基本使用数据输入与数据输出坐标系设置性能参数调试 前言 移动机器人的状态估计需要用到很多传感器&#xff0c;因为对单一的传感器来讲&#xff0c;都存在各自的优缺点&#xff0c;所以需要一种多…

简单工厂模式(Simple Factory)

简单工厂模式&#xff0c;又称为静态工厂方法(Static Factory Method)模式。在简单工厂模式中&#xff0c;可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例&#xff0c;被创建的实例通常都具有共同的父类。简单工厂模式不属于GoF的23个…

嵌入式开发学习(STC51-7-矩阵按键)

内容 按下S1-S16键&#xff0c;对应数码管最左边显示0-F 矩阵按键简介 独立按键与单片机连接时&#xff0c;每一个按键都需要单片机的一个I/O 口&#xff0c;若某单片机系统需较多按键&#xff0c;如果用独立按键便会占用过多的I/O口资源&#xff1b;而单片机 系统中I/O口资…

Maven介绍-下载-安装-使用-基础知识

Maven介绍-下载-安装-使用-基础知识 Maven的进阶高级用法可查看这篇文章&#xff1a; Maven分模块-继承-聚合-私服的高级用法 文章目录 Maven介绍-下载-安装-使用-基础知识01. Maven1.1 初识Maven1.1.1 什么是Maven1.1.2 Maven的作用 02. Maven概述2.1 Maven介绍2.2 Maven模型…

hive编译报错整理

背景 最近在修hive-1.2.0的一个bug&#xff0c;需要修改后重新打包部署到集群&#xff0c;打包的时候报下面的错误&#xff0c;原因很简单&#xff0c;从远程仓库里面已经拉不到这个包了。 org.pentaho:pentaho-aggdesigner-algorithm:jar:5.1.5-jhyde was not found in http…

Unity进阶--通过PhotonServer实现联网登录注册功能(服务器端)--PhotonServer(二)

文章目录 Unity进阶--通过PhotonServer实现联网登录注册功能(服务器端)--PhotonServer(二)服务器端大体结构图BLL层&#xff08;控制层&#xff09;DAL层&#xff08;数据控制层&#xff09;模型层DLC 服务器配置类 发送消息类 以及消息类 Unity进阶–通过PhotonServer实现联网…

HCIP——STP

STP 一、STP概述二、二层环路带来的问题1、广播风暴问题2、MAC地址漂移问题3、多帧复制 三、802.1D生成树STP的BPDU1、配置BPDU2、RPC3、COST4、配置BPDU的工作过程5、TCN BPDU6、TCN BPDU的工作原理 四、STP的角色五、STP角色选举六、STP的接口状态七、接口状态的迁移八、STP的…

minio-分布式文件存储系统

minio-分布式文件存储系统 minio的简介 MinIO基于Apache License v2.0开源协议的对象存储服务&#xff0c;可以做为云存储的解决方案用来保存海量的图片&#xff0c;视频&#xff0c;文档。由于采用Golang实现&#xff0c;服务端可以工作在Windows,Linux, OS X和FreeBSD上。配置…

线程间的同步、如何解决线程冲突与死锁

一、线程同步概念&#xff1a; 线程同步是指在多线程编程中&#xff0c;为了保证多个线程之间的数据访问和操作的有序性以及正确性&#xff0c;需要采取一些机制来协调它们的执行。在多线程环境下&#xff0c;由于线程之间是并发执行的&#xff0c;可能会出现竞争条件&#xf…

RadioButton基本使用

作用&#xff1a;单选框&#xff0c;一般用于设置或者选择某项任务。 常用属性&#xff1a; 常用事件&#xff1a; 选中事件 后台代码&#xff1a; private void radioButton1_CheckedChanged(object sender, EventArgs e){if (radioButton1.Checked){MessageBox.Show(radioB…

AcWing 4310:树的DFS ← vector、auto、邻接表

【题目来源】https://www.acwing.com/problem/content/description/4313/【题目描述】 给定一棵 n 个节点的树。 节点的编号为 1∼n&#xff0c;其中 1 号节点为根节点&#xff0c;每个节点的编号都大于其父节点的编号。 现在&#xff0c;你需要回答 q 个询问。 每个询问给定两…

企业内网终端安全无客户端准入控制技术实践

终端无代理/无客户端准入控制技术因其良好的用户体验而倍受创新企业的青睐。无代理/无客户端准入控制技术&#xff0c;顾名思义&#xff0c;是一种在网络中对终端实施访问控制的方法&#xff0c;无需依赖特定的客户端软件。 不同于银行、医院等传统行业的终端准入控制需求&…

摄像头电池组和平衡车电池组

摄像头电池组 Wh~是电量 Wh V*Ah 毫安(mA)~是电流 电量是9.62Wh&#xff0c;电压是 3.7v 9.62 wh / 3.7v 2.6 Ah 2600mAH 4个并联电池&#xff1a;10400mAH / 4 2600mAH PH2.0mm-2Pins 平衡车 72 wh / 36v 2 Ah 2000mAH 对比自己买的单粒电池 vs 摄像头和平衡车的 …

java中io流、属性集Properties、缓冲流、转换流、序列化和反序列化、打印流、网络编程(TCP通信程序、文件复制案例、文件上传案例、B/S服务案例)

IO流&#xff1a; io流中i表示input输入&#xff0c;o表示output输出&#xff0c;流表示数据&#xff08;字符&#xff0c;字节&#xff0c;1个字符2个字节8个位&#xff09;&#xff1b;这里的输入输出是以内存为基础&#xff0c;将数据从内存中输出到硬盘的过程称为输出&…

【JavaEE初阶】博客系统后端

文章目录 一. 创建项目 引入依赖二. 设计数据库三. 编写数据库代码四. 创建实体类五. 封装数据库的增删查改六. 具体功能书写1. 博客列表页2. 博客详情页3. 博客登录页4. 检测登录状态5. 实现显示用户信息的功能6. 退出登录状态7. 发布博客 一. 创建项目 引入依赖 创建blog_sy…

ffmpeg.c源码与函数关系分析

介绍 FFmpeg 是一个可以处理音视频的软件&#xff0c;功能非常强大&#xff0c;主要包括&#xff0c;编解码转换&#xff0c;封装格式转换&#xff0c;滤镜特效。FFmpeg支持各种网络协议&#xff0c;支持 RTMP &#xff0c;RTSP&#xff0c;HLS 等高层协议的推拉流&#xff0c…

基于Java+SpringBoot+SpringCloud+Vue的智慧养老平台设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

Opencv-C++笔记 (15) : 像素重映射 与 图像扭曲

文章目录 一、重映射简介二、图像扭曲 一、重映射简介 重映射&#xff0c;就是把一幅图像中某位置的像素放置到另一图像指定位置的过程。即&#xff1a; 在重映射过程中&#xff0c;图像的大小也可以同时发生改变。此时像素与像素之间的关系就不是一一对应关系&#xff0c;因…

TCP Socket 基础知识点(实例是以Java进行演示)

本篇根据TCP & Socket 相关知识点和学习所得进行整理所得。 文章目录 前言1. TCP相关知识点1.1 双工/单工1.2 TCP协议的主要特点1.3 TCP的可靠性原理1.4 报文段1.4.1 端口1.4.2 seq序号1.4.3 ack确认号1.4.4 数据偏移1.4.5 保留1.4.6 控制位1.4.7 窗口1.4.8 校验和1.4.9 紧…