Linux:网络协议socket

我们之前学的通信是本地进程间通信,如果我们想在网络间通信的话,就需要用到二者的ip地址,分别被称为源IP地址和目的IP地址,被存入ip数据包中,其次我们还需要遵循一些通信协议。

TCP协议:传输层协议,有连接,可靠传输,面向字节流(无明确边界的字节序列),难

UDP协议:传输层协议,无连接,不可靠传输,面向数据流(以数据包为单位),简单

网络通信的本质也是进程间通信,你怎么知道你要和哪个进程通信?

端口号

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

端口号是一个2字节16位的整数

端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理

IP地址 + 端口号能够标识网络上的某一台主机的某一个进程

一个端口号只能被一个进程占用

IP+PORT表示互联网中唯一的一个进程,这样就可以锚定是哪个进程了

IP+PORT就是socket(套接字)

如果socket可以表示进程,那pid和socket是什么关系?

并不是所有进程都可以实现网络通信,有端口号的进程才是需要网络通信的进程;如果不加入端口号只用pid标识的话,系统和网络之间强耦合很容易出问题

一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定。

因为端口号也是有分类的,不同的端口号负责的部分不同

0~1023是比较知名的端口号,HTTP,FTP,SSH这些应用层协议都有固定端口号

1024~65535是操作系统动态分配的端口号,客户端程序的端口号就是由操作系统从这个范围分配的

传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号,就是在描述 数据是谁发的, 要发给谁

网络字节序

Linux:线程-CSDN博客这篇里我们提到了页表内对于地址的索引,是分大小端的

大端序:高字节存储在低地址,低字节存储在高地址。

小端序:低字节存储在低地址,高字节存储在高地址。

磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分

如何定义收到的网络数据流地址?

发送主句通常将发送缓冲区的数据按内存地址从低到高的顺序发出;接受主机把从网络上接到的字节保存在接收方的缓冲区内,也是按内存地址从低到高的顺序保存。

因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节. 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据

如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络 字节序和主机字节序的转换

h:主机字节序

n:网络字节序

l:32位长整数

s:16位短整数

htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;

如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

 inet_addr()
#include <arpa/inet.h>
in_addr_t inet_addr(const char *ip);

功能:是将一个点分十进制ipv4的IP地址转换32位大端网络字节序整数
参数:点分十进制的ip地址字符串ip
返回值:成功时返回32位大端整数,失败返回INADDR_NONE

socket

常用API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);// 绑定端口号 (TCP/UDP, 服务器)      
int bind(int socket, const struct sockaddr *address,socklen_t address_len);// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

接口结构:

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同,为了适用多种协议,才有多种结构

1、通信有网络通信和本地通信,下图的sockaddr就是用来判断是网络通信还是本地通信的

2、IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16 位端口号和32位IP地址

3、IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址, 不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容

 4、socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主 要有三部分信息: 地址类型, 端口号, IP地址

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

  - 功能:创建一个套接字 
        - 参数: 
                - domain: 协议族 
                        AF_INET : ipv4 
                        AF_INET6 : ipv6 
                        AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信) 
                - type: 通信过程中使用的协议类型 
                        SOCK_STREAM : 流式协议 
                        SOCK_DGRAM : 报式协议 
                - protocol : 具体的一个协议。一般写0 
                        - SOCK_STREAM : 流式协议默认使用 TCP 
                        - SOCK_DGRAM : 报式协议默认使用 UDP 
                - 返回值: 
                        - 成功:返回文件描述符,操作的就是内核缓冲区。 
                        - 失败:-1

当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命 名 

        - 功能:绑定,将fd 和本地的IP + 端口进行绑定 
        - 参数: 
                - sockfd : 通过socket函数得到的文件描述符 
                - addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息 
                - addrlen : 第二个参数结构体占的内存大小 

   bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

int listen(int sockfd, int backlog);      // /proc/sys/net/core/somaxconn 

      - 功能:监听这个socket上的连接 
        - 参数: 
                - sockfd : 通过socket()函数得到的文件描述符 
                - backlog : 未连接的和已经连接的和的最大值, 5 

 作为服务端需要时刻监听是否有客户端发来的数据,服务端就是调用listen()来监听建立的socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

   - 功能: 客户端连接服务器 
        - 参数: 
                - sockfd : 用于通信的文件描述符 
                - addr : 客户端要连接的服务器的地址信息 
                - addrlen : 第二个参数的内存大小 
        - 返回值:成功 0, 失败 -1 

 客户端通过调用connect函数来建立与TCP服务器的连接

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

 - 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接 
        - 参数: 
                - sockfd : 用于监听的文件描述符 
                - addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)
                - addrlen : 指定第二个参数的对应的内存大小 
        - 返回值: 
                - 成功 :用于通信的文件描述符 
                - -1 : 失败 

      服务器侧在调用socket()、bind()、listen()之后,就会监听指定的socket地址了。客户端在调用socket()、connect()之后就建立了一条连接通道并发向服务端发送一个请求,服务器监听到这个请求之后,就会调用accept()函数取接收请求。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作

发送和接收的接口:

ssize_t recv(int socket, void *buf, size_t len, int flags)

参数一:指定接收端套接字描述符;
参数二:指向一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
参数三:指明buf的长度;
参数四:一般置为0;
返回值:失败时,返回值小于0;超时或对端主动关闭,返回值等于0;成功时,返回值是返回接收数据的长度。

ssize_t send(int socket, const void *buf, size_t len, int flags);  

参数一:指定发送端套接字描述符;
参数二:指明一个存放应用程序要发送数据的缓冲区;
参数三:指明实际要发送的数据的字节数;
参数四:一般置0;
返回值:失败时,返回值小于0;超时或对端主动关闭,返回值等于0;成功时,返回值是返回发送数据的长度。

查看正在使用该端口的进程

sudo lsof -i :8888

server.c


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include<pthread.h>#define SERVER_PORT 8888
//调用socket函数返回的文件描述符int serverSocket;
//声明两个socket_in变量,表示客户端和服务端
struct sockaddr_in server_Addr;
struct sockaddr_in client_Addr;int addr_len = sizeof(client_Addr);//给accept用的
int clientSocket;
char recvbuffer[128]; //存储 发送和接收的信息 
int iDataNum;
char sendbuffer[128];int sockets_create(){if((serverSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0){perror("socket");return 1;}
//初始化memset(&server_Addr,0,sizeof(server_Addr));server_Addr.sin_family = AF_INET;server_Addr.sin_port = htons(SERVER_PORT);//ip可是是本服务器的ip,也可以用宏INADDR_ANY代替,代表0.0.0.0,表明所有地址server_Addr.sin_addr.s_addr = htonl(INADDR_ANY);return 0;
}
//监听
int Listen(){if(bind(serverSocket, (struct sockaddr *)&server_Addr, sizeof(server_Addr)) < 0){perror("connect");return 1;}//监听,最大数为5if(listen(serverSocket,5)<0){perror("Listen");return 1;}printf("监听端口:%d\n",SERVER_PORT);return 0;
}
//接收/发送消息
void* client_handler(void* client_Socket){int clientSocket = *(int *)client_Socket;printf("IP is %s\n", inet_ntoa(client_Addr.sin_addr)); //把来访问的客户端的IP地址打出来printf("Port is %d\n", htons(client_Addr.sin_port)); while(1){iDataNum=recv(clientSocket,recvbuffer,1024,0);if(iDataNum < 0)continue;recvbuffer[iDataNum]='\0';if(strcmp(recvbuffer,"quit")==0)break;printf("client#%s\n",recvbuffer);printf("server#");scanf("%s",sendbuffer);send(clientSocket,sendbuffer,sizeof(sendbuffer),0);if(strcmp(sendbuffer, "quit") == 0) break;}return 0;
}
int main(){int *new_Socket;//指针类型,和其他客户端指向同一个地址sockets_create();Listen();//监听//循环等待连接,连接上了创建新线程while(1){//acceptclientSocket=accept(serverSocket,(struct sockaddr*)&client_Addr, (socklen_t*)&addr_len);if(clientSocket < 0){perror("accept");return 1;}new_Socket=malloc(sizeof(clientSocket));*new_Socket=clientSocket;pthread_t client_pthread;if(pthread_create(&client_pthread,NULL,client_handler,(void*)new_Socket)<0){perror("pthread create");close(clientSocket);free(new_Socket);new_Socket=NULL;continue;}printf("Handler assigned\n");pthread_detach(client_pthread);}//msg();return 0;
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>  // 包含 inet_pton 的头文件
#include <unistd.h>  #define SERVER_PORT 8888//定义文件描述符和套接字,用于和服务器通信
int serverSocket;
char SERVER_IP[16];
struct sockaddr_in serverAddr;
int iDataNum;
//定义buffer
char sendbuf[128];
char recvbuf[128];
//创建套接字
int sockets_create(){if((serverSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0){perror("socket");return 1;}//初始化serverAddr.sin_family=AF_INET;serverAddr.sin_port=htons(SERVER_PORT);printf("[DEBUG] 转换 IP 地址: %s\n", SERVER_IP);if(inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) <= 0) {perror("inet_pton error");//serverAddr.sin_addr.s_addr=inet_pton(SERVER_IP);inet_addr()函数,将点分十进制IP转换成网络字节序IP(感觉和pton没什么区别)return 1;}printf("[DEBUG] 转换 IP 地址 成功");return 0;
}//连接服务端
int connect_ser(){printf("connecting...\n");if(connect(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0){perror("connect");return 1;}printf("连接到主机%s...\n",SERVER_IP);
}
//发送/接收信息
int msg(){while(1){printf("client#");scanf("%s",sendbuf);send(serverSocket, sendbuf, strlen(sendbuf), 0); //向服务端发送消息if(strcmp(sendbuf, "quit") == 0) break;printf("server#:");recvbuf[0] = '\0';iDataNum = recv(serverSocket, recvbuf, 1024, 0); //接收服务端发来的消息if(iDataNum < 0)continue;recvbuf[iDataNum] = '\0';printf("%s\n", recvbuf);}close(serverSocket);
}int main(){printf("please send host\n");scanf("%s",SERVER_IP);sockets_create();connect_ser();msg();return 0;
}

这就很抽象,首先我们实现了多个client和一个server的对话,但是可以看出,client端只能一收一发,不能连发多条

如果想实现边收边发我觉得我们需要再线程里创建线程,使读和写并发

下次再改,现在我要睡觉了,电脑没电了

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

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

相关文章

相机硬触发

PLC 接线图 通过使用PNP光电感应器 实现相机的硬触发 流程&#xff1a;触发相机拍照 然后相机控制光源触发 完成线路连接后 使用MVS 配置相机硬触发参数 通过 pnp传感器控制 硬触发拍照 检测 在2开项目中 不用在点击执行流程 通过PNP传感器就能触发 扩展&#xff1a; 在VP…

浅谈UI自动化

⭐️前言⭐️ 本篇文章围绕UI自动化来展开&#xff0c;主要内容包括什么是UI自动化&#xff0c;常用的UI自动化框架&#xff0c;UI自动化原理等。 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f349;博主将持续更新学习记录收获&#xff0c;友友们有任何问题…

儿童安全座椅行业全面深入分析

儿童安全座椅就是一种专为不同体重&#xff08;或年龄段&#xff09;的儿童设计&#xff0c;将孩子束缚在安全座椅内&#xff0c;能有效提高儿童乘车安全的座椅。欧洲强制性执行标准ECE R44/03的定义是&#xff1a;能够固定到机动车辆上&#xff0c;带有ISOFIX接口、LATCH接口的…

net core Autofac 替换默认的服务容器 DI,微软自动的容器 不支持命名选项的

微软默认的容器&#xff0c;不支持命名选项&#xff0c;同一接口&#xff0c;多个实现。 就不支持了。 配置core 支持Autofac 容器 using Autofac; using Autofac.Extensions.DependencyInjection;namespace WebApplication13 {public interface IMyService{string GetData()…

架构系列---高并发

目录标题 前言宏观架构细节解读第一层 &#xff1a;DNS第二层 &#xff1a; LVS 负载第三层 &#xff1a; Nginx第四层 &#xff1a; Gateway Application并发上限更多方案 业务扩展从域名角度如何承受更大的流量从业务的角度看如何分流大的流量 总结 前言 年轻的时候看到文章…

植被遥感常用反射特征表达

Figure: HDRF Let Ω ′ \Omega Ω′ be the incident solid angle, Ω \Omega Ω is leaving solid angle. Consider the BRDF of a Lamvertian target is 1 π \frac{1}{\pi} π1​, the BRF is 1. The HDRF of a target is defined as: R h e m ( Ω ) Φ r Φ r l a …

使用 MONAI Deploy 在 AMD GPU 上进行全身分割

Total body segmentation using MONAI Deploy on an AMD GPU — ROCm Blogs 2024 年 4 月 4 日 作者&#xff1a; Vara Lakshmi Bayanagari. 医疗开放网络人工智能&#xff08;MONAI&#xff09;是一个开源组织&#xff0c;提供最先进的医疗成像模型的 PyTorch 实现&#xff0c…

解决 ClickHouse 高可用集群中 VRID 冲突问题:基于 chproxy 和 keepalived 的实践分析

Part1背景描述 近期&#xff0c;我们部署了两套 ClickHouse 生产集群&#xff0c;分别位于同城的两个数据中心。这两套集群的数据保持一致&#xff0c;以便在一个数据中心发生故障时&#xff0c;能够迅速切换应用至另一个数据中心的 ClickHouse 实例&#xff0c;确保服务连续性…

推荐FileLink数据跨网摆渡系统 — 安全、高效的数据传输解决方案

在数字化转型的浪潮中&#xff0c;企业对于数据传输的需求日益增加&#xff0c;特别是在不同网络环境之间的文件共享和传输。为了满足这一需求&#xff0c;FileLink数据跨网摆渡系统应运而生&#xff0c;为企业提供了一种安全、高效的数据传输解决方案。 安全第一&#xff0c;保…

力扣排序350题 两个元组的交集2

题目&#xff1a; 给你两个整数数组 nums1 和 nums2 &#xff0c;请你以数组形式返回两 数组的交集。返回结果中每个元素出现的次数&#xff0c;应与元素在两个 数组中都出现的次数一致&#xff08;如果出现次数不一致&#xff0c;则考虑取 较小值&#xff09;。可以不考虑输出…

Python酷库之旅-第三方库Pandas(193)

目录 一、用法精讲 896、pandas.Index.isna方法 896-1、语法 896-2、参数 896-3、功能 896-4、返回值 896-5、说明 896-6、用法 896-6-1、数据准备 896-6-2、代码示例 896-6-3、结果输出 897、pandas.Index.notna方法 897-1、语法 897-2、参数 897-3、功能 897…

使用Mac如何才能提高OCR与翻译的效率

OCR与截图大家都不陌生&#xff0c;或许有的朋友对于这两项功能用到的不多&#xff0c;但是如果经常会用到的话&#xff0c;那你就该看看了 iOCR&#xff0c;快捷键唤出翻译窗口&#xff0c;不论是截图翻译、划词翻译、输入翻译、剪切板翻译&#xff0c;统统快捷键完成&#x…

《欢乐饭米粒儿9》第五期:用笑声诠释生活,让爱成为日常

在忙碌的生活节奏中&#xff0c;我们总是在寻找那份能够触动心灵深处的温暖与欢笑。由鲜博士独家冠名播出的独创小品剧《欢乐饭米粒儿》第九季作为一档家庭喜剧节目&#xff0c;正是这样一股清流&#xff0c;它以轻松幽默的方式&#xff0c;将家的温暖、爱的传递和生活的真谛娓…

基于人脸识别PCA算法matlab实现及详细步骤讲解

人脸识别 % FaceRec.m % PCA 人脸识别修订版&#xff0c;识别率88&#xff05; % calc xmean,sigma and its eigen decomposition allsamples[];%所有训练图像 for i1:40 for j1:5 aimread(strcat(e:\ORL\s,num2str(i),\,num2str(j),.jpg)); % imshow(a); ba(1:112*92…

C#的Event事件示例小白级剖析

1、委托Delegate 首先说一下delegate委托&#xff0c;委托是将方法作为参数进行传递。 // 定义了一个委托类型public delegate void MyDelegate(int num);// 定义了一个啥也不干的委托实例public MyDelegate m_delegate _ > {};// 定义了一个和委托相同格式的方法public …

Android 使用ninja加速编译的方法

ninja的简介 随着Android版本的更迭&#xff0c;makefile体系逐渐增多&#xff0c;导致make单编模块的时间越来越长&#xff0c;每次都需要半个小时甚至更长时间&#xff0c;其原因为每次make都会重新加载所有mk文件&#xff0c;再生成ninja编译&#xff0c;此完整过程十分耗时…

VSCode 1.82之后的vscode server离线安装

概述 因为今天在公司开发项目的时候&#xff0c;需要离线配置vscode远程开发环境&#xff0c; 根据参考链接1配置了一遍&#xff0c;不管怎么重启&#xff0c;VSCODE都还是提示下载vscode server&#xff0c;后面在官方issue上找到了解决方案 解决方案 修改Remote SSH的配置…

什么是 OpenTelemetry?

OpenTelemetry 定义 OpenTelemetry (OTel) 是一个开源可观测性框架&#xff0c;允许开发团队以单一、统一的格式生成、处理和传输遥测数据&#xff08;telemetry data&#xff09;。它由云原生计算基金会 (CNCF) 开发&#xff0c;旨在提供标准化协议和工具&#xff0c;用于收集…

缓存、注解、分页

一.缓存 作用&#xff1a;应用查询上&#xff0c;内存中的块区域。 缓存查询结果&#xff0c;减少与数据库的交互&#xff0c;从而提高运行效率。 1.SqlSession 缓存 1. 又称为一级缓存&#xff0c;mybatis自动开启。 2. 作用范围&#xff1a;同一…

AI打造超写实虚拟人物:是科技奇迹还是伦理挑战?

内容概要 在这个科技飞速发展的时代&#xff0c;超写实虚拟人物仿佛从科幻小说中走进了我们的日常生活。它们以生动的形象和细腻的动作&#xff0c;不仅在影视、广告和游戏中吸引了无数目光&#xff0c;更让我们对AI技术的未来充满了期待和疑惑。这些数字化身在逼真的外貌下&a…