【linux网络(二)】网络基础之套接字编程

2024-06-11 13:20

本文主要是介绍【linux网络(二)】网络基础之套接字编程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:Linux从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学更多操作系统知识
  🔝🔝


在这里插入图片描述

Linux网络

  • 1. 前言
  • 2. 端口号详解
  • 3. 认识TCP/UDP协议
  • 4. 对网络字节序的理解
  • 5. socket套接字API
  • 6. 套接字编程
  • 7. 总结

1. 前言

Linux网络部分,挺长时间没更新了, 秋招在即, 这篇文章就当是对网络知识的复习, 让我们一起进入网络的时间

本章重点:

本篇文章会认识, 端口号, 网络字节序等网络编程中的基本概念. 会带大家初识TCP和UDP协议. 并且通过套接字编程, 带大家实现一个简单的TCP/UDP服务器


2. 端口号详解

我们知道,一台机器可以启动多个服务. 那么当客户端拿到IP地址来访问你机器时, 你机器上有这么多个服务, 我怎么知道客户端想要访问哪个? 所以说我们需要一个字段来标识一台机器上的唯一一个服务(进程)

在这里插入图片描述

端口号(port)就是用来表示唯一一个服务的
IP + port可以标识全网唯一的服务

端口号(port)是传输层协议的内容:

  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用.

端口号和进程pid的关系:

在学习Linux系统时,学到过进程id可以用来标识唯一的一个进程. 那么为什么网络服务不直接使用pid来标识唯一的服务(进程)呢? 答案是: 1. 一个端口号只能绑定一个进程, 但一个进程可以绑定到多个端口号上 2. 解耦合, 端口号是传输层到应用层寻找服务时需要使用的字段, 而进程的pid往往用于操作系统管理不同的进程


3. 认识TCP/UDP协议

我们先对TCP和UDP有一个大概的认识,在后面再详细讲解它的协议内容:

TCP协议:

  • 传输层协议
  • 有连接: 通信前需要先建立连接
  • 可靠传输: TCP协议有一些措施来保证传输的可靠性
  • 面向字节流

UDP协议:

  • 传输层协议
  • 无连接: 通信前不需要建立连接
  • 不可靠传输: 无措施来保证可靠性
  • 面向数据报

对于连接性的解释:

TCP通信前需要先建立连接(也就是大名鼎鼎的TCP三次握手,后面会讲). 而UDP通信什么都不用提前做. 我们可以把TCP通信比喻为打电话, 想和你通信必须先经过你的同意. 而把UDP通信比喻为寄快递, 我不需要经过你的同意,我只需要知道你的地址就可以无脑给你寄快递

对于可靠性的解释:

TCP是可靠的,而UDP是不可靠的, 那么在实际生活中我们用TCP就行了啊,为什么还要有UDP协议的存在呢? 相信聪明的你也能想到, TCP的可靠性是通过一定的策略实现的, 所以一定就证明了TCP是比UDP要复杂的, 并且从效率上来说, TCP和UDP谁快谁慢还不一定. 所以在不同场景下, 用到的协议也不同. 比如微信发送信息时, 我们一定不希望消息在网络传输中丢失了, 所以大概率会选择TCP协议. 而当我们在直播上网课时, 及时有部分包丢失在网络中,也不会对整个直播有太大的影响, 所以这时往往会选择UDP协议

对于面向字节流/数据报的解释:

什么是面向字节流? 意思就是TCP协议在发送数据时, 不管一次性发送多少数据, 也不管数据一共要发送几次,它只关心能尽快的将数据从客户端发送到服务器. 所以说TCP在发送数据时, 完整的数据可能是: “abcdefg123456"但是第一次可能发送了"abcdefg1”,第二次再发送"234",再发送"56". 这都是不定的. 而UDP是面向数据报的, 它每次发送数据时, 会将完整的数据全部保存在一个报文中, 然后将这个数据报整体发送过去

所以TCP会有粘包问题,后面会讲


4. 对网络字节序的理解

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
  • 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  • 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

在这里插入图片描述

可使用系统调用做大小端字节序的交换

在这里插入图片描述


5. socket套接字API

在这里插入图片描述

socket函数的作用是创建套接字, 套接字的本质是一个文件描述符, 这个套接字会在后续中起重要作用. 第二个函数bind, 它的作用是: 将本服务的IP和端口号绑定到操作系统内部,供外部来访问. 而最后三个函数: listen和accept是TCP通信中需要用到的, 它们表示: listen用于开始监听是否有请求到来. accept用于将到来的请求拿到内存当中做解析. 而connect函数用于发送TCP请求的一方调用,与服务器建立连接

socket函数详解:

  • 第一个参数代表套接字的类型,是个宏. AF_INET代表ipv4,最常用
    在这里插入图片描述

  • 第二个参数代表通信的类型,是个宏. SOCK_DGRAM代表UDP. SOCK_STREAM代表TCP
    在这里插入图片描述

  • 第三个参数设置为0即可

后续的函数看后面的代码就能懂, 如有不懂,欢迎私信


6. 套接字编程

由于socket套接字编程比较复杂, 所以这里只提供TCP的示例:

服务器

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  
#define BUF_SIZE 1024  
#define PORT 8080  
int main() {  int server_fd, new_socket;  struct sockaddr_in address;  int opt = 1;  int addrlen = sizeof(address);  char buffer[BUF_SIZE] = {0};  const char *hello = "Hello from server";  // 创建socket文件描述符  if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {  perror("socket failed");  exit(EXIT_FAILURE);  }  // 设置socket选项  if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {  perror("setsockopt");  exit(EXIT_FAILURE);  }  address.sin_family = AF_INET;  address.sin_addr.s_addr = INADDR_ANY;  address.sin_port = htons(PORT);  // 绑定socket到端口  if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {  perror("bind failed");  exit(EXIT_FAILURE);  }  // 开始监听  if (listen(server_fd, 3) < 0) {  perror("listen");  exit(EXIT_FAILURE);  }  // 等待客户端连接  if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {  perror("accept");  exit(EXIT_FAILURE);  }  // 发送一些数据  write(new_socket, hello, strlen(hello));  // 读取客户端数据并回显  while ((read(new_socket, buffer, BUF_SIZE - 1)) > 0) {  // 发送数据回客户端  write(new_socket, buffer, strlen(buffer));  memset(buffer, 0, BUF_SIZE);  }  if (read(new_socket, buffer, 0) < 0) {  perror("read failed");  }  // 关闭连接  close(new_socket);  close(server_fd);  return 0;  
}

客户端

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  #define BUF_SIZE 1024  
#define SERVER_IP "127.0.0.1" // 服务器IP地址,这里使用本地回环地址  
#define SERVER_PORT 8080        // 服务器端口号,与服务器设置的端口一致  int main() {  int sockfd;  struct sockaddr_in server_addr;  char buffer[BUF_SIZE] = {0};  char *message = "Hello from client";  // 创建socket文件描述符  sockfd = socket(AF_INET, SOCK_STREAM, 0);  if (sockfd < 0) {  perror("socket creation failed");  exit(EXIT_FAILURE);  }  memset(&server_addr, 0, sizeof(server_addr));  // 配置服务器地址信息  server_addr.sin_family = AF_INET;  server_addr.sin_port = htons(SERVER_PORT);  if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {  perror("Invalid server address");  exit(EXIT_FAILURE);  }  // 连接到服务器  if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {  perror("connection failed");  exit(EXIT_FAILURE);  }  // 接收服务器的欢迎消息  read(sockfd, buffer, BUF_SIZE - 1);  printf("Server: %s\n", buffer);  // 向服务器发送消息  write(sockfd, message, strlen(message));  printf("Client: %s\n", message);  // 读取服务器的响应  memset(buffer, 0, BUF_SIZE); // 清空缓冲区以便接收新数据  read(sockfd, buffer, BUF_SIZE - 1);  printf("Server echo: %s\n", buffer);  // 关闭连接  close(sockfd);  return 0;  
}

7. 总结

网络套接字编程是掌握网络至关重要的一步, 学会了这个网络编程的流程和底层逻辑, 下次遇见其他语言封装好的网络编程函数,你甚至能想象到它底层是如何实现的. 所以学技能不能只学接口,要学底层原理和系统调用, 这些你看所有封装好了的函数都是透明的


🔎 下期预告:HTTP协议详解 🔍

这篇关于【linux网络(二)】网络基础之套接字编程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1051284

相关文章

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

在Linux终端中统计非二进制文件行数的实现方法

《在Linux终端中统计非二进制文件行数的实现方法》在Linux系统中,有时需要统计非二进制文件(如CSV、TXT文件)的行数,而不希望手动打开文件进行查看,例如,在处理大型日志文件、数据文件时,了解... 目录在linux终端中统计非二进制文件的行数技术背景实现步骤1. 使用wc命令2. 使用grep命令

Linux如何快速检查服务器的硬件配置和性能指标

《Linux如何快速检查服务器的硬件配置和性能指标》在运维和开发工作中,我们经常需要快速检查Linux服务器的硬件配置和性能指标,本文将以CentOS为例,介绍如何通过命令行快速获取这些关键信息,... 目录引言一、查询CPU核心数编程(几C?)1. 使用 nproc(最简单)2. 使用 lscpu(详细信

linux重启命令有哪些? 7个实用的Linux系统重启命令汇总

《linux重启命令有哪些?7个实用的Linux系统重启命令汇总》Linux系统提供了多种重启命令,常用的包括shutdown-r、reboot、init6等,不同命令适用于不同场景,本文将详细... 在管理和维护 linux 服务器时,完成系统更新、故障排查或日常维护后,重启系统往往是必不可少的步骤。本文

基于Linux的ffmpeg python的关键帧抽取

《基于Linux的ffmpegpython的关键帧抽取》本文主要介绍了基于Linux的ffmpegpython的关键帧抽取,实现以按帧或时间间隔抽取关键帧,文中通过示例代码介绍的非常详细,对大家的学... 目录1.FFmpeg的环境配置1) 创建一个虚拟环境envjavascript2) ffmpeg-py

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

从基础到进阶详解Pandas时间数据处理指南

《从基础到进阶详解Pandas时间数据处理指南》Pandas构建了完整的时间数据处理生态,核心由四个基础类构成,Timestamp,DatetimeIndex,Period和Timedelta,下面我... 目录1. 时间数据类型与基础操作1.1 核心时间对象体系1.2 时间数据生成技巧2. 时间索引与数据

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

详解Linux中常见环境变量的特点与设置

《详解Linux中常见环境变量的特点与设置》环境变量是操作系统和用户设置的一些动态键值对,为运行的程序提供配置信息,理解环境变量对于系统管理、软件开发都很重要,下面小编就为大家详细介绍一下吧... 目录前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变