Linux C网络通信过程

news2024/9/23 19:18:08

socket函数、sockaddr_in结构体 和 bind函数

socket函数的作用是创建一个网络文件描述符,程序通过这个文件描述符将数据发送到网络,也通过这个文件描述符从网络中接受数据。观察一下socket函数:

int listenfd;
listenfd = socket(AF_INET, SOCK_STREAM, 0)

会发现这个函数有三个参数,其中前两个参数指定了底层协议族为AF_INET(进行本地域通信),传输层使用SOCK_STREAM(字节流协议),即TCP协议。

我们知道,在传输层中网络是通过套接字(ip,端口)来进行定位的,但是socket中并没有指定套接字。默认情况下,系统会会随意分配一个端口,使用本机的ip地址。

这在客户端是没有问题的,客户端可以选择任意的端口和服务器进行通信。但是,服务器不行,因为客户端是主动向服务器发送数据的,它需要知道数据应该发送到服务器的哪个端口。所以需要服务器事先指定好端口号,服务器通过这个端口向服务器发送数据,也从这个端口从客户端接收数据。

由此,需要一个套接字结构体sockaddr_in来定义套接字:

struct sockaddr_in servaddr; // 服务端地址信息的数据结构。
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;                // 协议族,在socket编程中只能是AF_INET。
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。
servaddr.sin_port = htons(atoi(argv[1])); // 指定通信端口。

在这个套接字中,除了ip和端口,也指定了协议族,该协议组应该和传递给socket函数的一致。这里INADDR_ANY表示本机的任意ip地址,因为有些服务器不止一块网卡,多网卡的情况下,用该参数表示所有网卡的ip地址。ip地址也可以通过servaddr.sin_port = htons(atoi(argv[1]))的方式指定特定ip地址。

好了,现在有了套接字,但是和先前定义的socket文件描述符没有关系,所以还需要通过bind函数进行绑定:

bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

这个过程也叫socket命名。

上面的过程配置的是服务器的套接字,对于服务器来说,这是数据发送的起点,要将数据发送给客户端,还需要知道数据的终点,也就是客户端的套接字。客户端的套接字可以有accept函数获取。

listen监听队列、accept函数 及 数据的收发

当服务器得到socket文件描述符后,就可以准备和用户进行通信了。由于一般有多个客户端,所以服务器会通过一个监听队列来保存用户的连接请求。该监听队列通过listen函数来创建:

listen(listenfd, 5);

这里listen的是服务器的listenfd套接字,对客户端来说,这是数据的终点,所以服务器可以针对这个套接字进行监听。当有数据发送到这个套接字的时候,服务器将这个连接请求放入到listen监听队列。

当服务器要处理客户端连接请求时,通过accept函数选择一个连接。客户端发送的请求到达服务器后,服务器可以从报文的报头部分获得客户端的ip地址和端口号。
创建一个客户端套接字,服务器将这些信息保存到该套接字中,以便后续通信中将数据发送到客户端套接字。

int clientfd;                             // 客户端的socket。
int socklen = sizeof(struct sockaddr_in); // struct sockaddr_in的大小
struct sockaddr_in clientaddr;            // 客户端的地址信息。
clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, (socklen_t *)&socklen);

需要注意的是,accept是阻塞式的,当监听队列中没有请求连接时,accept将进入阻塞状态。

这就意味着,在传统的网络通信模型中,单线程的服务器将只能处理一个连接请求,只有当前的请求完成后,才能处理下一个请求,这是串行的处理方式。

梳理一下:服务器在处理客户端请求的过程中,将涉及到两个套接字,服务器通过监听本地的套接字来获取连接请求;之后通过accept获取客户端套接字,进行数据的收发,所以在进行数据收发时,只需要用到客户端套接字clientfd

char buffer[1024];
memset(buffer, 0, sizeof(buffer));
recv(clientfd, buffer, sizeof(buffer), 0);	\\接收数据
strcpy(buffer, "ok");
send(clientfd, buffer, strlen(buffer), 0);	\\发送数据

连接的释放

当客户端主动释放连接后,客户端和服务器之间将进行四次挥手,其中第四次挥手可以通过netstat -nt | grep port观察到

比如下面的服务器程序,该程序的功能是接受客户端发送的信息,再将信息发送回去。

/*
 * 程序名:server.cpp,此程序用于演示socket通信的服务端
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
  if (argc != 2)
  {
    printf("Using:./server port\nExample:./server 5005\n\n");
    return -1;
  }

  // 第1步:创建服务端的socket,和文件描述符一样
  int listenfd;
  // 初始化时只指定了所用的底层协议族为AF_INET(本地域通信),传输层使用SOCK_STREAM(字节流协议),即TCP协议
  if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
  {
    perror("socket");
    return -1;
  }

  // 第2步:把服务端用于通信的地址和端口绑定到socket上。
  struct sockaddr_in servaddr; // 服务端地址信息的数据结构。
  memset(&servaddr, 0, sizeof(servaddr));
  servaddr.sin_family = AF_INET;                // 协议族,在socket编程中只能是AF_INET。
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。
  // servaddr.sin_addr.s_addr = inet_addr("192.168.190.134"); // 指定ip地址。
  servaddr.sin_port = htons(atoi(argv[1])); // 指定通信端口。
  if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0)
  {
    perror("bind");
    close(listenfd);
    return -1;
  }

  // 第3步:把socket设置为监听模式。
  if (listen(listenfd, 5) != 0) // listen函数创建一个listen监听队列用于存放用户连接!!!
  {
    perror("listen");
    close(listenfd);
    return -1;
  }
  printf("Fininsh listening, try to accept...\n");

  // 第4步:接受客户端的连接。
  int clientfd;                             // 客户端的socket。
  int socklen = sizeof(struct sockaddr_in); // struct sockaddr_in的大小
  struct sockaddr_in clientaddr;            // 客户端的地址信息。

  // accept从listen监听队列中取出一个用户连接,当监听队列为空时,accept陷入阻塞!!!
  clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, (socklen_t *)&socklen);
  printf("客户端(%s)已连接。\n", inet_ntoa(clientaddr.sin_addr));

  // 第5步:与客户端通信,接收客户端发过来的报文后,回复ok。
  char buffer[1024];
  while (1)
  {
    int iret;
    memset(buffer, 0, sizeof(buffer));
    if ((iret = recv(clientfd, buffer, sizeof(buffer), 0)) <= 0) // 接收客户端的请求报文。
    {
      printf("iret=%d, waitting next connection\n", iret);
      break;
    }
    printf("接收:%s\n", buffer);

    strcpy(buffer, "ok");
    if ((iret = send(clientfd, buffer, strlen(buffer), 0)) <= 0) // 向客户端发送响应结果。
    {
      perror("send");
      break;
    }
    printf("发送:%s\n", buffer);
  }

  // 第6步:关闭socket,释放资源。
  close(listenfd);
  close(clientfd);
}

首先通过gcc -o server ./server.c编译得到可执行文件,再通过./server 1234启动该服务程序后,可以通过telnet 127.0.0.1 1234在本地建立和服务器(本机)的连接。

随后,通过netstat -nt | grep 1234可以查看连接释放后端口的使用情况,如下:

tcp        0      0 127.0.0.1:1234          127.0.0.1:60026         TIME_WAIT  

和1234有关的连接只有一个,这是客户端到服务器的连接,该连接处于TIME_WAIT状态。对照四次挥手示意图,可以看到客户端的这个连接将会持续2MSL。此时再次通过1234端口来启动服务器将会出现端口占用的提示:

bind: Address already in use

在这里插入图片描述
简单分析一下,当客户端接受到FIN=1,ACK=1后,客户端知道服务器要释放连接了,于是发送报文ACK=1,在收到该报文之前服务器处于LAST_ACK状态,并未释放连接资源。

如果网络不好,服务器可能收不到该报文,一直处于LAST_ACK状态,并且把原因归咎为客户端没有收到FIN=1, ACK=1报文,所以服务器会继续发送该报文。此时客户端等待的时间没有超过2MSL并处于TIME_WAIT状态,接受到该报文后他明白服务器的连接还未释放,所以再次发送ACK=1报文,通知服务器正常释放连接,避免服务器资源的消耗

网络正常的情况下,当2MSL过去后,第四次挥手顺利完成,再次使用netstat -nt | grep 1234查看会发现该连接自动释放掉了。

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

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

相关文章

NNDL 作业11:优化算法比较

目录 1. 编程实现图6-1&#xff0c;并观察特征 2. 观察梯度方向 3. 编写代码实现算法&#xff0c;并可视化轨迹 4. 分析上图&#xff0c;说明原理&#xff08;选做&#xff09; 5. 总结SGD、Momentum、AdaGrad、Adam的优缺点&#xff08;选做&#xff09; 6. Adam这么好&…

Python威布尔分布

文章目录威布尔分布及其性质在Python中生成威布尔分布的随机数指数分布和拉普拉斯分布的对比威布尔分布及其性质 威布尔分布&#xff0c;即Weibull distribution&#xff0c;又被译为韦伯分布、韦布尔分布等&#xff0c;是仅分布在正半轴的连续分布。 在numpy.random中&#…

python中urllib库的使用

1. 获取目标页面的源码 以获取百度页面源码为例 #使用urllib获取百度首页的源码 import urllib.request#1 定义一个url 作为需要访问的网址 url http://www.baidu.com#2 模拟浏览器向服务器发送请求 response响应 response urllib.request.urlopen(url)#3 获取响应中的页面…

Monkey测试

一、什么是 Monkey 测试 Monkey 测试是通过向系统发送伪随机的用户事件流&#xff08;如按键输入、触摸屏输入、手势输入等&#xff09;&#xff0c;实现对应用程序客户端的稳定性测试&#xff1b;通俗来说&#xff0c;Monkey 测试即“猴子测试”&#xff0c;是指像猴子一样&a…

JVM垃圾回收算法整理

JVM垃圾回收算法整理前言关键概念了解标记–清除算法复制算法标记–整理算法分代收集算法仰天大笑出门去&#xff0c;我辈岂是蓬蒿人前言 大概内容&#xff1a; jvm垃圾回收算法&#xff1a; 1、“标记–清除”算法&#xff1b;首先标记出所有需要被回收的对象&#xff0c;然…

搭建自己的SSR

Vue SSR介绍 是什么 官方文档&#xff1a;https://ssr.vuejs.org/Vue SSR&#xff08;Vue.js Server-Side Rendering&#xff09; 是 Vue.js 官方提供的一个服务端渲染&#xff08;同构应用&#xff09;解 决方案使用它可以构建同构应用还是基于原有的 Vue.js 技术栈 官方文档…

XXL-JOB逻辑自测及执行参数配置踩坑

概述 关于XXL-JOB的使用遇到的问题记录。对XXL-JOB不熟的&#xff0c;可以先参考分布式任务调度平台XXL-JOB深度实战 实战 业务DTO定义如下&#xff1a; Data public class AdAccountDTO {private String accountId;/*** yyyy-MM-dd HH:mm:ss*/private String startCreateT…

ThingBoard源码解析-缓存

配置 TB支持两种缓存&#xff1a;Caffeine和Redis,通过配置cache.type来指定使用哪种缓存。 位于 org.thingsboard.server.cache Caffeine 配置类&#xff1a;CaffeineCacheConfiguration Configuration ConditionalOnProperty(prefix "cache", value "t…

HTML CSS 个人网页设计 WEB前端大作业代码

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

【计算机毕业设计】7.线上花店系统maven源码

一、系统截图&#xff08;需要演示视频可以私聊&#xff09; 摘 要 随着互联网突飞猛进的发展及其对人们的生活产生至关重要的影响&#xff0c;线上购花&#xff0c;送货到家的购物方式受到了越来越多顾客的接受与喜爱。线上花卉小铺的设计与实现不仅可以带来更广泛的选择与实…

餐饮业如何现业绩突破性增长?

疫情反复无常&#xff0c;餐饮人每天都面临着极大的挑战&#xff1a;无法预测的关店通知、突如其来的禁止堂食命令......餐饮店客流减少&#xff0c;业绩下滑成为不可避免的趋势。 在这种情形下&#xff0c;不少餐饮老板拒绝“躺平”&#xff0c;上演“花式自救”&#xff1a;…

cpu设计和实现(数据预取)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面说过了一条指令经过cpu处理的时候需要经历几个阶段。通过实验&#xff0c;我们发现&#xff0c;哪怕是再简单的ori指令也要经历取指、译码、执…

MyBatis是什么?使用方式?

目录 前言&#xff1a; 一、概念讲述 1.什么是MyBatis&#xff1f; 2.官网网址 二、使用方式 1.pom.xml里面添加依赖包 2.新建统一配置文件&#xff08;俗称数据库连接文件&#xff09; 3.新建立映射文件 &#xff08;俗称数据库表对应xml&#xff09; 4.建立数据库表…

ArcMap中之提取影像数据边界

1、前言 手里有一些经过裁剪的不规则多边形影像数据&#xff08;如图例所示&#xff09;&#xff0c;希望能批量获取该类影像的边界信息&#xff0c;即影像对应的面信息&#xff0c;边界线信息。这里我们提供一种利用镶嵌数据集Footprint图层的方法来获取&#xff0c;面&#…

基于Python机器学习及深度学习在空间模拟与时间预测应用

了解机器学习的发展历史、计算原理、基本定义&#xff0c;熟悉机器学习方法的分类&#xff0c;常用机器学习方法&#xff0c;以及模型的评估与选择&#xff1b;熟悉数据预处理的流程&#xff0c;掌握python程序包的使用&#xff1b;理解机器学习在生态水文中的应用&#xff0c;…

超实用的图片处理技巧,一分钟轻松完成图片编辑

图片想必大家都很熟悉&#xff0c;无论是我们平时在聊天的时候使用的表情包或者是在工作中插入的插图都属于图片&#xff0c;在使用图片的时候大家会遇到各种各样的问题&#xff0c;比如上传的图片格式不对、使用的图片尺寸太大等等&#xff0c;都会导致图片无法正常使用&#…

[附源码]java毕业设计医院就诊流程管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

c++旅行商问题 (暴力解)

目录一、旅行商问题简介旅行商问题问题概述问题由来二、枚举所有方案1、思路2、代码3、复杂度分析三、深度优先搜索1、思路2、代码3、复杂度分析一、旅行商问题简介 旅行商问题 TSP&#xff0c;即旅行商问题&#xff0c;又称TSP问题&#xff08;Traveling Salesman Problem&am…

白盒测试与黑盒测试

白盒测试技术 白盒测试技术也称结构性测试&#xff0c;是一种设计测试用例的方法&#xff0c;一般用于分析程序的内部结构&#xff0c;使用该方法测试程序时测试者可以看到被测试程序&#xff0c;并分析其内部结构。 按照被测试测试时是否需要执行测试程序可以分为静态和动态…

RK3399平台开发系列讲解(中断篇)中断控制器驱动初始化

🚀返回专栏总目录 文章目录 一、设备树源文件1.1、gic控制器节点1.2、timer节点二、中断控制器匹配表三、中断控制器初始化3.1、函数of_irq_init3.2、函数gicv3_of_init3.3、函数gic_init_bases沉淀、分享、成长,让自己和他人都能有所收获!😄 一、设备树源文件 ARM64架构…