【在Linux世界中追寻伟大的One Piece】Socket编程TCP

目录

1 -> TCP socket API

2 -> V1 -Echo Server

2.1 -> 测试多个连接的情况


1 -> TCP socket API

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. 端口号为SERV_PORT,定义为9999。

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 -> V1 -Echo Server

nocopy.hpp

#pragma once
#include <iostream>class nocopy
{
public:nocopy() {}nocopy(const nocopy&) = delete;const nocopy& operator = (const nocopy&) = delete;~nocopy() {}
};

TcpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "Comm.hpp"const static int default_backlog = 6; // TODO
class TcpServer : public nocopy
{
public:TcpServer(uint16_t port) : _port(port), _isrunning(false){}// 都是固定套路void Init(){// 1. 创建 socket, file fd, 本质是文件_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){lg.LogMessage(Fatal, "create socket error, errnocode: % d, error string : % s\n", errno, strerror(errno));exit(Fatal);}int opt = 1;setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR |SO_REUSEPORT, &opt, sizeof(opt));lg.LogMessage(Debug, "create socket success,sockfd: % d\n", _listensock);// 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 = htonl(INADDR_ANY);// 2.1 bindif (bind(_listensock, CONV(&local), sizeof(local)) != 0){lg.LogMessage(Fatal, "bind socket error, errnocode: % d, error string : % s\n", errno, strerror(errno));exit(Bind_Err);}lg.LogMessage(Debug, "bind socket success, sockfd: %d\n",_listensock);// 3. 设置 socket 为监听状态,tcp 特有的if (listen(_listensock, default_backlog) != 0){lg.LogMessage(Fatal, "listen socket error, errnocode: % d, error string : % s\n", errno, strerror(errno));exit(Listen_Err);}lg.LogMessage(Debug, "listen socket success,sockfd: % d\n", _listensock);}// Tcp 连接全双工通信的.void Service(int sockfd){char buffer[1024];// 一直进行 IOwhile (true){ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;std::cout << "client say# " << buffer <<std::endl;std::string echo_string = "server echo# ";echo_string += buffer;write(sockfd, echo_string.c_str(),echo_string.size());}else if (n == 0) // read 如果返回值是 0,表示读到了文件结尾(对端关闭了连接!){lg.LogMessage(Info, "client quit...\n");break;}else{lg.LogMessage(Error, "read socket error, errnocode: % d, error string : % s\n", errno, strerror(errno));break;}}}void Start(){_isrunning = true;while (_isrunning){// 4. 获取连接struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensock, CONV(&peer), &len);if (sockfd < 0){lg.LogMessage(Warning, "accept socket error, errnocode: % d, error string : % s\n", errno, strerror(errno));continue;}lg.LogMessage(Debug, "accept success, get n newsockfd: % d\n", sockfd);// 5. 提供服务, v1~v4// v1// Service(sockfd);close(sockfd);}}~TcpServer(){}
private:uint16_t _port;int _listensock; // TODObool _isrunning;
};

Comm.hpp

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>enum 
{Usage_Err = 1,Socket_Err,Bind_Err,Listen_Err
};#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)

TcpClient.hpp

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Comm.hpp"
using namespace std;void Usage(const std::string& process)
{std::cout << "Usage: " << process << " server_ip server_port"<< std::endl;
}// ./tcp_client serverip serverport
int main(int argc, char* argv[])
{if (argc != 3){Usage(argv[0]);return 1;}std::string serverip = argv[1];uint16_t serverport = stoi(argv[2]);// 1. 创建 socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){cerr << "socket error" << endl;return 1;}// 2. 要不要 bind?必须要有 Ip 和 Port, 需要 bind,但是不需要用户显示的 bind,client 系统随机端口// 发起连接的时候,client 会被 OS 自动进行本地绑定// // 2. connectstruct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);// p:process(进程), n(网络) -- 不太准确,但是好记忆inet_pton(AF_INET, serverip.c_str(), &server.sin_addr); // 1. 字符串 ip->4 字节 IP 2. 网络序列int n = connect(sockfd, CONV(&server), sizeof(server)); // 自动进行 bind 哦!if (n < 0){cerr << "connect error" << endl;return 2;}// 并没有向 server 一样,产生新的 sockfd.未来我们就用 connect 成功的sockfd 进行通信即可.while (true){string inbuffer;cout << "Please Enter# ";getline(cin, inbuffer);ssize_t n = write(sockfd, inbuffer.c_str(),inbuffer.size());if (n > 0){char buffer[1024];ssize_t m = read(sockfd, buffer, sizeof(buffer) - 1);if (m > 0){buffer[m] = 0;cout << "get a echo messsge -> " << buffer <<endl;}else if (m == 0 || m < 0){break;}}else{break;}}close(sockfd);return 0;
}

由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。

注意:

  • 客户端不是不允许调用bind(),只是没有必要显示的调用bind()固定一个端口号。否则如果在同一台机器上启动多个客户端,就会出现端口号被占用导致不能正确建立连接。
  • 服务器也不是必须调用bind(),但如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。

2.1 -> 测试多个连接的情况

再启动一个客户端,尝试连接服务器,发现第二个客户端,不能正确的和服务器进行通信。

分析原因,是因为我们accecpt了一个请求之后,就在一直while循环尝试read,没有继续调用到 accecpt,导致不能接受新的请求。

我们当前的这个TCP,只能处理一个连接,这是不科学的。


感谢各位大佬支持!!!

互三啦!!!

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

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

相关文章

基于MATLAB疲劳监测系统

MATLAB疲劳监测系统课题介绍 该课题为基于眼部和嘴部的疲劳驾驶检测。带有一个人机交互界面GUI&#xff0c;通过输入视频&#xff0c;分帧&#xff0c;定位眼睛和嘴巴&#xff0c;通过眼睛和嘴巴的张合度&#xff0c;来判别是否疲劳。 二、操作步骤 第一步&#xff1a;最好电…

NCCL安装(Ubuntu等)

目录 一、NCCL的定义二、安装NCCL的原因1、加速多GPU通信2、支持流行的深度学习框架3、提高计算效率4、易于使用和集成5、可扩展性 三、NCCL安装方法1、下载安装包2、更新APT数据库3、使用APT安装libnccl2包&#xff0c;另外&#xff0c;如果需要使用NCCL编译应用程序&#xff…

PostgreSQL的学习心得和知识总结(一百五十七)|新的 COPY 选项 LOG_VERBOSITY

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

Android 刘海屏适配指南

如果您不希望您的内容与刘海区域重叠&#xff0c; 以确保您的内容不会与状态栏及 导航栏。如果您要呈现在刘海区域中&#xff0c;请使用 WindowInsetsCompat.getDisplayCutout() 检索 DisplayCutout 对象 包含每个刘海屏的安全边衬区和边界框。借助这些 API 您需要检查视频内容…

Spring Boot 配置文件详解与最佳实践

目录 前言1. 配置文件的作用2. Spring Boot 主要配置内容2.1 Actuator 配置2.2 缓存配置2.3 核心配置2.4 数据库与数据迁移配置2.5 开发工具配置2.6 Docker Compose 配置2.7 JSON 配置2.8 安全配置 3. 多个配置文件的处理方法3.1 使用 Profile 文件区分环境3.2 结合优先级加载配…

【05-多处理器编程入门到放弃】课堂代码调试

lecture05是并发的第一节课。主要讲了入门&#xff08;两个API&#xff0c;create和join&#xff09;和放弃&#xff08;原来很自然的串行想法&#xff09; 并发线程模型最小线程库线程自问自答11思考题&#xff1a;3个T_sum线程&#xff0c;sum的结果最小是多少&#xff1f;补…

探索Python文档自动化的奥秘:揭开docxtpl库的神秘面纱

文章目录 探索Python文档自动化的奥秘&#xff1a;揭开docxtpl库的神秘面纱1. 背景介绍2. 库简介3. 安装指南4. 基础函数介绍5. 实际应用场景6. 常见问题及解决方案7. 总结 探索Python文档自动化的奥秘&#xff1a;揭开docxtpl库的神秘面纱 1. 背景介绍 在日常工作中&#xf…

计算机四级嵌入式·操作系统知识点总结(一)

页式存储管理方案:页号是地址的高位部分,页内地址是地址的低位部分。页式存储管理方案中的快表放在缓存Cache中。在页式存储管理方案中,用户使用连续的逻辑地址。在虚拟页式存储管理系统中,有效位决定是否产生缺页中断,有效位称为驻留位、存在位、中断位。 修改位表示该页…

Web大学生网页作业成品——家乡广州介绍设计与实现(HTML+CSS)(5个页面)

&#x1f389;&#x1f389;&#x1f389; 常见网页设计作业题材有**汽车、环保、明星、文化、国家、抗疫、景点、人物、体育、植物、公益、图书、节日、游戏、商城、旅游、家乡、学校、电影、动漫、非遗、动物、个人、企业、美食、婚纱、其他**等网页设计题目, 可满足大学生网…

【信息安全设计】系统安全设计方案,系统安全保护设施设计实施方案(Word原件)

1.1 总体设计 1.1.1 设计原则 1.2 物理层安全 1.2.1 机房建设安全 1.2.2 电气安全特性 1.2.3 设备安全 1.2.4 介质安全措施 1.3 网络层安全 1.3.1 网络结构安全 1.3.2 划分子网络 1.3.3 异常流量管理 1.3.4 网络安全审计 1.3.5 网络访问控制 1.3.6 完整性检查 1.…

【 纷享销客-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

Halcon-模板匹配(WPF)

halcon的代码 dev_open_window (0, 0, 512, 512, black, WindowHandle) read_image (Image, C:/Users/CF/Desktop/image.jpg) dev_display (Image)draw_rectangle1 (WindowHandle, Row1, Column1, Row2, Column2) gen_rectangle1 (Rectangle, Row1, Column1, Row2, Column2) r…

一文看懂 Stable Diffusion是什么?能做什么?

stable diffusion是一款非常强大的AI绘画软件&#xff0c;简单来说&#xff0c;Stable Diffusion&#xff08;简称SD&#xff09;就是一个AI自动生成图片的软件&#xff0c;通过我们输入文字&#xff0c;SD就能生成对应的一张图片&#xff0c;不再需要像以前一样要把图片“画”…

Linux高阶——1027—进程间关系相关

本章节介绍&#xff0c;进程间的各种关系&#xff1a;亲缘关系&#xff0c;终端进程&#xff0c;进程组&#xff0c;会话&#xff0c;孤儿进程&#xff0c;守护进程 1、亲缘关系 Linux或unix操作系统&#xff0c;进程间具备亲缘关系&#xff0c;分为强亲缘与弱亲缘 强亲缘&a…

VoxelMap论文翻译

文章目录 前言一、介绍二. 相关工作三. 方法论A. 概率平面表示1) 点 W p i {}^{W} p_{i} Wpi​ 的不确定性&#xff1a;2) 平面不确定性建模&#xff1a; B. 粗到细高效体素地图构建1) 动机&#xff1a;2) 体素地图构建&#xff1a;3) 体素地图更新&#xff1a; C. 点到平面配…

使用LangChain控制大模型的输出——解析器Parser

LangChain框架中有两个好用的工具&#xff1a; 提示词模板(PromptTemplate)用于指定LLM的输入&#xff0c;解析器(Parser)来正确解释LLM给出的输出 即&#xff1a; 提示词模板(PromptTemplate)&#xff1a;用于格式化地接受输入string变量&#xff0c;作为完整的提示词。 如 给…

从技术与市场角度看:3D 创作软件与信创系统的 “距离”

在 3D 建模与渲染这个充满创意与技术挑战的领域&#xff0c;系统安装和硬件选择是常被讨论的话题。相关的系统和硬件选项繁多&#xff0c;国内外产品水平参差不齐。当下&#xff0c;国家大力推动信创产业发展&#xff0c;国产软硬件技术也在飞速进步&#xff0c;可为何 3Dmax、…

数据结构与算法实验练习(二)(排序及线性表的应用)

数据结构与算法分析课下实验练习&#xff0c;现记录一下解答过程&#xff0c;欢迎大家批评指正。 声明&#xff1a;本题目来源于西安交通大学电信学院原盛老师&#xff0c;任何单位或个人在使用、转载或引用本题目时&#xff0c;请务必标明出处为“西安交通大学电信学院原盛老…

关于回溯与分支限界的一些介绍

这篇文章将介绍回溯算法与分支限界算法的有关概念、具体应用及代码等内容。 一、回溯法 1.1 概念 回溯法是一种试探法&#xff0c;所以它也叫试探算法。它尝试构建问题的解&#xff0c;并且在发现解不满足条件的时候撤销选择&#xff08;即“回溯”&#xff09;&#xff0c;…

【解决方案】用git reset --hard重置了提交但是发现reset了一些本不该reset的内容,是不是寄了?

使用 git reset --hard [commit_id] 命令后&#xff0c;所有的更改&#xff08;包括暂存区和工作区的更改&#xff09;都会被重置到指定的提交。如果想要撤销这个操作&#xff0c;恢复到重置之前的状态&#xff0c;可以尝试以下方法&#xff1a; 1. 使用 Git Reflog 恢复 Git…