服务器开发 Socket 相关基础

Socket 三要素

1.通信的目的地址;
2.使用的端口号;
3.使用的传输层协议(如 TCP、UDP)

Socket 通信模型

在这里插入图片描述

服务端实现

#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>#define SERVER_PORT 8089using namespace std;int main(int argc, char const *argv[])
{int sock;   // mailboxstruct sockaddr_in server_addr;//Create a mailboxsock = socket(AF_INET, SOCK_STREAM, 0);// clear tags, write addresses and portbzero(&server_addr, sizeof(server_addr));server_addr.sin_family = AF_INET;                //选择协议族IPV4server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //监听本地所有IP地址server_addr.sin_port = htons(SERVER_PORT);       //绑定端口号// label affixed to the receiving mailboxbind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));listen(sock, 128);  //the number of clients// ready to mailcout<<"wait for client"<<endl;int done = 1;while(done){struct sockaddr_in client;int client_socket, len;char client_ip[64];char buf[256];socklen_t client_addr_len;client_addr_len = sizeof(client);client_socket = accept(sock, (struct sockaddr *)&client, &client_addr_len);// print the IP address amd port of clientcout<<"client IP: "<<inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip))<<"  port: "<<ntohs(client.sin_port)<<endl;//read client->messagelen = read(client_socket, buf, sizeof(buf)-1);buf[len] = '\0';cout<<"receive:  "<< buf  <<"  len:"<<len <<endl;len = write(client_socket, buf, len);cout<<"write finished. len: "<<len<<endl;close(client_socket);}return 0;
}

客户端实现

#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>using namespace std;#define SERVER_PORT 8089
#define SERVER_IP "127.0.0.1"int main(int argc, const char* argv[]) {int sockfd;const char *message;struct  sockaddr_in servaddr;int n;char buf[64];if(argc!=2){fputs("Usage: ./echo_client message \n", stderr);exit(1);}message = argv[1];cout<<"message: "<< message <<endl;sockfd = socket(AF_INET, SOCK_STREAM, 0);memset(&servaddr, '\0', sizeof(struct sockaddr_in));servaddr.sin_family = AF_INET;inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);servaddr.sin_port = htons(SERVER_PORT);connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));write(sockfd, message, strlen(message));n = read(sockfd, buf, sizeof(buf)-1);if (n > 0){buf[n] = '\0';printf("receive: %s\n", buf);}else{perror("error!!!");}printf("finished.\n");close(sockfd);return 0;
}

在这里插入图片描述

socket 连接过程(TCP)

在这里插入图片描述

服务器端:
1.首先调用 socket() 函数,创建网络协议为 IPv4,以及传输协议为 TCP 的 Socket ,接着调用 bind() 函数,给这个 Socket 绑定一个 IP 地址和端口;

绑定端口的目的:当内核收到 TCP 报文,通过 TCP 头里面的端口号,来找到我们的应用程序,然后把数据传递给我们。
绑定 IP 地址的目的:一台机器是可以有多个网卡的,每个网卡都有对应的 IP 地址,当绑定一个网卡时,内核在收到该网卡上的包,才会发给我们;

2.绑定完 IP 地址和端口后,就可以调用 listen() 函数进行监听,此时对应 TCP 状态图中的 listen,如果我们要判定服务器中一个网络程序有没有启动,可以通过 netstat 命令查看对应的端口号是否有被监听。
3.服务端进入了监听状态后,通过调用 accept() 函数,来从内核获取客户端的连接,如果没有客户端连接,则会阻塞等待客户端连接的到来。

客户端:
客户端在创建好 Socket 后,调用 connect() 函数发起连接,该函数的参数要指明服务端的 IP 地址和端口号,然后 TCP 三次握手。

在 TCP 连接的过程中,服务器的内核实际上为每个 Socket 维护了两个队列:
一个是「还没完全建立」连接的队列,称为 TCP 半连接队列,这个队列都是没有完成三次握手的连接,此时服务端处于 syn_rcvd 的状态;
一个是「已经建立」连接的队列,称为 TCP 全连接队列,这个队列都是完成了三次握手的连接,此时服务端处于 established 状态;

当 TCP 全连接队列不为空后,服务端的 accept() 函数,就会从内核中的 TCP 全连接队列里拿出一个已经完成连接的 Socket 返回应用程序,后续数据传输都用这个 Socket。

监听的 Socket 和真正用来传数据的 Socket 是两个:
一个叫作监听 Socket;
一个叫作已连接 Socket;

连接建立后,客户端和服务端就开始相互传输数据了,双方都可以通过 read()write() 函数来读写数据。
至此, TCP 协议的 Socket 程序的调用过程就结束了。

小林coding

套接字概念

Socket 在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。
既然是文件,那么可以使用文件描述符引用套接字。
Linux系统将其封装成文件的目的是为了统接口,使得读写套接字和读写文件的操作一致。
区别是文件主要应用于本地持久化数据的读写,而套接字多应用于网络进程间数据的传递

创建 Socket 的时候,可以指定网络层使用的是 IPv4 还是 IPv6,传输层使用的是 TCP 还是 UDP.

在TCP/IP协议中,“IP 地址+TCP或UDP端口号”唯-标识网络通讯中的一个进程。
“IP 地址+端口号”就对应一个 sockct,欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一 个连接。 因此可以用Sockst来描述网络连接的一对一 关系。
在这里插入图片描述
在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。

Socket 编程基础

网络字节序(大小端)

网络传输中要多字节数据要进行主机/网络字节序转换的原因
网络字节序中,发送端发送的第一个字节是高位字节(取自内存的低地址),接收端收到后存入低位地址,由于主机的CPU架构不同,导致不同主机的字节序有所不同,因此对于多字节数据,在网络传输中需要进行主机/网络字节序的转换,接收端/发送端 才能正确的解析出多字节数据。

网络字节序(大端字节序)的定义:规定使用大端字节序(网络字节序)作为标准:接收端接收到的第一个字节是发送端的高位字节,存放到低位地址。
大端字节序的主机:按照从左往右的字节顺序将数据存储在内存中,低位字节存储在高位地址,高位字节存储在低位地址。
小端字节序的主机:按照从左往右的字节顺序将数据存储在内存中,低位字节存储在低位地址,高位字节存储在高位地址。

网络字节序
由于不同的主机架构,字节序有所不同,网络通信中,规定使用大端字节序(网络字节序)作为标准:接收端接收到的第一个字节是发送端的高位字节,存放到低位地址。因此需要发送端/接收端进行主机字节序和网络字节序的转换,以确保数据的正确传输和解析。

发送端:
发送的第一个字节是高位字节(取自内存中的低位地址),因此多字节数据在进行网络数据传输时,需要将主机字节序转为网络字节序。

接收端:
接收的第一个字节(高位字节)存放在低位地址。因此,需要将网络字节序转为主机字节序。

在这里插入图片描述

htonl()htons() /* 主机字节序转化为网络字节序 */
ntohl()ntohs() /* 网络字节序转化为主机字节序 */

sockaddr 数据结构

很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr 退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是其他的,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

在这里插入图片描述
通用套接字地址格式:

/* POSIX.1g 规范规定了地址族为2字节的值.  */
typedef unsigned short int sa_family_t;
/* 描述通用套接字地址  */
struct sockaddr{sa_family_t sa_family;  /* 地址族.  16-bit*/char sa_data[14];   /* 具体的地址值 112-bit */
}; 

IPv4 套接字格式地址:

/* IPV4套接字地址,32bit值.  */
typedef uint32_t in_addr_t;
struct in_addr
{in_addr_t s_addr;
};/* 描述IPV4的套接字地址格式  */
struct sockaddr_in
{sa_family_t sin_family; 	/* 16-bit */in_port_t sin_port;    		/* 端口口  16-bit*/struct in_addr sin_addr;    /* Internet address. 32-bit *//* 这里仅仅用作占位符,不做实际用处  */unsigned char sin_zero[8];
};

IPv4的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,但是sock API的实现早于ANSI C标准化,那时还没有void *类型,因此这些像bind 、accept函数的参数都用 struct sockaddr *类型表示,在传递参数之前要强制类型转换一下:

struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));		/* initialize servaddr */

IPv6 套接字地址格式:

struct sockaddr_in6
{sa_family_t sin6_family; 	/* 16-bit */in_port_t sin6_port;  		/* 传输端口号 # 16-bit */uint32_t sin6_flowinfo; 	/* IPv6流控信息 32-bit*/struct in6_addr sin6_addr;  /* IPv6地址128-bit */uint32_t sin6_scope_id; 	/* IPv6域ID 32-bit */
};

整个结构体长度是 28 个字节,其中流控信息和域 ID 先不用管,这两个字段,一个在 glibc 的官网上根本没出现,另一个是当前未使用的字段。这里的地址族显然应该是 AF_INET6,端口同 IPv4 地址一样,关键的地址从 32 位升级到 128 位,这个数字就大到恐怖了,完全解决了寻址数字不够的问题。

本地套接字地址格式:

struct sockaddr_un {unsigned short sun_family; 	/* 固定为 AF_LOCAL */char sun_path[108];   		/* 路径名 */
};

各种套接字对比分析:
在这里插入图片描述

地址族字段详解
地址族字段,它表示使用什么样的方式对地址进行解释和保存。地址族在 glibc 里的定义非常多,常用的有以下几种:

AF_LOCAL:表示的是本地地址,对应的是 Unix 套接字,这种情况一般用于本地 socket 通信,很多情况下也可以写成AF_UNIXAF_FILE
AF_INET:因特网使用的 IPv4 地址;
AF_INET6:因特网使用的 IPv6 地址。

这里的 AF_ 表示的含义是 Address Family,但是很多情况下,我们也会看到以 PF_ 表示的宏,比如 PF_INETPF_INET6 等,实际上 PF_ 的意思是 Protocol Family,也就是协议族的意思。我们用 AF_xxx 这样的值来初始化 socket 地址,用 PF_xxx 这样的值来初始化 socket。我们在 <sys/socket.h> 头文件中可以清晰地看到,这两个值本身就是一一对应的。

/* 各种地址族的宏定义  */
#define AF_UNSPEC PF_UNSPEC
#define AF_LOCAL  PF_LOCAL
#define AF_UNIX   PF_UNIX
#define AF_FILE   PF_FILE
#define AF_INET   PF_INET
#define AF_AX25   PF_AX25
#define AF_IPX    PF_IPX
#define AF_APPLETALK  PF_APPLETALK
#define AF_NETROM PF_NETROM
#define AF_BRIDGE PF_BRIDGE
#define AF_ATMPVC PF_ATMPVC
#define AF_X25    PF_X25
#define AF_INET6  PF_INET6

IP 地址转换函数

#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);

该函数将一个点分十进制串转换为一个二进制的网络字节顺序的IP地址。如果src没有指向一个合法的点分十进制字符串,那么该函数返回0。成功返回1,失败返回-1。void *dst用于存放转换后的网络字节序的IP地址。

const char* inet_ntop(int af, const void *src, char *dst, socklen_t size);

该函数将一个二进制的网络字节顺序的IP地址转换为它对应的点分十进制的字符串,并把得到的以null结尾的字符串复制到dst。成功返回指向点分十进制的指针,失败返回NULL
其中af代表地址类型,const void *src是需要被转换的网络字节序的IP地址;char *dst用于存放转换后的字符串类型的IP地址;socklen_t size代表数组char *dst的长度。

af 取值可选为 AF_INETAF_INET6, 即对应 ipv4 和 ipv6 对应。
其中 inet_ptoninet_ntop 不仅可以转换 IPv4 的 in_addr,还可以转换 IPv6 的 in6_addr.
因此函数接口是 void *addrptr

int inet_aton(const char *string, struct in_addr* addr);

输入参数string包含ASCII表示的IP地址, 输出参数addr是将要用新的IP地址更新的结构。如果输入地址不正确,则返回0;如果成功,返回非零;如果失败,返回-1

char *inet_ntoa(struct in_addr in);

该函数在内部申请了一块空间保存返回的点分十进制的IP地址。因为返回的结果放到静态存储区,所以不需要手动释放。

in_addr_t inet_addr(const char *str);

该函数可将点分十进制的IP地址转换为无符号长整型。

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

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

相关文章

day13-实战:商城首页(上)

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 作业 作业 .bg-backward {width: 60px; height: 60px;background: url(../ima…

LeetCode 热题 100 题解(二):双指针部分(1)

题目一&#xff1a;移动零&#xff08;No. 283&#xff09; 题目链接&#xff1a;https://leetcode.cn/problems/move-zeroes/description/?envTypestudy-plan-v2&envIdtop-100-liked 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同…

Python对docx文本一些操作

文本要是docx结尾 安装 Python-docx 包 读取word from docx import Document doc Document("c:/word22.docx") 获取word中的所有表格 from docx import Document doc Document("c:/word22.docx") doc.tables # 返回所有表格的list 获取表格中的总行…

uni-admin初始化一直提示未初始化数据库问题

uni-admin初始化&#xff0c;一直提示&#xff1a; “检测到您未初始化数据库&#xff0c;请先右键uni-admin项目根目下的 uniCloud/database 目录&#xff0c;执行初始化云数据库&#xff0c;否则左侧无法显示菜单等数据” 最后清除了localStorage&#xff0c;发现就好了。

盘点6个AI绘画免费网站,第一个不仅免费还好用!

随着人工智能技术的前沿发展&#xff0c;人工智能在各个领域发挥了重要作用。人工智能的受欢迎程度不断增加&#xff0c;引起了越来越多的关注。借助动画人工智能生成器&#xff0c;用户可以通过简单的操作获得专业的动画作品&#xff0c;而无需掌握高端技术。今天我们将盘点 1…

算法学习 -- 多路归并

思想 : 抽象出来一个例子 : 合并k个长度相等升序列表 : 抽象成一张表也就是 : 做法 : 用一个小根堆来维护 &#xff0c; 首先将每个序列的第一个元素放入队列中 &#xff0c; 然后模拟&#xff0c;每次取出队头&#xff0c;作为结果序列的下一个元素 &#xff0c; 然后向堆…

系统架构最佳实践 -- 人力资源(E-HR)应用架构设计

当谈到人力资源管理时&#xff0c;电子人力资源&#xff08;E-HR&#xff09;系统已经成为现代企业不可或缺的组成部分。E-HR系统的设计与实践对于提高组织的人力资源管理效率和员工体验至关重要。本文将探讨E-HR应用架构的设计与实践&#xff0c;以及如何借助信息技术优化人力…

第 6 章 Gazebo仿真环境搭建(自学二刷笔记)

6.6.4 Gazebo仿真环境搭建 到目前为止&#xff0c;我们已经可以将机器人模型显示在 Gazebo 之中了&#xff0c;但是当前默认情况下&#xff0c;在 Gazebo 中机器人模型是在 empty world 中&#xff0c;并没有类似于房间、家具、道路、树木... 之类的仿真物&#xff0c;如何在 …

第十四届蓝桥杯C/C++大学B组题解(二)

6、岛屿个数 #include <bits/stdc.h> using namespace std; const int M51; int T,m,n; int vis[M][M],used[M][M]; int dx[]{1,-1,0,0,1,1,-1,-1}; int dy[]{0,0,1,-1,1,-1,1,-1}; string mp[M]; struct node{//记录一点坐标 int x,y; }; void bfs_col(int x,int y){ qu…

Linux安全认证隐匿插件:PAM配置探秘

Linux安全认证隐匿插件&#xff1a;PAM配置探秘 初遇PAM&#xff1a;踏入未知领域 案例&#xff1a; 现网环境升级总是报错端口已被占用&#xff0c;原因是执行升级包中的一条命令时&#xff0c;返回多了一条日志打印&#xff0c;导致升级包中解析命令执行结果错误 当时是第…

【图论】图的存储--链式前向星存图法以及深度优先遍历图

图的存储 介绍 无向图-就是一种特殊的有向图-> 只用考虑有向图的存储即可 有向图 邻接矩阵邻接表 邻接表 存储结构: (为每一个点开了一个单链表,存储这个点可以到达哪个点) 1:3->4->null2:1->4->null3:4->null4:null 插入一条新的边 比如要插一条边&am…

STM32学习和实践笔记(4): 分析和理解GPIO_InitTypeDef GPIO_InitStructure (e)

接上文&#xff0c;继续来看这个函数&#xff1a; /*** brief Initializes the GPIOx peripheral according to the specified* parameters in the GPIO_InitStruct.* param GPIOx: where x can be (A..G) to select the GPIO peripheral.* param GPIO_InitStruct:…

C++ stl容器vector的认识与简单使用

目录 前言&#xff1a; 本篇文档图片引用自&#xff1a;https://cplusplus.com/reference/vector/vector/ 1.vector的结构 2.迭代器类型 3.构造函数 4.迭代器 反向迭代器遍历 const迭代器 5.容量 maxsize shrink_to_fit reverse resize 6.修改 insert和erase 7.…

[C#]OpenCvSharp利用MatchTemplate实现多目标匹配

【效果展示】 原图 模板图 匹配结果&#xff1a; 【实现部分代码】 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using…

单链表专题

文章目录 目录1. 链表的概念及结构2. 实现单链表2.1 链表的打印2.2 链表的尾插2.3 链表的头插2.4 链表的尾删2.5 链表的头删2.6 查找2.7 在指定位置之前插入数据2.8 在指定位置之后插入数据2.9 删除pos节点2.10 删除pos之后的节点2.11 销毁链表 3. 链表的分类 目录 链表的概念…

苹果电脑怎么彻底删除软件 苹果电脑卸载软件在哪里 cleanmymac x怎么卸载 mac废纸篓怎么删除

苹果电脑卸载软件的方法相对直观和简单&#xff0c;尤其是对于习惯使用Mac操作系统的用户来说。以苹果MacBook Pro为例&#xff0c;以下是卸载软件的详细步骤、使用方法、注意事项与建议。 一、卸载软件的详细步骤&#xff1a; 1. 打开Mac电脑&#xff0c;进入桌面&#xff0c…

React面试

React渲染流程(重点) jsx描述界面 jsx babel render function>vdom vdom fiber 在进行渲染 vdom 转换fiber reconcile 转换过程创建dom commit 到domvdom React Element 对象, 只记录了子节点, 没有记录兄弟节点, 因为渲染不可中断 fiber fiberNode 对象, 是一个链表 父节…

linux大文件IO

在Linux中处理大文件&#xff08;通常指大小超过2GB的文件&#xff09;时&#xff0c;需要使用特定的系统调用和标志&#xff0c;以确保程序能够正确地处理大文件的读写。这主要是因为在32位系统上&#xff0c;传统的文件偏移量和文件大小使用off_t类型表示&#xff0c;它通常是…

HarmonyOS 开发-MpChart运动健康场景实践案例

介绍 MpChart是一个包含各种类型图表的图表库&#xff0c;主要用于业务数据汇总&#xff0c;例如销售数据走势图&#xff0c;股价走势图等场景中使用&#xff0c;方便开发者快速实现图表UI&#xff0c;MpChart主要包括线形图、柱状图、饼状图、蜡烛图、气泡图、雷达图、瀑布图…

GIF在线生成器

上传图片就能生成GIF的前端WEB工具 源码也非常简单 <!DOCTYPE html> <html lang"zh" class"dark"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1, m…