【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-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

poj 3181 网络流,建图。

题意: 农夫约翰为他的牛准备了F种食物和D种饮料。 每头牛都有各自喜欢的食物和饮料,而每种食物和饮料都只能分配给一头牛。 问最多能有多少头牛可以同时得到喜欢的食物和饮料。 解析: 由于要同时得到喜欢的食物和饮料,所以网络流建图的时候要把牛拆点了。 如下建图: s -> 食物 -> 牛1 -> 牛2 -> 饮料 -> t 所以分配一下点: s  =  0, 牛1= 1~

poj 3068 有流量限制的最小费用网络流

题意: m条有向边连接了n个仓库,每条边都有一定费用。 将两种危险品从0运到n-1,除了起点和终点外,危险品不能放在一起,也不能走相同的路径。 求最小的费用是多少。 解析: 抽象出一个源点s一个汇点t,源点与0相连,费用为0,容量为2。 汇点与n - 1相连,费用为0,容量为2。 每条边之间也相连,费用为每条边的费用,容量为1。 建图完毕之后,求一条流量为2的最小费用流就行了

poj 2112 网络流+二分

题意: k台挤奶机,c头牛,每台挤奶机可以挤m头牛。 现在给出每只牛到挤奶机的距离矩阵,求最小化牛的最大路程。 解析: 最大值最小化,最小值最大化,用二分来做。 先求出两点之间的最短距离。 然后二分匹配牛到挤奶机的最大路程,匹配中的判断是在这个最大路程下,是否牛的数量达到c只。 如何求牛的数量呢,用网络流来做。 从源点到牛引一条容量为1的边,然后挤奶机到汇点引一条容量为m的边