【计算机网络学习之路】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.mzph.cn/news/161597.shtml

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

相关文章

Oracle的控制文件多路复用,控制文件备份,控制文件手工恢复

一.配置控制文件多路复用 1.查询Oracle的控制文件所在位置 SQL> select name from v$controlfile;NAME -------------------------------------------------------------------------------- /u01/app/oracle/oradata/orcl/control01.ctl /u01/app/oracle/fast_recovery_a…

【docker】docker总结

一、Docker简介 Docker是开源应用容器引擎&#xff0c;轻量级容器技术。基于Go语言&#xff0c;并遵循Apache2.0协议开源Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的Linux系统上&#xff0c;也可以实现虚拟化容…

No matching variant of com.android.tools.build:gradle:7.4.2 was found.

一、报错信息 创建个新项目&#xff0c;运行直接报错&#xff0c;信息如下&#xff1a; No matching variant of com.android.tools.build:gradle:7.4.2 was found. The consumer was configured to find a runtime of a library compatible with Java 8, packaged as a jar,…

shell 条件语句

目录 测试 test测试文件的表达式 是否成立 格式 选项 比较整数数值 格式 选项 字符串比较 常用的测试操作符 格式 逻辑测试 格式 且 &#xff08;全真才为真&#xff09; 或 &#xff08;一真即为真&#xff09; 常见条件 双中括号 [[ expression ]] 用法 &…

关于一些bug的解决1、el-input的输入无效2、搜索之后发现数据不对3、el多选框、单选框点击无用4、

el-input输入无效 原来的代码是 var test null 但是我发现不能输入任何值 反倒修改test的初始值为123是可以的 于是我确定绑定没问题 就是修改的问题 于是改成 var test ref&#xff08;&#xff09; v-model绑定的值改成test.value就可以了 因为ref是相应式的 可以通过输入…

Go语言多线程爬虫万能模板它来了!

对于长期从事爬虫行业的技术员来说&#xff0c;通过技术手段实现抓取海量数据并且做到可视化处理&#xff0c;我在想如果能写一个万能的爬虫模板&#xff0c;后期遇到类似的工作只要套用模板就能解决大部分的问题&#xff0c;如此提高工作效率何乐而不为&#xff1f; 以下是一个…

Mac自带的看图如何连续查看多张图片

一、问题 mac看访达里的图片时&#xff0c;双击打开一张图片&#xff0c;然后按上下左右键都没法切换到另外的图片。而且也没找到像window一样单击缩略图可以看到预览图。其实是自己不懂得怎么使用&#xff0c;哈哈哈&#x1f602; 二、方法 2.1、图标方式 可以看到缩略图&a…

新的centos7.9安装jenkins(二)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 接上一节文章。 这个版本默认git也安装好了&#xff0c;所以全局配置这个不需要了。 maven安装3.9.3版本…

前缀和——DP35 【模板】二维前缀和

文章目录 &#x1f34e;1. 题目&#x1f352;2. 算法原理&#x1f345;3. 代码实现 &#x1f34e;1. 题目 题目链接&#xff1a;【模板】二维前缀和_牛客题霸_牛客网 (nowcoder.com) 描述 给你一个 n 行 m 列的矩阵 A &#xff0c;下标从1开始。 接下来有 q 次查询&#xff0…

【OpenCV实现图像:使用OpenCV生成拼图效果】

文章目录 概要通用配置不考虑间隔代码实现考虑间隔代码实现小结 概要 概要&#xff1a; 拼图效果是一种将图像切割为相邻正方形并重新排列的艺术效果。在生成拼图效果时&#xff0c;可以考虑不同的模式&#xff0c;包括是否考虑间隔和如何处理不能整除的部分。 不考虑间隔&a…

【NLP】GPT 模型如何工作

介绍 2021 年&#xff0c;我使用 GPT 模型编写了最初的几行代码&#xff0c;那时我意识到文本生成已经达到了拐点。我要求 GPT-3 总结一份很长的文档&#xff0c;并尝试了几次提示。我可以看到结果比以前的模型先进得多&#xff0c;这让我对这项技术感到兴奋&#xff0c;并渴望…

HQL刷题 50道

HQL刷题 50道 尚硅谷HQL刷题网站 答案 1.查询累积销量排名第二的商品 select sku_id from (select sku_id, dense_rank() over (order by total desc) rnfrom (select sku_id, sum(sku_num) totalfrom order_detailgroup by sku_id) t1) t2 where rn 2;2.查询至少连续三天下…

php 时区查看和设置

php的时区&#xff0c;关系到相关时间函数的结果 其他相关&#xff1a; linux时区设置&#xff1a;链接 pgsql时区设置&#xff1a; 一、查看可以用的时区列表 新建一个php文件&#xff0c;输入下面程序即可 <?php echo "<pre>"; var_dump(timezone_id…

IOS+Appium+Python自动化全实战教程

由于公司的产品坐落于不同的平台&#xff0c;如ios、mac、Android、windows、web。因此每次有新需求的时候&#xff0c;开发结束后&#xff0c;留给测试的时间也不多。此外&#xff0c;一些新的功能实现&#xff0c;偶尔会影响其他的模块功能正常的使用。 网上的ios自动化方面的…

计算机网络之物理层(数据通信有关)

一、概述 1.1物理层引入的目的 屏蔽掉传输介质的多样性&#xff0c;导致数据传输方式的不同&#xff1b;物理层的引入使得高层看到的数据都是统一的0,1构成的比特流 1.2.物理层如何实现屏蔽 物理层靠定义的不同的通信协议&#xff08;一般称通信规程&#xff09; 这些协议…

基于高质量训练数据,GPT-4 Turbo更出色更强大

11月7日消息&#xff0c;OpenAI在首届开发者大会上正式推出了GPT-4 Turbo。 与GPT-4相比&#xff0c;GPT-4 Turbo主要有6方面的提升&#xff1a; 1、扩展下文对话长度&#xff1a;GPT4最大只能支持8k的上下文长度&#xff08;约等于6000个单词&#xff09;&#xff0c;而GPT-4…

智能小车速通版——手把手教程

考虑到大部分学校&#xff0c;会发放简易小车来作为智能车初期培训和筛选的工具&#xff0c; 于是&#xff0c;我写一个简单的教程&#xff0c;能够实现简单小车的电磁循迹。 通过这个教程&#xff0c;能够通过简化的步骤搭建寻迹小车&#xff0c;进而了解整个智能车是如何实…

Redis-Redis持久化,主从哨兵架构详解

Redis持久化 RDB快照&#xff08;snapshot&#xff09; 在默认情况下&#xff0c; Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中。 你可以对 Redis 进行设置&#xff0c; 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时&#xff0c; 自动保存一次数…

【操作系统】I/O软件层次结构

文章目录 1. 前言2. I/O软件层次结构2.1 用户层软件2.2 设备独立性软件2.3 设备驱动程序2.4 中断处理程序 1. 前言 偶然看到“程序员的护城河是什么”这个话题&#xff0c;作为一个工作两年多的程序员吧&#xff0c;经常看到网上关于各种35岁危机、裁员甚至猝死之云云。最近也…

modbus协议及modbus TCP协议

一、Modbus协议 1.起源 Modbus由Modicon公司于1979年开发&#xff0c;是一种工业现场总线协议标准。 Modbus通信协议具有多个变种&#xff0c;其中有支持串口&#xff0c;以太网多个版本&#xff0c;其中最著名的是Modbus RTU&#xff08;通信效率最高&#xff0c;基于串口&am…