【网络】套接字 -- TCP

news2024/11/28 5:34:43

🥁作者华丞臧.
📕​​​​专栏:【网络】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站


文章目录

  • 一、TCP协议
    • 1.1 socket 常见API
      • listen
      • accept
      • connect
    • 1.2 TCP服务器
      • init()
      • start()
    • 1.3 TCP客户端
    • 1.4 测试


一、TCP协议

对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识:

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

1.1 socket 常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
  • socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;
  • 应用程序可以像读写文件一样用read/write在网络上收发数据;
  • 如果socket()调用出错则返回-1;
  • 对于IPv4, family参数指定为AF_INET;
  • 对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议
  • protocol参数的介绍从略,指定为0即可。
// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
  • 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号;
  • bind()成功返回0,失败返回-1。
  • bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听
  • myaddr所描述的地址和端口号;
  • 前面讲过,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;

程序中对 sockaddr 参数是这样初始化的:

#include <netinet/in.h>
#include <arpa/inet.h>

// 2.填充域
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
  1. 将整个结构体清零;
  2. 设置地址类型为AF_INET;
  3. 网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;
  4. 端口号为SERV_PORT, 我们定义为9999。

listen

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
  • listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5);
  • listen()成功返回0,失败返回-1。

accept

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
  • 三次握手完成后, 服务器调用accept()接受连接;
  • 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr是一个传出参数,accept()返回时传出客户端的地址和端口号;
  • 如果给addr 参数传NULL,表示不关心客户端的地址;
  • addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度
  • 以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);

connect

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 客户端需要调用connect()连接服务器;
  • connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
  • connect()成功返回0,出错返回-1;

1.2 TCP服务器

在这里插入图片描述

#include <iostream>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "Log.hpp"

volatile bool quitSer = false;

void Usage(void *vgs)
{
    std::cout << "Usage:./tcpserver port ip" << std::endl;
}
class server
{
public:
    server(int port, std::string ip = "")
        : sockfd_(-1)
        , ip_(ip)
        , port_(port)
    {
    }
    ~server()
    {
    }

public:
    void init()
    {}
    void start()
    {}

private:
    int sockfd_;
    uint16_t port_;
    std::string ip_;
};

// ./tcpserver port ip
int main(int argc, char *argv[])
{
    if (argc < 2 || argc > 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
        ip = argv[2];

    server tcpSer(port, ip);
    tcpSer.init();
    tcpSer.start();
    return 0;
}

init()

TCP套接字服务器端的 init() ,与UDP类似,只不过多了最后一步监听套接字,TCP套接字设置步骤如下:

在这里插入图片描述

void init()
{
    // 1. 创建套接字
    sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd_ < 0)
    {
        logMessage(FATAL, "socket:%s[%d]", strerror(errno), sockfd_);
        exit(SOCK_ERR);
    }
    logMessage(DEBUG, "socket success..");

    // 2.填充域
    struct sockaddr_in local;
    bzero(&local, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(port_);
    ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));

    // 3. 绑定网络信息
    if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
    {
        logMessage(FATAL, "bind:%s[%d]", strerror(errno), sockfd_);
        exit(BIND_ERR);
    }
    logMessage(DEBUG, "bind success...");

    // 4. 监听套接字
    // 为什么要监听套接字? 因为TCP是面向连接的,在任何时候都可能请求连接 
    if (listen(sockfd_, 5) < 0)
    {
        logMessage(FATAL, "listen:%s[%d]", strerror(errno), sockfd_);
        exit(LISTEN_ERR);
    }
    logMessage(DEBUG, "listen success...");

    // 完成
}

start()

在TCP套接字提供服务之前,需要使用accept获取连接,accept返回值是一个新的套接字文件描述符,使用该套接字文件描述符来进行网络通信。start()是server提供服务的接口,因此该函数必须是一个死循环(服务器都是在一个死循环当中)以给用户提供持续的服务;在start函数中,主要完成接收用户发送的消息并且将消息提取出来,其主要步骤如下图:
在这里插入图片描述

void start()
{
    char inbuffer_[1024]; // 用来接收客户端发来的消息
    // 提供服务
    while (true)
    {
        quitSer = false;
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // 5. 获取连接, accept 的返回值是一个新的 socketfd
        int serviceSock = accept(sockfd_, (struct sockaddr *)&peer, &len);
        if (serviceSock < 0)
        {
            // 获取链接失败
            logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
            continue;
        }

        while (!quitSer)
        {
            memset(inbuffer_, 0, sizeof(inbuffer_));
            ssize_t s = recvfrom(serviceSock, inbuffer_, sizeof(inbuffer_) - 1, 0,
                                 (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                std::cout << s << std::endl;
                // 接收成功
                inbuffer_[s] = '\0';
            }
            else if (s == -1)
            {
                //
                logMessage(WARINING, "recvfrom fialed:%s[%d]", strerror(errno), sockfd_);
                continue;
            }
            uint16_t peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);
            if(s > 0)
                logMessage(NOTICE, "[%s:%d]# %s", peerIp.c_str(), peerPort, inbuffer_);
            if (strcmp(inbuffer_, "quit") == 0)
            {
                quitSer = true;
            }
            else
            {
                sendto(serviceSock, inbuffer_, strlen(inbuffer_), 0,\
                    (const struct sockaddr *)&peer, sizeof(peer)); 
            }
        }
        
        logMessage(DEBUG, "quit server...");
        close(serviceSock);
    }
}

1.3 TCP客户端

TCP客户端与UDP不同的是,在进行网络通信之前需要对服务器发起连接请求,连接成功后才能进行网络通信。
在这里插入图片描述

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"

volatile bool quit = false;

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
    std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
              << std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    // 1. 创建socket SOCK_STREAM
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket: " << strerror(errno) << std::endl;
        exit(SOCK_ERR);
    }

    // 2. connect,发起连接请求,你想谁发起请求呢??当然是向服务器发起请求喽
    // 2.1 先填充需要连接的远端主机的基本信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverPort);
    inet_aton(serverIp.c_str(), &server.sin_addr);
    // 2.2 发起请求,connect 会自动帮我们进行bind!
    if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0)
    {
        std::cerr << "connect: " << strerror(errno) << std::endl;
        exit(CONN_ERR);
    }
    std::cout << "info : connect success: " << sock << std::endl;

    std::string message;
    while (!quit)
    {
        message.clear();
        std::cout << "请输入你的消息>>> ";
        std::getline(std::cin, message);
        if (strcasecmp(message.c_str(), "quit") == 0)
            quit = true;

        ssize_t s = write(sock, message.c_str(), message.size());
        logMessage(DEBUG, "write success...");
        if (s > 0)
        {
            message.resize(1024);
            ssize_t s = read(sock, (char *)(message.c_str()), 1024);
            if (s > 0)
                message[s] = 0;
            std::cout << "Server Echo>>> " << message << std::endl;
        }
        else if (s <= 0)
        {
            break;
        }
    }
    close(sock);
    return 0;
}

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

注意:

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

1.4 测试

在这里插入图片描述

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

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

相关文章

记录一次nginx转发代理skywalking白屏 以及nginx鉴权配置

上nginx代码 #user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; …

【2023】某python语言程序设计跟学第二周内容

本文说明&#xff1a; 案例内容为北理工python语言程序设计课程&#xff0c;如有不妥请联系&#xff01; 目录蟒蛇绘制案例&#xff1a;执行结果&#xff1a;代码分析&#xff1a;举一反三&#xff1a;绘制一个五角星图案执行结果&#xff1a;turtle库根据案例简单说明&#xf…

linux(Centos)安装docker

官网地址&#xff1a;Install Docker Engine on CentOS 首先检查linux系统版本及内核&#xff1a; 安装docker要求系统版本至少为7.x版本&#xff0c;内核至少为3.8以上 cat /etc/redhat-release # 查看系统版本号uname -r #查看linux系统内核 检查系统是否能连上外网&#…

3.基于Label studio的训练数据标注指南:文本分类任务

文本分类任务Label Studio使用指南 1.基于Label studio的训练数据标注指南&#xff1a;信息抽取&#xff08;实体关系抽取&#xff09;、文本分类等 2.基于Label studio的训练数据标注指南&#xff1a;&#xff08;智能文档&#xff09;文档抽取任务、PDF、表格、图片抽取标注等…

NJU数电实验-1

实验一 选择器 2选1多路选择器 逻辑表达式&#xff1a;y(∼s&a)∣(s&b)y(\sim s\&a)|(s\&b)y(∼s&a)∣(s&b) 逻辑电路&#xff1a; 数据流建模 数据流建模主要是通过连续赋值语句 assign 来描述电路的功能 module m_mux21(a,b,s,y);input a,b,s;…

这是一篇很好的互动式文章,Framer Motion 布局动画

重现framer的神奇布局动画的指南。 到目前为止&#xff0c;我最喜欢 Framer Motion 的部分是它神奇的布局动画–将 layout prop 拍在任何运动组件上&#xff0c;看着该组件从页面的一个部分无缝过渡到下一个部分。 <motion.div layout /> 在这篇文章中&#xff0c;我们…

【测试岗】那个准点下班的人,比我先升职了...

前言 陈双喜最近心态很崩。和他同期一道进公司的陈琪又升了一级&#xff0c;可是明明大家在进公司时&#xff0c;陈琪不论是学历还是工作经验&#xff0c;样样都不如自己&#xff0c;眼下不过短短的两年时间便一跃在自己的职级之上&#xff0c;这着实让他有几分不甘心。 程双…

linux常用命令介绍 03 篇——常用的文本处理工具之grep和cut(以及部分正则使用)

linux常用命令介绍 03 篇——常用的文本处理工具之grep和cut&#xff08;以及部分正则使用&#xff09;1 常用命令01篇 和 02篇1.1 Linux命令01篇——Linux解压缩文件常用命令1.2 Linux命令02篇——linux日常常用命令介绍2. 正则表达式2.1 基本定义2.2 正则中常用的元字符3. gr…

【python】异常详解

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录错误分类捕捉异常实例finally的使用捕捉特定异常抛出异常用户自定义异常&#x1f338;I could be bounded in a nutshell and count myself a king of infinite space. 特别鸣谢&#xff1a;木芯工作室 、I…

项目质量管理有哪些不同阶段?其中“质量“指的是什么?

项目质量管理是指在整个项目中管理和保持质量的过程。 "质量 "不是意味着 "完美"&#xff0c;通常更多的是指在整个项目中确保质量的一致性。然而&#xff0c;"质量 "的确切含义取决于客户或利益相关者对项目的需求&#xff0c;因此在每个项目可…

Ubuntu开机自动挂载硬盘

查看挂载信息&#xff0c;命令台输入 df -h能够看到/dev/nvme0n1p2是我们要挂在的硬盘&#xff0c;其路径是/media/lkzcswq/Data 找到要挂载磁盘的UUID sudo blkid /dev/nvme0n1p2观察到这个磁盘的UUID为72922DF0922DBA0D&#xff0c;type为ntfs 4. 编辑/etc/fstab文件 #如…

【服务器数据恢复】VMware虚拟机下的SQL Server数据库数据恢复案例

服务器数据恢复环境&#xff1a; 一台某品牌PowerEdge系列服务器和一台PowerVault系列存储&#xff0c;上层是ESXI虚拟机文件&#xff0c;虚拟机中运行SQL Server数据库。 服务器故障&#xff1a; 机房非正常断电导致虚拟机无法启动。管理员检查虚拟机发现虚拟机配置文件丢失&…

一、Java概述

一、Java概述 1.1 版本 Java SE 标准版&#xff08;核心版本&#xff09;&#xff0c;主要包含Java最核心的库包括&#xff1a;集合&#xff0c;IO&#xff0c;数据库连接、网络编程等 Java EE 企业版&#xff0c;主要用于开发&#xff0c;装配&#xff0c;部署企业级应用包括…

工业机器人编程调试怎么学

很多人觉得工业机器人很难学学&#xff0c;实际上机器人涉及的知识远比PLC要少。现简单说明一下初学者学习工业机器人编程调试的流程&#xff0c;以AUBO机器人为例&#xff1a; 首先我们需要知道工业机器人的调试学起来不难&#xff0c;远比编程更简单&#xff0c;示教器上的编…

基于信息间隙决策理论的碳捕集电厂调度(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Golang Map原理(底层结构、查找/新增/删除、扩缩容)

参考&#xff1a; 解剖Go语言map底层实现Go语言核心手册-3.字典 一、Go Map底层结构&#xff1a; Go map的底层实现是一个哈希表&#xff08;数组 链表&#xff09;&#xff0c;使用拉链法消除哈希冲突&#xff0c;因此实现map的过程实际上就是实现哈希表的过程。 先来看下…

react hooks学习记录

react hook学习记录1.什么是hooks2.State Hook3.Effect Hook4.Ref Hook1.什么是hooks (1). Hook是React 16.8.0版本增加的新特性/新语法 (2). 可以让你在函数组件中使用 state 以及其他的 React 特性 貌似现在更多的也是使用函数式组件的了&#xff0c;重要 2.State Hook imp…

Linux系统安装:Zookeeper

目录 Zookeeper的安装 1、环境准备 2、上传 3、解压文件到opt/zookeeper目下 4、安装完后进入zookeeper&#xff0c;找到conf目录 5、复制zoo_sample.cfg 6、编辑zoo.cfg 7、复制一份会话&#xff0c;进入zookeeper安装目录&#xff0c;创建一个文件夹zkdata&#xff0…

使用yeoman根据自己的模板创建一个脚手架

介绍 本文使用的模板并不是通用模板~&#xff0c;是自己构建的模板。内部具体如何选择模板逻辑就没有了&#xff0c;仅仅相当于入门demo实现流程。有兴趣学习脚手架的话yo还是不错的&#xff0c;走完本文逻辑可以试试看抽成自己项目&#xff0c;然后引用至公司~加油&#xff0…

【日常总结】Docker 磁盘占满解决方案

目录 项目背景&#xff1a; 问题描述 原因分析&#xff1a; 解决方案&#xff1a; Step 1&#xff1a;查看硬盘使用情况 Step 2&#xff1a;安装crontab Step 3&#xff1a;编写清理脚本cleardockerlog.sh&#xff0c;并执行一次 Step 4&#xff1a;加入定时任务,并设置…