编写一个最简单的Linux服务端和客户端程序

2023年8月3日,周四下午

这篇文章我从下午开始写了几个小时,

这篇文件基本总结了我今天学到的知识,

在写这篇文章的过程中灵感不断涌现、想明白了很多知识点,非常酣畅淋漓。

什么叫做深度学习?这就是深度学习!深度学习就是打破砂锅问到底,就是不断提出问题和解决问题。

有点飘了,感觉自己也能写书和出书了,哈哈哈。

其实我感觉自己还没写完,还有些疑惑没解决,以后有空再更新。


目录


纯享版

功能

服务端负责发送Hello给客户端,

客户端负责把Hello接收并打印出来。

服务端程序

cpp 复制代码
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
int main(){

    int serv_sock=socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(9990);

    bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));

    listen(serv_sock,5);

    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size=sizeof(clnt_addr);
    int clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size);

    write(clnt_sock,"Hello",sizeof("Hello"));

    close(clnt_sock);
    close(serv_sock);
    return 0;
}

客户端程序

cpp 复制代码
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<string.h>

int main(){
    
    int sock=socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
    serv_addr.sin_port=htons(9990);

    connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));

    char message[30];
    read(sock,message,sizeof(message)-1);
    printf("%s\n",message);

    close(sock);
    return 0;
}

通俗版

服务端程序

cpp 复制代码
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
int main(){
    
    //创建一台手机
    int serv_sock=socket(AF_INET,SOCK_STREAM,0);
    
    //创建一张电话卡
    struct sockaddr_in serv_addr;
    
    //运营商负责给电话卡填写信息
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(9990);
    
    //把电话卡插到手机里面
    bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    
    //设置手机来电话时提醒
    listen(serv_sock,5);
    
    //再创建一张电话卡,用来存储打电话过来的手机的电话卡信息,比如手机号
    struct sockaddr_in clnt_addr;

    socklen_t clnt_addr_size=sizeof(clnt_addr);
    
    //如果有电话打来就把来电的电话卡信息存储到clnt_addr电话卡里面
    int clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size);
    
    //按照clnt_addr电话卡的信息给这个来电的手机发送一个字符串"Hello"
    write(clnt_sock,"Hello",sizeof("Hello"));

    close(clnt_sock);
    close(serv_sock);
    return 0;
}

客户端程序

cpp 复制代码
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<string.h>

int main(){
    
    //创建一部手机
    int sock=socket(AF_INET,SOCK_STREAM,0);
    
    //创建一张电话卡卡
    struct sockaddr_in serv_addr;
    
    //运营商负责填写电话卡信息
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//负责确定打给谁
    serv_addr.sin_port=htons(9990);//负责确定打给谁
    
    //拨打电话
    connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));

    char message[30];
    //读取收到的"Hello"字符串
    read(sock,message,sizeof(message)-1);
    printf("%s\n",message);

    close(sock);
    return 0;
}

讲解版

服务端程序

cpp 复制代码
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
int main(){

    int serv_sock=socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY表示可以接受来自任意IP地址的连接
    serv_addr.sin_port=htons(9990);//设置服务器端口号

    bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));

    listen(serv_sock,5);

    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size=sizeof(clnt_addr);
    int clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size);

    write(clnt_sock,"Hello",sizeof("Hello"));

    close(clnt_sock);
    close(serv_sock);
    return 0;
}

为什么要设置服务器端口号?

端口号是一个16位的无符号整数,它可以唯一地标识服务器上的不同服务。

因为服务器在互联网中的IP地址是唯一的,但是服务器上运行着很多服务或者说程序,有的可能是用来处理web网页请求,有的可能是处理远程发来的数据库请求的。而由上面这个服务端代码建立的服务或者说程序只是服务器上众多服务或者说程序中的一个,那么当用本文的客户端代码创建的客户端程序通过IP地址找到这个服务器时,客户端程序怎么知道服务器上的运行着的数量众多的程序中哪个是用本文的服务端代码创建的服务端程序呢?通过设置具有唯一性的用来标识服务器上的不同服务的服务器端口号就能完美的解决这个问题,所以要设置服务器端口号。

进一步的说,用本文的客户端代码创建的客户端程序只要知道运行着用本文的服务端代码创建的服务端程序的服务器在互联网中的IP地址和这个服务端程序在服务器中的端口号,就能在茫茫的互联网中,精确的找到用本文的服务端代码创建的服务端程序。

在计算机网络中,客户端通过指定目标服务器的IP地址和端口号来与服务器建立连接。当客户端向服务器发送请求时,服务器会根据目标端口号识别请求,并将其路由到相应的服务程序。

通过设置端口号,可以使服务器上的不同服务在不同的端口上监听连接请求。这样,当客户端想要连接到特定的服务时,只需要指定相应的端口号即可。例如,Web服务器通常监听80端口,SMTP服务器通常监听25端口。

总而言之,设置服务器端口号是为了标识服务器上的不同服务,使客户端可以通过指定端口号与特定服务建立连接。

如果没有IP地址,怎么在互联网的几千万台计算机中,找到运行着用本文的服务端代码创建的服务端程序的计算机呢?如果没有端口,怎么在运行着众多程序的计算机中,用本文的服务端代码创建的服务端程序呢?

这就是为什么要设置端口和IP地址。

为什么不需要设置服务端的IP地址?

以后有空再写....

INADDR_ANY是什么意思?

INADDR_ANY表示可以接受来自任意IP地址的连接。

如果你把INADDR_ANY替换成成127.0.0.1,那么只会接收IP地址为127.0.0.1的客户端的连接请求

怎么获取到客户端的IP地址和端口号?

在上面的代码中,clnt_addr 是 sockaddr_in 类型的结构体,用于保存客户端的地址信息

accept函数会自动为我们填充 clnt_addr 结构体,其中包含:

  • clnt_addr.sin_addr.s_addr 客户端的IP地址
  • clnt_addr.sin_port 客户端的端口号

所以,在上面的代码中的accept函数下面添加如下代码就可以获取到客户端的IP地址和端口号:

cpp 复制代码
char* ip = inet_ntoa(clnt_addr.sin_addr);
printf("Client IP: %s\n", ip);

int port = ntohs(clnt_addr.sin_port);        
printf("Client port: %d\n", port);

值得注意的是,客户端程序的端口号和服务端设置的端口号是不一样的

怎么理解listen(serv_sock,5)?

通过设置 listen的第2个参数,可以控制连接队列中允许等待的最大连接数。如果连接队列已满,新的连接请求将被拒绝,直到有连接请求被处理并从队列中移出。

对于这个连接队列,我想说说自己的理解:

我认为连接队列在上面这个服务端程序是不可能满的,因为上面这个服务端程序没有创建新的线程或进程来处理连接请求。我认为只有使用了线程或者进程才可能会让连接队列满,之后有空的话,我会写一些程序来说明这个。

客户端程序

cpp 复制代码
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<string.h>

int main(){
    
    int sock=socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=inet_addr("127.0.0.1"); //指明要连接的服务器的地址是什么
    serv_addr.sin_port=htons(9990);//指明要连接服务器的哪个端口

    connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));

    char message[30];
    read(sock,message,sizeof(message)-1);
    printf("%s\n",message);

    close(sock);
    return 0;
}

这部分有空再更新....

相关推荐
食咗未12 小时前
Linux USB HOST EXTERNAL STORAGE
linux·驱动开发
食咗未12 小时前
Linux USB HOST HID
linux·驱动开发·人机交互
Xの哲學12 小时前
Linux SLAB分配器深度解剖
linux·服务器·网络·算法·边缘计算
齐鲁大虾13 小时前
UOS(统信操作系统)如何更新CUPS(通用Unix打印系统)
linux·服务器·chrome·unix
虾..14 小时前
Linux 简单日志程序
linux·运维·算法
huoxingwen15 小时前
Ubuntu 22.04 上 VMware Workstation 点击虚拟机窗口就消失的解决历程
linux·运维·ubuntu
姚青&16 小时前
Linux 常用命令之基本命令
linux·运维·服务器
一路往蓝-Anbo16 小时前
【第05期】数据的微观世界 (五) —— 浮点数 vs 定点数:MCU的数学课
linux·stm32·单片机·嵌入式硬件·物联网
G_H_S_3_16 小时前
【网络运维】企业级监控平台Zabbix:部署与实践指南
linux·运维·网络·zabbix
小周学学学16 小时前
Vcenter Auto Deploy安装与使用
linux·运维·服务器