【Linux】网络---->套接字编程(TCP)

news2025/1/9 15:58:45

套接字编程TCP

  • TCP的编程流程
  • TCP的接口
  • TCP的代码(单线程、多进程、多线程代码)
    • 单线程
    • 多进程
    • 多线程

TCP的编程流程

在这里插入图片描述
TCP的编程流程:大致可以分为五个过程,分别是准备过程、连接建立过程、获取新连接过程、消息收发过程和断开过程。

1.准备过程:服务端和客户端需要创建各自的套接字,除此之外服务端还需要绑定自己的地址信息和进行监听。注意:服务端调用listen函数后,处理监听状态,这时由socket函数返回的描述符被称为”监听套接字“,本质上是一个文件描述符,但是客户端没有”监听套接字‘这个概念。
2.连接建立过程:三次握手的过程,请注意,三次握手需要在服务端开始监听之后才能进行的,并且在应用层只需要程序员调用connect接口就可以了,剩下的三次握手的具体过程是在操作系统内核进行的,无需程序员干预
3.获取新连接过程:当连接建立好之后,建立好的连接会被放入已完成连接队列,在服务端只需要调用accept接口获取新的套接字即可。注意:如果没有调用accept接口,新的套接字不会被拿到应用层。也就没有真正意义上的建立连接
4.消息收发过程:当本端给对端发送消息时,将消息发送后,对端会回复一个确认消息。这个确认消息并不是程序员调用发送接口发送的,而是TCP协议自己发送的。
5.关闭过程:调用close接口后,就会关闭本端的新连接套接字。

对于连接建立过程来说:当某个客户端发送了连接请求后,还处于正在建立连接的过程中,就会处于未完成连接队列,而已经完成了建立连接这个过程的,就处于已完成连接队列。accept函数就是从已完成连接队列中拿新连接套接字的。

在这里插入图片描述

TCP的接口

对于创建套接字和绑定的操作接口,在【Linux】网络---->套接字编程UDP中已介绍,这里就只介绍和UDP不同的接口。

监听:当TCP服务端调用listen函数时,属于listen状态,标志着服务端可以正常接收客户端的请求了。

int listen(int sockfd, int backlog);

参数:
sockfd:套接字描述符,也就是socket函数的返回值,被称为”监听套接字“
backlog:实际含义是:已完成连接队列的大小。引申含义是:TCP并发连接数:TCP瞬时能处理TCP连接的最大数量(瞬时没有调用accept函数)。当服务端不进行accept,服务端最多能建立连接的个数为backlog+1。
可以通过/proc/sys/net/ipv4/tcp_max_syn_backlog修改未完成连接队列的大小。

注意:TCP并发连接数不等同于TCP服务端最多能够处理多少个TCP连接

问题:那么TCP服务端到底最多能够处理多少个TCP连接呢?
这个问题其实就是TCP服务端到底能够打开多少个文件描述符?

而到底能够打开多少个文件描述符,实际上是可以通过命令查看的,也可以通过命令修改,但是当超过某个值后,硬件就限制了其无法真正的打开n多个文件描述符。

accept获取新连接套接字

//这个函数是一个阻塞函数,如果没有已完成连接的连接,则阻塞等待,如有则返回
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:
sockfd:套接字描述符
addr:地址信息结构体,描述客户端地址信息的结构体
addrlen:地址信息长度
返回值:成功返回新连接的套接字,失败返回-1

注意:返回的新连接套接字是后续客户端和服务端进行通信的。而监听套接字是在准备阶段,连接建立阶段和获取连接阶段使用的。

在这里插入图片描述

意味着:监听套接字负责接收来自各个客户端连接请求的,而连接完成,服务端进行accept之后,会为每一个客户端创建一个独有的专门的新套接字。服务端通过新连接套接字和对应的客户端进行通信。

由客户端调用connect函数

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:
sockfd:套接字描述符(客户端)
addr:描述服务端的地址信息(服务端的ip和端口)
addrlen:地址信息长度
返回值:成功返回0,失败返回-1

注意:connect函数也可以绑定客户端的地址信息。(如果客户端没有进行绑定)

发送和接收

ssize_t send(int sockfd, const void* buf, size_t len, int flags);

参数:
sockfd:套接字描述符
buf:发送buf指向空间的内容
len:发送数据的长度
flags:0阻塞发送

注意:返回值很重要,成功返回发送的字节数,失败返回-1。

ssize_t recv(int sockfd, void* buf, size_t len, int flags);

参数:
sockfd:新连接套接字描述符
服务端:新连接套接字
客户端:socket 函数的返回值
buf,将接收到的数据存放在buf指定的空间,空间需要提前开辟好
len:期望接收的字节个数
flags:0阻塞接收

注意:返回值很重要,成功返回接收到的字节个数,接收失败返回-1, 对端关闭连接0

TCP的代码(单线程、多进程、多线程代码)

单线程

注:由于未使用多路转接,所以无法使用单线程完成—>既要accept又要send和recv,因此这个代码是有bug的。
服务端代码:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
  /*
   * 1.创建套接字
   * 2.绑定地址信息
   * 3.监听
   * 4.通信
   * 5.关闭
   * */
  int listen_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(listen_sockfd < 0)
  {
    perror("socket");
    return 0;
  }

  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(27878);
  addr.sin_addr.s_addr = inet_addr("0.0.0.0");

  int ret = bind(listen_sockfd, (struct sockaddr*)&addr, sizeof(addr));
  if(ret < 0)
  {
    perror("bind");
    return 0;
  }
  ret = listen(listen_sockfd, 5);
  if(ret < 0)
  {
    perror("listen_sockfd");
    return 0;
  }
  while(1)
  {
    int newsockfd = accept(listen_sockfd, NULL, NULL);
    if(newsockfd < 0)
    {
      return 0;
    }
    printf("newsockfd:%d\n", newsockfd);
  
    //通信过程
    char buf[1024] = {0};
    ssize_t r_size = recv(newsockfd, buf, sizeof(buf)-1, 0);
    if(r_size < 0)
    {
      continue;
    }
    else if(r_size == 0)
    {
      printf("%d connect shutdown\n", newsockfd);
      close(newsockfd);
    }else{
      printf("client say : %s\n", buf);
      send(newsockfd, buf, strlen(buf), 0);
    }
  }
  while(1)
  {
    sleep(1);
  }

  return 0;
}

客户端代码:

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

using namespace std;
int main()
{
  /*
   * 1.创建套接字
   * 2.发起连接
   * */
  int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(sockfd < 0)
  {
    perror("socket");
    return 0;
  }

  /*要描述服务端的ip和port*/
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(27878);
  addr.sin_addr.s_addr = inet_addr("10.0.12.14");

  int ret = connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));
  if(ret < 0)
  {
    perror("connect");
    return 0;
  }
  while(1)
  {
    char buf[1024] = {0};
    printf("please enter: ");
    fflush(stdout);
    cin >> buf;
    send(sockfd, buf, strlen(buf), 0);

    memset(buf, '\0', sizeof(buf));
    ssize_t r_size = recv(sockfd, buf, sizeof(buf)-1, 0);
    if(r_size < 0)
    {
      continue;
    }
    else if(r_size == 0)
    {
      printf("%d connect shutdown\n", sockfd);
      close(sockfd);
    }else{
      printf("server recall ------- %s\n", buf);
    }
  }

  return 0;
}

多进程

注:由于未使用多路转接,所以对于服务端来说,只能通过创建多个进程来实现父进程accept,子进程和客户端进行通信
服务端代码:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>


void signalcallback(int sig)
{
  wait(NULL);
}

int main()
{
  signal(SIGCHLD, signalcallback);
  /*
   * 1.创建套接字
   * 2.绑定地址信息
   * 3.监听
   * 4.通信
   * 5.关闭
   * */
  int listen_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(listen_sockfd < 0)
  {
    perror("socket");
    return 0;
  }

  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(27878);
  addr.sin_addr.s_addr = inet_addr("0.0.0.0");

  int ret = bind(listen_sockfd, (struct sockaddr*)&addr, sizeof(addr));
  if(ret < 0)
  {
    perror("bind");
    return 0;
  }
  ret = listen(listen_sockfd, 5);
  if(ret < 0)
  {
    perror("listen_sockfd");
    return 0;
  }
  while(1)
  {
    int newsockfd = accept(listen_sockfd, NULL, NULL);
    if(newsockfd < 0)
    {
      return 0;
    }
    printf("newsockfd:%d\n", newsockfd);
    
    /*
     * 1.创建子进程
     * 2.子进程和客户端通信
     * */

    //通信过程
    
    pid_t pid = fork();
    if(pid < 0)
    {
      close(newsockfd);//客户端通过接收的返回值可以感知到连接断开
      continue;
    }else if(pid == 0)
    {
      //子进程进行通信
      
      close(listen_sockfd);
      while(1)
      {
        char buf[1024] = {0};
        ssize_t r_size = recv(newsockfd, buf, sizeof(buf)-1, 0);
        if(r_size < 0)
        {
          continue;
        }
        else if(r_size == 0)
        {
          printf("%d connect shutdown\n", newsockfd);
          close(newsockfd);
          exit(1);  //结束子进程
        }else{
      }
        printf("client say : %s\n", buf);
      }
    }
    else{
      //父进程
      //自定义SIGCHLD信号,让父进程可以继续accept
    }
  }
  return 0;
}

多线程

注:由于未使用多路转接,因此除了使用多进程之外,还可以使用多线程进行,让主线程进行accept,工作线程和客户端通信。
服务端代码:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/wait.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

struct NewSockfd
{
  int _sockfd;
};

void* thread_worker(void* arg)
{
  struct NewSockfd* wokerfd = (struct NewSockfd*)arg;
  int newsockfd = wokerfd->_sockfd;
  pthread_detach(pthread_self());

  //通信过程
  while(1)
  {
    char buf[1024] = {0};
    ssize_t r_size = recv(newsockfd, buf, sizeof(buf)-1, 0);
    if(r_size < 0)
    {
      continue;
    }
    else if(r_size == 0)
    {
      printf("%d connect shutdown\n", newsockfd);
      close(newsockfd);
      delete wokerfd;
      pthread_exit(NULL); //结束线程
    }else{
        printf("client say : %s\n", buf);
        send(newsockfd, buf, strlen(buf), 0);
    }

  return NULL;
}

void signalcallback(int sig)
{
  wait(NULL);
}

int main()
{
  signal(SIGCHLD, signalcallback);
  /*
   * 1.创建套接字
   * 2.绑定地址信息
   * 3.监听
   * 4.通信
   * 5.关闭
   * */
  int listen_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(listen_sockfd < 0)
  {
    perror("socket");
    return 0;
  }

  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(27878);
  addr.sin_addr.s_addr = inet_addr("0.0.0.0");

  int ret = bind(listen_sockfd, (struct sockaddr*)&addr, sizeof(addr));
  if(ret < 0)
  {
    perror("bind");
    return 0;
  }
  ret = listen(listen_sockfd, 5);
  if(ret < 0)
  {
    perror("listen_sockfd");
    return 0;
  }
  while(1)
  {
    int newsockfd = accept(listen_sockfd, NULL, NULL);
    if(newsockfd < 0)
    {
      return 0;
    }
    printf("newsockfd:%d\n", newsockfd);
    
    /*
     * 1.创建工作线程
     * 2.工作线程和客户端通信
     * */

    struct NewSockfd* fd = new struct NewSockfd;
    fd->_sockfd = newsockfd;
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, thread_worker, (void*)fd);
    if(ret < 0)
    {
      close(newsockfd);
      continue;
    }


    //通信过程
 
      }
    }
  return 0;
}

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

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

相关文章

【youcans的深度学习 07】PyTorch入门教程:张量的基本操作 2

欢迎关注『youcans的深度学习』系列&#xff0c;持续更新中… 【youcans的深度学习 01】安装环境之 miniconda 【youcans的深度学习 02】PyTorch CPU版本安装与环境配置 【youcans的深度学习 03】PyTorch CPU版本安装与环境配置 【youcans的深度学习 04】PyTorch入门教程&#…

面向对象程序设计概述

&#x1f9d1;‍&#x1f4bb;CSDN主页&#xff1a;夏志121的主页 &#x1f4cb;专栏地址&#xff1a;Java核心技术专栏 目录 一、类 二、对象 三、识别类 四、类之间的关系 面向对象程序设计&#xff08;Object-Oriented Programming,OOP)是当今的主流程序设计范型&#x…

线段树详解

目录 线段树的概念 线段树的实现 线段树的存储 需要4n大小的数组 线段树的区间是确定的 线段树的难点在于lazy操作 代码样例 线段树的概念 线段树&#xff08;Segment Tree&#xff09;是一种平衡二叉树&#xff0c;用于解决区间查询问题。它将一个区间划分成若干个子区…

Android 车载值不值得入手学?

前言 随着智能车的不断普及和智能化程度的提高&#xff0c;车载系统也在逐步升级和演进&#xff0c;越来越多的汽车厂商开始推出采用Android系统的车载设备&#xff0c;这为Android车载开发提供了广泛的市场需求。 其次&#xff0c;随着人工智能技术的发展和应用&#xff0c;…

Linux : 安装源码包

安装源码包之前我们要准备好yum环境&#xff0c;或者使用默认上网下载的yum仓库或者查看&#xff1a;Linux&#xff1a;rpm查询安装 && yum安装_鲍海超的博客-CSDN博客 准备离线yum仓库 &#xff0c;默认的需要在有网环境下才能去网上下载 其次就是安装 gcc make 准…

UDP协议 sendto 和 recvfrom 浅析与示例

UDP&#xff08;user datagram protocol&#xff09;用户数据报协议&#xff0c;属于传输层。 UDP是面向非连接的协议&#xff0c;它不与对方建立连接&#xff0c;而是直接把数据报发给对方。UDP无需建立类如三次握手的连接&#xff0c;使得通信效率很高。因此UDP适用于一次传…

Kyligence Zen 一站式指标平台体验——“绝对实力”的指标分析和管理工具——入门体验评测

&#x1f996;欢迎观阅本本篇文章&#xff0c;我是Sam9029 文章目录 前言Kyligence Zen 是什么Kyligence Zen 能做什么Kyligence Zen 优势在何处 正文注册账号平台功能模块介绍指标图表新建指标指标模板 目标仪表盘数据设置 实际业务体验---使用官网数据范例使用流程归因分析指…

MySQL --- 多表设计

关于单表的操作(包括单表的设计、单表的增删改查操作)我们就已经学习完了。接下来我们就要来学习多表的操作&#xff0c;首先来学习多表的设计。 项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构…

ChatGPT-4怎么对接-ChatGPT-4强化升级了哪些功能

ChatGPT-4怎么使用 使用ChatGPT-4&#xff0c;需要通过OpenAI的API接口来对接ChatGPT-4。OpenAI是一个人工智能公司&#xff0c;为开发者提供多个API接口&#xff0c;包括自然语言处理&#xff0c;图像处理等。ChatGPT-4是OpenAI开发的最新版本的聊天式对话模型&#xff0c;可…

React antd Form item「受控组件与非受控组件」子组件 defaultValue 不生效等问题总结

一、为什么 Form.Item 下的子组件 defaultValue 不生效&#xff1f; 当你为 Form.Item 设置 name 属性后&#xff0c;子组件会转为受控模式。因而 defaultValue 不会生效。你需要在 Form 上通过 initialValues 设置默认值。name 字段名&#xff0c;支持数组 类型&#xff1a;N…

2.存储器层次系统

存储器 随机访问存储器 RAM&#xff08;随机存储器&#xff09; SRAM 双稳态触发器&#xff0c;有电就保持不变&#xff0c;干扰消除后时会恢复到稳定值&#xff0c;晶体管多因此密集度低 DRAM 每个位存储为对一个电容的充电&#xff0c;对干扰敏感&#xff0c;漏电所以需要刷…

静态数码管

静态数码管 1、简介工作方式数码管静态显示原理 2、硬件设计3、软件设计4、 1、简介 一般共阳极数码管更为常用 好处&#xff1a;将驱动数码管的工作交到公共端&#xff08;一般接驱动电源&#xff09;&#xff0c;加大驱动电源的功率自然要比加大IC芯片I/O口的驱动电流简单许…

【python 生成器】零基础也能轻松掌握的学习路线与参考资料

一、学习路线 了解生成器的概念和作用 首先&#xff0c;需要明确生成器的概念和作用&#xff0c;生成器是一种特殊的迭代器&#xff0c;它可以在循环中逐个地产生值&#xff0c;而不是一次性将所有的值产生出来。它的作用是使程序更加高效&#xff0c;达到节省内存等的效果。…

Linux 入门

文章目录 一、概述二、安装CentOS下载地址VMware下载地址 三、linux文件与目录结构Linux系统中一切皆文件Linux目录结构 四、VI/VIM 编辑器vi/vim是什么一般模式常用语法键盘图编辑模式指令模式 五、网络配置六、远程登陆七、系统管理Linux 中的进程和服务service 服务管理chkc…

几种常见的电源防反接电路

电源防反接&#xff0c;也即是防止电源的正负极搞反而导致电路损坏&#xff0c;例如你采用的是标准的DC口&#xff0c;那么没什么必要加入此种电路。而如果采用的是非常规的&#xff0c;如自定义的接插件等&#xff0c;那么就很有必要了。 举个例子&#xff1a;小编以前就采用…

企业在线制作帮助中心,选择:语雀、石墨、Baklib哪个好?

在当今互联网时代&#xff0c;越来越多的企业开始将帮助中心建设在线化。在线帮助中心的好处不仅可以提高用户的使用体验&#xff0c;也可以提高企业的工作效率。然而&#xff0c;选择一个合适的在线制作帮助中心工具却并不是一件容易的事情。在众多的在线制作帮助中心工具中&a…

Python3 入门教程||Python3 SMTP发送邮件||Python3 多线程

Python3 SMTP发送邮件 在Python3 中应用的SMTP&#xff08;Simple Mail Transfer Protocol&#xff09;即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则&#xff0c;由它来控制信件的中转方式。 python的 smtplib 提供了一种很方便的途径发送电子邮件。它对…

[cryptoverse CTF 2023] crypto部分

没打,完事作作题. Warmup 1 Decode the following ciphertext: GmvfHt8Kvq16282R6ej3o4A9Pp6MsN. Remember: CyberChef is your friend. Another great cipher decoding tool is Ciphey. 热身一下就凉,问了别人,用ciphey说是能自动解,但是安装报错 rot13base58 这个没有自动的…

JavaCollection集合:概述、体系特点、常用API、遍历方式

一、集合概述 集合和数组都是容器 数组 特点&#xff1a;数组定义完成并启动后&#xff0c;类型确定、长度固定。 劣势&#xff1a;在进行增删数据操作的时候&#xff0c;数组是不太合适的&#xff0c;增删数据都需要放弃原有数组或者移位。 使用场景&#xff1a;当业务数…

JMeter 常用的几种断言方法,你会了吗?

JMeter是一款常用的负载测试工具&#xff0c;通过模拟多线程并发请求来测试系统的负载能力和性能。在进行性能测试时&#xff0c;断言&#xff08;Assertion&#xff09;是非常重要的一部分&#xff0c;可以帮助我们验证测试结果的正确性。下面介绍JMeter常用的几种断言方法。 …