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

这篇具有很好参考价值的文章主要介绍了【Linux】网络---->套接字编程(TCP)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

TCP的编程流程

【Linux】网络---->套接字编程(TCP)
TCP的编程流程:大致可以分为五个过程,分别是准备过程、连接建立过程、获取新连接过程、消息收发过程和断开过程。

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

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

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

TCP的接口

首先需要创建套接字和绑定地址信息。

创建套接字:(无论是TCP还是UDP,无论是服务端还是客户端,都需要这一步操作来创建套接字描述符)

int socket(int domain, int type, int protocol);

参数:
domain:指定协议族----AF_INET为IPV4,AF_INET6为IPV6
type:指定socket类型-----SOCK_STREAM(面向流式Socket),SOCK_DGRAM(无连接的数据报)
protocol:指定协议----IPPROTO_TCP(TCP协议),IPPROTO_UDP(UDP协议)

绑定端口号:(在服务端,一定要绑定端口号,以便于后续客户端进行连接时,可以知道连接哪个服务端端口)

int bind(int socket, const struct sockaddr *address, socklen_t address_len);

参数:
socket:用socket函数创建的文件描述符
address:是一个指向类型为struct sockaddr的结构体指针
需要先在struct sockaddr_in创建的对象addr中设置addr.sin_family = AF_INET,addr.sin_port = htons(端口号),addr.sin_addr.s_addr = inet_addr(“ip地址”),
最后将设置好的结构体强转为struct sockaddr*类型
address_len:struct sockaddr结构体的长度

监听:当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

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

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

意味着:监听套接字负责接收来自各个客户端连接请求的,而连接完成,服务端进行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,工作线程和客户端通信。
服务端代码:文章来源地址https://www.toymoban.com/news/detail-440016.html

#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;
}

到了这里,关于【Linux】网络---->套接字编程(TCP)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux网络编程——tcp套接字

    本章Gitee仓库:tcp套接字 客户端: 客户端: 关于构造和初始化,可以直接在构造的时候,将服务器初始化,那为什么还要写到 init 初始化函数里面呢? 构造尽量简单一点,不要做一些“有风险”的操作。 tcp 是面向连接的,通信之前要建立连接,服务器处于等待连接到来的

    2024年02月20日
    浏览(33)
  • 【Linux网络编程】网络编程套接字(TCP服务器)

    作者:爱写代码的刚子 时间:2024.4.4 前言:本篇博客主要介绍TCP及其服务器编码 只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP地址 但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换 字符串转in

    2024年04月14日
    浏览(45)
  • 【探索Linux】P.29(网络编程套接字 —— 简单的TCP网络程序模拟实现)

    在前一篇文章中,我们详细介绍了UDP协议和TCP协议的特点以及它们之间的异同点。 本文将延续上文内容,重点讨论简单的TCP网络程序模拟实现 。通过本文的学习,读者将能够深入了解TCP协议的实际应用,并掌握如何编写简单的TCP网络程序。让我们一起深入探讨TCP网络程序的

    2024年04月14日
    浏览(42)
  • 【Linux】TCP网络套接字编程+协议定制+序列化和反序列化

    悟已往之不谏,知来者之可追。抓不住的就放手,属于你的都在路上…… 1. 为了让我们的代码更规范化,所以搞出了日志等级分类,常见的日志输出等级有DEBUG NORMAL WARNING ERROR FATAL等,再配合上程序运行的时间,输出的内容等,公司中就是使用日志分类的方式来记录程序的输

    2024年02月08日
    浏览(53)
  • [Linux] 网络编程 - 初见TCP套接字编程: 实现简单的单进程、多进程、多线程、线程池tcp服务器

    网络的上一篇文章, 我们介绍了网络变成的一些重要的概念, 以及 UDP套接字的编程演示. 还实现了一个简单更简陋的UDP公共聊天室. [Linux] 网络编程 - 初见UDP套接字编程: 网络编程部分相关概念、TCP、UDP协议基本特点、网络字节序、socket接口使用、简单的UDP网络及聊天室实现…

    2024年02月16日
    浏览(48)
  • 【JaveEE】网络编程之TCP套接字、UDP套接字

    目录 1.网络编程的基本概念 1.1为什么需要网络编程  1.2服务端与用户端 1.3网络编程五元组  1.4套接字的概念 2.UDP套接字编程 2.1UDP套接字的特点  2.2UDP套接字API 2.2.1DatagramSocket类 2.2.2DatagramPacket类  2.2.3基于UDP的回显程序 2.2.4基于UDP的单词查询  3.TCP套接字编程 3.1TCP套接字的特

    2023年04月13日
    浏览(46)
  • 【JavaEE】网络编程之TCP套接字、UDP套接字

    目录 1.网络编程的基本概念 1.1为什么需要网络编程  1.2服务端与用户端 1.3网络编程五元组  1.4套接字的概念 2.UDP套接字编程 2.1UDP套接字的特点  2.2UDP套接字API 2.2.1DatagramSocket类 2.2.2DatagramPacket类  2.2.3基于UDP的回显程序 2.2.4基于UDP的单词查询  3.TCP套接字编程 3.1TCP套接字的特

    2023年04月20日
    浏览(47)
  • 网络编程套接字应用分享【Linux &C/C++ 】【UDP应用 | TCP应用 | TCP&线程池小项目】

    目录 前提知识 1. 理解源ip,目的ip和Macip 2. 端口号 3. 初识TCP,UDP协议 4. 网络字节序 5. socket 编程 sockaddr类型  一,基于udp协议编程  1. socket——创建套接字 2. bind——将套接字强绑定  3. recvfrom——接受数据 4. sendto——发出信息  遇到的问题 (1. 云服务器中以及无法分配I

    2024年04月08日
    浏览(70)
  • 网络编程套接字( TCP )

    目录 1、实现一个TCP网络程序(单进程版)         1.1、服务端serverTcp.cc文件                  服务端创建套接字                  服务端绑定                  服务端监听                  服务端获取连接                  服务

    2024年01月17日
    浏览(40)
  • 网络编程【TCP流套接字编程】

    目录 TCP流套接字编程 1.ServerSocket API 2.Socket API 3.TCP中的长短连接 4.回显程序(短连接) 5.服务器和客户端它们的交互过程 6.运行结果及修改代码   ❗❗两个核心: ServerSocket     Socket 1.ServerSocket API ✨ ServerSocket 是创建 TCP服务端Socket的API ServerSocket 构造方法: ServerSocket 方法 :

    2023年04月12日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包