Linux:网络协议socket

news2024/12/23 13:45:01

我们之前学的通信是本地进程间通信,如果我们想在网络间通信的话,就需要用到二者的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;
    }
    //监听,最大数为5
    if(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){
    //accept
    clientSocket=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.coloradmin.cn/o/2239716.html

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

相关文章

Leetcode - 143双周赛

目录 一&#xff0c;3345. 最小可整除数位乘积 I 二&#xff0c;3346. 执行操作后元素的最高频率 I 1.差分数组 2.同向三指针 滑动窗口 三&#xff0c; 3348. 最小可整除数位乘积 II 一&#xff0c;3345. 最小可整除数位乘积 I 本题直接暴力枚举&#xff0c;题目求 >n…

Springboot3 配置Swargger3.0版本

一、swagger 版本配置&#xff0c;我用的3.0.0 <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${swagger.version}</version></dependency>二、说明&#xff1a;springdo…

error MSB3325:无法导入以下密钥文件xxx,该密钥文件可能受密码保护

错误提示信息(类似如下)&#xff1a; error MSB3325: 无法导入以下密钥文件: F:\...\Common.pfx。该密钥文件可能受密码保护。若要更正此问题&#xff0c;请尝试再次导入证书&#xff0c;或手动将证书安装到具有以下密钥容器名称的强名称 CSP: VS_KEY_A65F207BE95F57D0 出现此…

喜报|超维机器人荣获昇腾AI创新大赛铜奖

近日&#xff0c;在备受瞩目的昇腾AI创新大赛中&#xff0c;超维机器人凭借扎实的技术实力和创新产品&#xff0c;荣获大赛铜奖。这一荣誉不仅展现了超维机器人在智能巡检领域的技术创新与突破&#xff0c;也标志着超维机器人的智能巡检解决方案在人工智能领域获得了广泛认可&a…

[[nodiscard]] 使用说明

1 作用 [[nodiscard]] 属性&#xff1a;这个属性可以用于函数或者返回类型。它的作用是告诉编译器&#xff1a;调用这个函数时&#xff0c;它的返回值不应被忽略。如果程序员调用了这样的函数但没有使用它的返回值&#xff0c;编译器会发出警告。这对于那些返回重要状态或错误…

golang分布式缓存项目 Day4 一致性哈希

注&#xff1a;该项目原作者&#xff1a;https://geektutu.com/post/geecache-day1.html。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习 为什么使用一致性哈希 我该访问谁 对于分布式缓存来说&#xff0c;当一个节点接收到请求&#xff0c;如…

MySQL数据库专栏(四)MySQL数据库链接操作C#篇

摘要 本篇文章主要介绍C#链接MySQL数据库的接口介绍&#xff0c;使用实例及注意事项&#xff0c;辅助类的封装及调用实例&#xff0c;可以直接移植到项目里面使用。 目录 1、添加引用 2、接口介绍 2.1、MySqlConnection 2.2、MySqlCommand 2.3、MySqlDataReader…

借助 Pause 容器调试 Pod

借助 Pause 容器调试 Pod 在 K8S 中&#xff0c;Pod 是最核心、最基础的资源对象&#xff0c;也是 Kubernetes 中调度最小单元。在介绍 Pause 容器之前需要先说明下 Pod 与容器的关系来理解为什么需要 Pause 容器来帮助调试 1. Pod 与 容器的关系 Pod 是一个抽象的逻辑概念&…

UE5 umg学习(四) 将UI控件切换到关卡中

视频资料 7、将UI控件渲染到关卡_哔哩哔哩_bilibili 在前三节里&#xff0c;创建了用户的控件蓝图Widget_BP 目标是运行的时候&#xff0c;开始运行这个蓝图&#xff0c;因此需要在开始事件触发运行 首先&#xff0c;回到主页&#xff0c;点击关卡蓝图 要从事件开始运行时 …

React Hooks在现代前端开发中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 React Hooks在现代前端开发中的应用 React Hooks在现代前端开发中的应用 React Hooks在现代前端开发中的应用 引言 React Hooks …

军工行业运维:监控易引领自主可控新潮流

在军工行业&#xff0c;信息安全和技术创新始终是发展的重中之重。随着信息化建设的不断深入&#xff0c;对监控产品的要求也日益严格。 监控易作为一款高性能、全面性的运维监控解决方案&#xff0c;凭借其国产化、自主可控的特性&#xff0c;以及对军工行业特殊需求的深刻理解…

【操作系统】——调度算法

&#x1f339;&#x1f60a;&#x1f339;博客主页&#xff1a;【Hello_shuoCSDN博客】 ✨操作系统详见 【操作系统专项】 ✨C语言知识详见&#xff1a;【C语言专项】 目录 先来先服务&#xff08;FCFS, First Come First Serve) 短作业优先&#xff08;SJF, Shortest Job Fi…

C++入门基础知识147—【关于C++ 一元运算符重载】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C 一元运算符重载的相关内容&#xff0…

【测试框架篇】单元测试框架pytest(1):环境安装和配置

一、pytest简介 Pytest是Python的一种单元测试框架&#xff0c;与Python自带的unittest测试框架类似&#xff0c;但是比 unittest框架使用起来更简洁&#xff0c;效率更高。 二、pytest特点 Pytest是一个非常成熟的Python测试框架,主要特点有以下几点&#xff1a; 非常容易…

【CANOE】【学习】【DecodeString】字节转为中文字符输出

系列文章目录 文章目录 系列文章目录前言一、DecodeString 转为中文字节输出二、代码举例1.代码Demo2.DecodeString 函数说明函数语法&#xff1a;参数说明&#xff1a;返回值&#xff1a;使用示例&#xff1a;示例代码&#xff1a; 说明&#xff1a; 前言 有时候使用的时候&a…

边缘计算在智能交通系统中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 边缘计算在智能交通系统中的应用 边缘计算在智能交通系统中的应用 边缘计算在智能交通系统中的应用 引言 边缘计算概述 定义与原…

CC1链学习记录

&#x1f338; 前言 上篇文章学习记录了URLDNS链&#xff0c;接下来学习一下Common-Colections利用链。 &#x1f338; 相关介绍 Common-Colections是Apache软件基金会的项目&#xff0c;对Java标准的Collections API提供了很好的补充&#xff0c;在其基础上对常用的数据结构…

边缘计算在智能物流中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 边缘计算在智能物流中的应用 边缘计算在智能物流中的应用 边缘计算在智能物流中的应用 引言 边缘计算概述 定义与原理 发展历程 …

《多模稳控应急通讯设备技术规范》出炉,应急通讯有章可循!

近日&#xff0c;由深圳市遨游通讯设备有限公司参与的《多模稳控应急通讯设备技术规范》在经历多项严格的审核程序后&#xff0c;由中国中小商会企业协会正式发布实施。该标准详细规定了多模稳控应急通讯设备的术语和定义、产品型号、技术要求、试验方法、检验规则、标志、包装…

生成任意3D和4D场景!GenXD:通用3D-4D联合生成框架 | 新加坡国立微软

文章链接: https://arxiv.org/pdf/2411.02319 项目链接&#xff1a;https://gen-x-d.github.io/ 有视频 亮点直击 设计了一个数据整理流程&#xff0c;从视频中获取包含可移动物体的高质量4D数据&#xff0c;并为30,000个视频标注了相机姿态。这个大规模数据集称为CamVid-30K&…