【计算机网络学习之路】TCP socket编程

文章目录

  • 前言
  • 一. 服务器
    • 1. 初始化服务器
    • 2. 启动服务器
  • 二. 客户端
  • 三. 多进程服务器
  • 结束语

前言

本系列文章是计算机网络学习的笔记,欢迎大佬们阅读,纠错,分享相关知识。希望可以与你共同进步。

本篇博客基于UDP socket基础,介绍TCP socket编程接口和细节

UDP socket编程可参看【计算机网络学习之路】UDP socket编程

本次编写的服务器和客户端依然是最简单的echo服务器

一. 服务器

服务器的基本框架:

tcp_server.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>namespace ns_server
{const uint16_t default_port = 8888;class TcpServer{public:TcpServer(uint16_t port = default_port) : _port(port){}void InitServer(){}//初始化服务器void Start(){}//启动服务器~TcpServer(){}private:int _sock; // 监听套接字uint16_t _port;  // 端口号};
}

tcp_server.cc

#include"tcp_server.hpp"
#include<memory>using namespace std;
using namespace ns_server;static void usage(char*argv)
{cout<<"Usage\n\t"<<argv<<" serverPort"<<endl;
}
int main(int argc,char*argv[])
{if(argc!=2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);unique_ptr<TcpServer> usvr(new TcpServer(echo,port));usvr->InitServer();usvr->Start();return 0;
}

1. 初始化服务器

服务器的初始化,还是一样的

  1. 创建套接字
  2. 绑定套接字
 void InitServer()
{// 1.创建套接字_sock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){std::cerr << "create sock error," << strerror(errno) << std::endl;exit(1);}std::cout << "create listensock success: " << _sock << std::endl;// 2.绑定套接字struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;if (bind(_sock , (struct sockaddr *)&local, sizeof(local)) < 0){std::cerr << "bind error," << strerror(errno) << std::endl;exit(2);}
}

需要注意的是,socket的第二个参数为SOCK_STREAM面向字节流

TCP与UDP不同的地方是,TCP是面向连接的,UDP是无连接的
所以TCP还需要listen

在这里插入图片描述

返回值:成功返回0,失败返回-1并设置错误码

backlog参数需要在后续TCP详解中学习,先定义大小为32

const int backlog = 32;
void InitServer()
{// 1.创建套接字_sock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){std::cerr << "create sock error," << strerror(errno) << std::endl;exit(1);}std::cout << "create listensock success: " << _sock << std::endl;// 2.绑定套接字struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;if (bind(_sock , (struct sockaddr *)&local, sizeof(local)) < 0){std::cerr << "bind error," << strerror(errno) << std::endl;exit(2);}// 3.监听if (listen(_listensock, backlog) < 0){std::cerr << "listen error," << strerror(errno) << std::endl;exit(3);}
}

初始化到此就结束了
接下来是启动服务器

2. 启动服务器

TCP通过accept获取客户端连接

在这里插入图片描述

  • sockfd:socket返回的文件描述符
  • addr:输入输出型参数,客户端信息的结构体
  • addrlen:输入输出型参数,结构体大小。注意:需要传入addr的大小
  • 返回值是网络文件描述符

在TCP中,socket返回的网络文件可以理解为连接文件,内部保存了连接信息
而accept是从连接文件中获取连接,然后创建套接字,网络文件。
真正通信的是connect创建的网络文件

我们将私有成员的_sock改为_listensock

void Start()
{while (true){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sock = accept(_listensock, (struct sockaddr *)&client, &len);if (sock < 0){std::cerr << "accept error" << std::endl;continue;}// 提取客户端信息std::string clientIp = inet_ntoa(client.sin_addr);uint16_t clientPort = ntohs(client.sin_port);std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";std::cout << "create sock " << sock << " from " << _listensock << std::endl;}
}

接下来就可以在connect返回的套接字中读写数据了。
本次使用readwrite

void Start()
{while (true){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sock = accept(_listensock, (struct sockaddr *)&client, &len);if (sock < 0){std::cerr << "accept error" << std::endl;continue;}// 提取客户端信息std::string clientIp = inet_ntoa(client.sin_addr);uint16_t clientPort = ntohs(client.sin_port);std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";std::cout << "create sock " << sock << " from " << _listensock << std::endl;char buffer[1024];while (true){int n = read(sock, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';std::cout << name << "# " << buffer << std::endl;std::string responce = buffer;//返回收到的数据int m = write(sock, responce.c_str(), responce.size());}else if (n == 0){// 写端关闭std::cout << name << " quit,me to" << std::endl;close(sock);break;}else{// 读数据异常std::cerr << "read error" << std::endl;break;}}}
}

完整代码:
tcp_server.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>namespace ns_server
{const uint16_t default_port = 8888;const int backlog = 32;class TcpServer{public:TcpServer(func_t func, uint16_t port = default_port) : _port(port), _func(func){}void InitServer(){// 1.创建套接字_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){std::cerr << "create sock error," << strerror(errno) << std::endl;exit(1);}std::cout << "create listensock success: " << _listensock << std::endl;// 2.绑定套接字struct sockaddr_in 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){std::cerr << "bind error," << strerror(errno) << std::endl;exit(2);}// 3.监听if (listen(_listensock, backlog) < 0){std::cerr << "listen error," << strerror(errno) << std::endl;exit(3);}}void Start(){while (true){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sock = accept(_listensock, (struct sockaddr *)&client, &len);if (sock < 0){std::cerr << "accept error" << std::endl;continue;}// 提取客户端信息std::string clientIp = inet_ntoa(client.sin_addr);uint16_t clientPort = ntohs(client.sin_port);std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";std::cout << "create sock " << sock << " from " << _listensock << std::endl;char buffer[1024];while (true){int n = read(sock, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';std::cout << name << "# " << buffer << std::endl;std::string responce = buffer;int m = write(sock, responce.c_str(), responce.size());}else if (n == 0){// 写端关闭std::cout << name << " quit,me to" << std::endl;close(sock);break;}else{// 读数据异常std::cerr << "read error" << std::endl;break;}}}}~TcpServer(){}private:int _listensock; // 监听套接字uint16_t _port;  // 端口号};
}

PS:上述的服务器是单进程,所以只能同时处理一个客户端,读者可以尝试添加一下多进程,多线程或者线程池

本篇博客最后会贴出多进程的方案

二. 客户端

客户端就不作封装了

最开始也是要创建套接字
然后TCP的客户端需要connect服务器

在这里插入图片描述

  • sockfd:socket返回的文件描述符
  • addr:服务器信息的结构体
  • addrlen:结构体大小。
  • 返回值:成功返回0,失败返回-1并设置错误码

注意:connect时OS会bind客户端
UDP是在发送数据时才会bind

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;static void usage(char *argv)
{cout << "Usage:\n\t" << argv << " serverIp serverPort" << endl;
}int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(USAGE_ERR);}string serverIp = argv[1];uint16_t serverPort = atoi(argv[2]);// 1.创建套接字int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){cerr << "create sock error," << strerror(errno) << endl;exit(SOCKET_ERR);}cout<<"create sock sucess:"<<sock<<endl;// 2. 连接struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverIp.c_str());server.sin_port = htons(serverPort);int cnt = 5; // 记录重连次数// connect时会bindwhile (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0){cout << "正在重连...还有" << cnt-- << "次" << endl;if (cnt <= 0){   cerr<<"连接失败"<<endl;exit(CONNECT_ERR);}sleep(1);}// 连接成功string name = "["+serverIp + ":" + to_string(serverPort)+"]";cout << "connect " << name << " sucess" << endl;return 0;
}

然后也可以开始读写数据了

完整代码:

tcp_client.cc

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;static void usage(char *argv)
{cout << "Usage:\n\t" << argv << " serverIp serverPort" << endl;
}int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(USAGE_ERR);}string serverIp = argv[1];uint16_t serverPort = atoi(argv[2]);// 1.创建套接字int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){cerr << "create sock error," << strerror(errno) << endl;exit(SOCKET_ERR);}cout<<"create sock sucess:"<<sock<<endl;// 2. 连接struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverIp.c_str());server.sin_port = htons(serverPort);int cnt = 5; // 记录重连次数// connect时会bindwhile (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0){cout << "正在重连...还有" << cnt-- << "次" << endl;if (cnt <= 0){   cerr<<"连接失败"<<endl;exit(CONNECT_ERR);}sleep(1);}// 连接成功string name = "["+serverIp + ":" + to_string(serverPort)+"]";cout << "connect " << name << " sucess" << endl;// 发送消息while (true){cout << "please enter your message# ";string message;getline(cin, message);int n = write(sock, message.c_str(), message.size());if (n < 0){cerr << "write error," << strerror(errno) << endl;break;}else if (n == 0){cout << "读端关闭,停止写" << endl;break;}char buffer[1024];int m = read(sock, buffer, sizeof(buffer) - 1);if (m > 0){buffer[n] = '\0';cout<<name<<" echo "<<buffer<<endl;}else if (m == 0){// 写端关闭std::cout << name << " quit,me to" << std::endl;close(sock);break;}else{// 读数据异常std::cerr << "read error" << std::endl;break;}}return 0;
}

三. 多进程服务器

tcp_server.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>namespace ns_server
{const uint16_t default_port = 8888;const int backlog = 32;class TcpServer{public:TcpServer(uint16_t port = default_port) : _port(port){}void InitServer(){// 1.创建套接字_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){std::cerr << "create sock error," << strerror(errno) << std::endl;exit(1);}std::cout << "create listensock success: " << _listensock << std::endl;// 2.绑定套接字struct sockaddr_in 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){std::cerr << "bind error," << strerror(errno) << std::endl;exit(2);}// 3.监听if (listen(_listensock, backlog) < 0){std::cerr << "listen error," << strerror(errno) << std::endl;exit(3);}}void Start(){//忽略子进程的信号,不需要等待子进程退出(推荐!!!)signal(SIGCHLD,SIG_IGN);while (true){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sock = accept(_listensock, (struct sockaddr *)&client, &len);if (sock < 0){std::cerr << "accept error" << std::endl;continue;}std::cout << "create sock " << sock << " from " << _listensock << std::endl;// 多进程pid_t id = fork();if (id < 0){close(sock);continue;}else if (id == 0){//子进程close(_listensock);//建议关掉不需要的fdif(fork()>0)exit(0);//子进程退掉,后续为孙子进程// 提取客户端信息std::string clientIp = inet_ntoa(client.sin_addr);uint16_t clientPort = ntohs(client.sin_port);service(sock, clientIp, clientPort);exit(0);}//父进程//一定关掉不需要的fd,防止fd泄露close(sock);//pid_t ret=waitpid(id,nullptr,0);//默认为阻塞等待//pid_t ret=waitpid(id,nullptr,WNOHANG);//非阻塞//if(ret==id) std::cout<<"wait "<<id<<" sucess"<<std::endl;}}void service(int sock, std::string &clientIp, uint16_t&clientPort){std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";char buffer[1024];while (true){int n = read(sock, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';std::cout << name << "# " << buffer << std::endl;std::string responce = buffer;int m = write(sock, responce.c_str(), responce.size());}else if (n == 0){// 写端关闭std::cout << name << " quit,me to" << std::endl;close(sock);break;}else{// 读数据异常std::cerr << "read error" << std::endl;break;}}}~TcpServer(){}private:int _listensock; // 监听套接字uint16_t _port;  // 端口号};
}

结束语

本篇博客到此结束,感谢看到此处。
欢迎大家纠错和补充
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

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

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

相关文章

企业建数仓的第一步是选择一个好用的ETL工具

当企业决定建立数据仓库&#xff08;Data Warehouse&#xff09;&#xff0c;第一步就是选择一款优秀的ETL&#xff08;Extract, Transform, Load&#xff09;工具。数据仓库是企业数据管理的核心&#xff0c;它存储、整合并管理各种数据&#xff0c;为商业决策和数据分析提供支…

模电知识点总结(二)二极管

系列文章目录 文章目录 系列文章目录二极管二极管电路分析方法理想模型恒压降模型折线模型小信号模型高频/开关 二极管应用整流限幅/钳位开关齐纳二极管变容二极管肖特基二极管光电器件光电二极管发光二极管激光二极管太阳能电池 二极管 硅二极管&#xff1a;死区电压&#xf…

从零开始的c语言日记day36——指针进阶

一、什么是指针: 指针的概念:1.指针就是个变量&#xff0c;用来存放地址&#xff0c;地址唯一标识一块内存空间。 ⒉指针的大小是固定的4/8个字节(32位平台/64位平台)。 指针是有类型&#xff0c;指针的类型决定了指针的-整数的步长&#xff0c;指针解引用操作的时候的权限。…

RTS 客户端-服务器网络

Stone Monarch 从一开始就支持多人游戏&#xff0c;但随着时间的推移&#xff0c;网络模型经历了多次迭代。我最初基于这篇著名的帝国时代文章实现了点对点锁步模型。 点对点锁定步骤有一些众所周知的问题。点对点方面使玩家很难相互连接&#xff0c;并增加了每个新玩家的网络…

spring boot 热部署

相信小伙伴们在日常的开发中&#xff0c;调试代码时&#xff0c;免不了经常修改代码&#xff0c;这个时候&#xff0c;为了验证效果&#xff0c;必须要重启 Spring Boot 应用。 频繁地重启应用&#xff0c;导致开发效率降低&#xff0c;加班随之而来。有没有什么办法&#xff0…

UEC++ day8

伤害系统 给敌人创建血条 首先添加一个UI界面用来显示敌人血条设置背景图像为黑色半透明 填充颜色 给敌人类添加两种状态表示血量与最大血量&#xff0c;添加一个UWidegtComponet组件与UProgressBar组件 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category "Enemy …

浏览器没收到返回,后端也没报错,php的json_encode问题bug

今天网站遇到个问题&#xff0c;后端返回异常&#xff0c;但是浏览器状态码200&#xff0c;但是看不到结果。经过排查发现&#xff0c;我们在返回结果的时候使用了json_encode返回给前端&#xff0c;结果里面的字符编码异常&#xff0c;导致json_encode异常&#xff0c;但是php…

禁止linux shell 终端显示完整工作路径,如何让linux bash终端不显示当前工作路径

在操作linux时&#xff0c;默认安装的linux终端会显示当前完整的工作目录&#xff0c;如果目录比较短还是可以接收&#xff0c;如果目录比较长&#xff0c;就显得比较别扭&#xff0c;操作起来不方便&#xff0c;因此需要关闭这种功能。 要关闭这个功能&#xff0c;请按如下步骤…

生命周期评估(LCA)与SimaPro碳足迹分析

SimaPro提供最新的科学方法和数据库以及丰富的数据&#xff0c;使您可以收集和评估产品和流程的环境绩效。通过这种方式&#xff0c;您可以将改变公司产品生命周期的想法提交给您的同事&#xff0c;以便阐明您的业务未来。 SimaPro软件的特点和功能&#xff1a; 完全控制产品生…

供应链和物流的自动化新时代

今天&#xff0c;当大多数人想到物流自动化时&#xff0c;他们会想到设备。机器人、无人机和自主卡车运输在大家的谈话中占主导地位。全自动化仓库的视频在网上流传&#xff0c;新闻主播们为就业问题绞尽脑汁。这种炒作是不完整的&#xff0c;它错过了供应链和物流公司的机会。…

基于安卓android微信小程序的刷题系统

项目介绍 面试刷题系统的开发过程中&#xff0c;采用B / S架构&#xff0c;主要使用jsp技术进行开发&#xff0c;中间件服务器是Tomcat服务器&#xff0c;使用Mysql数据库和Eclipse开发环境。该面试刷题系统包括会员、答题录入员和管理员。其主要功能包括管理员&#xff1a;个…

selenium 简单案例 <批量下载文件> <网页自动化点击上报>

一、批量下载文件 网页分析 点击跳转到下载页面 from selenium import webdriver import timedef get_link_list():# 创建浏览器对象driver webdriver.Chrome(executable_pathrC:\Users\nlp_1\Desktop\chromedriver\chromedriver-win32\chromedriver.exe)url https://www…

几款Java源码扫描工具(FindBugs、PMD、SonarQube、Fortify、WebInspect)

说明 有几个常用的Java源码扫描工具可以帮助您进行源代码分析和检查。以下是其中一些工具&#xff1a; FindBugs&#xff1a;FindBugs是一个静态分析工具&#xff0c;用于查找Java代码中的潜在缺陷和错误。它可以检测出空指针引用、资源未关闭、不良的代码实践等问题。FindBu…

从Github登录的双因子验证到基于时间戳的一次性密码:2FA、OTP与TOTP

Github于2023-03-09推出一项提高软件安全标准的措施&#xff0c;所有在Github上贡献过代码的开发人员在年底前必须完成 2FA&#xff08;Two-factory authentication&#xff0c;双因子认证&#xff09;。初听此事之时&#xff0c;不以为意&#xff0c;因为自己之前就知道双因子…

再探MDG cloud-ready模式!看未来MDG的发展路线

紧跟上一篇博客&#xff0c;我们将更加深入探讨一些MDG Cloud-Ready模式的相关内容。 背景 在2021年9月&#xff0c;Harald Kuck&#xff0c;SAP ABAP Platform老大&#xff0c;介绍了未来ABAP的发展路线&#xff0c;并最终在一年后正式推出了ABAP Cloud。他在会上是这么说的…

担忧CentOS停服?KeyarchOS系统来支撑

担忧CentOS停服&#xff1f;KeyarchOS系统来支撑 近年发生的“微软黑屏门”、“微软操作系统停更”等安全事件&#xff0c;敲响了我国 IT 产业的警钟&#xff0c;建立由我国主导的 IT 产业生态尤为迫切。对此&#xff0c;我国信息技术应用创新行业乘势而起&#xff0c;旨在通过…

基于单片机的光伏发电并网系统设计(论文+源码)

1.系统设计 片作为主控制器。由于太阳能板本身的能量输出受到负载影响&#xff0c;因此需要在太阳能板后面加入一级DC/DC电路&#xff0c;来实现最大功率跟踪&#xff0c;以提高整个系统的效率。接着&#xff0c;由于光伏逆变器需要产生220V的交流电给居民使用&#xff0c;因此…

子虔与罗克韦尔自动化合作 进博会签约自动化净零智造联创中心

11月6日进博会现场&#xff0c;漕河泾罗克韦尔自动化净零智造联创中心合作协议签约暨合作伙伴&#xff08;第一批&#xff09;授牌仪式举办&#xff0c;子虔科技作为联创中心合作伙伴签约&#xff0c;携手共建智能制造&#xff0c;引领行业可持续发展。 图示&#xff1a;子虔科…

BMS基础知识:BMS基本功能,铅酸和锂电池工作原理,电池系统的重要概念!

笔者有话说&#xff1a; 作为BMS从业者来讲&#xff0c;目前接触的BMS系统并不是很高大尚&#xff0c;但基础功能都是有的。 关于BMS的基本功能&#xff0c;工作原理&#xff0c;运行逻辑等&#xff0c;在此做一个梳理&#xff0c;讲一些最基础的扫盲知识&#xff0c;可以作为…

计算机网络:数据链路层

0 本节主要内容 问题描述 解决思路 1 问题描述 数据链路层主要面临四个问题&#xff1a; 封装成帧&#xff1b;透明传输&#xff1b;差错检测&#xff1b;实现相邻节点之间的可靠通信。 1.1 子问题1&#xff1a;封装成帧 怎么知道数据从哪里开始&#xff1f;到哪里结束&a…