imx6ull/linux应用编程学习(11)SOCKET

从上篇文章了解到了网络基础知识,那么socket与tcp/ip、udp等有什么关系呢?

imx6ull/linux应用编程学习(10)网络基础知识(基于正点原子)-CSDN博客

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后

Socket是计算机网络中的一个通信端点,可以视为网络通信的基础设施,它为不同的网络通信协议提供了统一的接口。通过它,程序可以发送和接收数据。Socket的主要作用是建立网络连接,并进行数据传输。

Socket的类型

  1. 流式套接字(Stream Socket) :基于TCP( Transmission Control Protocol)协议的Socket,提供面向连接的、可靠的数据传输服务。数据在传输过程中不会丢失,并且接收方会以发送方相同的顺序接收数据。

  2. 数据报套接字(Datagram Socket) :基于UDP (User Datagram Protocol)协议的Socket,提供无连接的、尽力而为的数据传输服务。数据可能会在传输过程中丢失,接收顺序也可能不同。

由上可知,socket根据作为udp和tcp的接口,可发为以上两类,在进行更详细对比之前,我们先回顾一下tcp与udp的区别。

TCP/IP与UDP的关系

TCP/IP和UDP是两种不同的传输层协议:

  1. TCP/IP协议

    • 面向连接:在发送数据之前需要建立连接(三次握手)。
    • 可靠传输:通过确认机制和重传机制确保数据的可靠传输。
    • 流控制:可以调整发送数据的速率,以避免网络拥塞。
    • 数据顺序:确保数据按照发送顺序接收。
  2. UDP协议

    • 无连接:发送数据时不需要建立连接,传输效率高。
    • 不保证可靠性:没有数据确认机制,数据可能丢失或重复。
    • 无序传输:数据报的接收顺序可能与发送顺序不同。
    • 适合实时应用:如视频流、语音通话等对数据传输速度要求高,但对数据丢失不敏感的应用。

(tips:tcp和udp都依赖IP,ip就相当于每台主机的身份证,tcp和udp需要根据ip去辨别不同的主机)

Socket与TCP/IP、UDP的关系

Socket应用层与传输层协议(如TCP、UDP)之间的桥梁 。通过Socket编程,开发者可以选择使用TCP或UDP协议来实现具体的网络通信功能

  • 使用TCP的Socket

    • 服务器端:创建一个流式Socket -> 绑定端口 -> 监听连接请求 -> 接受连接 -> 读写数据。
    • 客户端:创建一个流式Socket -> 连接服务器 -> 读写数据。
  • 使用UDP的Socket

    • 服务器端:创建一个数据报Socket -> 绑定端口 -> 接收和发送数据报。
    • 客户端:创建一个数据报Socket -> 发送和接收数据报。

总结

Socket是实现网络通信的基本单元,它提供了统一的接口,方便程序员使用TCP或UDP进行数据传输。通过选择不同类型的Socket,程序可以在可靠性、速度和资源利用之间进行权衡,以满足不同应用场景的需求。

引用网上的一张图:

socket常用API:

1. 创建Socket
  • socket()
    • 原型socket(socket_family, socket_type, protocol=0)
    • 描述:创建一个新的Socket。
    • 参数
      • socket_family:指定地址族,常用的有AF_INET(IPv4)、AF_INET6(IPv6)。
      • socket_type:指定Socket类型,常用的有SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)。
      • protocol:一般设为0,表示使用默认协议。
2. 绑定地址
  • bind()
    • 原型bind(address)
    • 描述:将Socket绑定到指定的地址(IP地址和端口)。
    • 参数
      • address:一个元组,包含IP地址和端口号,如('192.168.1.1', 8080)
3. 监听连接
  • listen()
    • 原型listen(backlog)
    • 描述:使Socket进入监听状态,准备接受连接请求。
    • 参数
      • backlog:指定等待连接的最大数量。
4. 接受连接
  • accept()
    • 原型accept()
    • 描述:接受一个连接,返回一个新的Socket对象和客户端地址。
    • 返回值 :一个元组(conn, address)conn是新的Socket对象,address是客户端地址。
5. 连接到远程服务器
  • connect()
    • 原型connect(address)
    • 描述:连接到远程服务器。
    • 参数
      • address:远程服务器的地址和端口号。
6. 发送数据
  • send()

    • 原型send(bytes)
    • 描述:通过Socket发送数据。
    • 参数
      • bytes:要发送的数据,类型为字节串。
  • sendto()

    • 原型sendto(bytes, address)
    • 描述:通过Socket发送数据到指定地址(用于UDP)。
    • 参数
      • bytes:要发送的数据,类型为字节串。
      • address:目标地址和端口号。
7. 接收数据
  • recv()

    • 原型recv(bufsize)
    • 描述:接收数据。
    • 参数
      • bufsize:指定一次接收的最大数据量。
  • recvfrom()

    • 原型recvfrom(bufsize)
    • 描述:接收数据并获取发送方地址(用于UDP)。
    • 参数
      • bufsize:指定一次接收的最大数据量。
    • 返回值 :一个元组(data, address)data是接收到的数据,address是发送方地址。
8. 关闭Socket
  • close()
    • 原型close()
    • 描述:关闭Socket,释放资源。

socket 编程实战:

任务:实现一个简单地服务器和一个简单地客户端应用程序,将ubuntu和开发板分别作为客户端和服务端,实现交互

1.服务器程序:

编写服务器应用程序的流程如下:

①、调用 socket()函数打开套接字,得到套接字描述符;

②、调用 bind()函数将套接字与 IP 地址、端口号进行绑定;

③、调用 listen()函数让服务器进程进入监听状态;

④、调用 accept()函数获取客户端的连接请求并建立连接;

⑤、调用 read/recv、 write/send 与客户端进行通信;

⑥、调用 close()关闭套接字。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define SERVER_PORT     8888    //端口号不能发生冲突,不常用的端口号通常大于5000

int main(void)
{
    struct sockaddr_in server_addr = {0};
    struct sockaddr_in client_addr = {0};
    char ip_str[20] = {0};
    int sockfd, connfd;
    int addrlen = sizeof(client_addr);
    char recvbuf[512];
    int ret;

    /* 打开套接字,得到套接字描述符 */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (0 > sockfd) {
        perror("socket error");
        exit(EXIT_FAILURE);
    }

    /* 将套接字与指定端口号进行绑定 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVER_PORT);

    ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (0 > ret) {
        perror("bind error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    /* 使服务器进入监听状态 */
    ret = listen(sockfd, 50);
    if (0 > ret) {
        perror("listen error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    /* 阻塞等待客户端连接 */
    connfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
    if (0 > connfd) {
        perror("accept error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("有客户端接入...\n");
    inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));
    printf("客户端主机的IP地址: %s\n", ip_str);
    printf("客户端进程的端口号: %d\n", client_addr.sin_port);

    /* 接收客户端发送过来的数据 */
    for ( ; ; ) {

        // 接收缓冲区清零
        memset(recvbuf, 0x0, sizeof(recvbuf));

        // 读数据
        ret = recv(connfd, recvbuf, sizeof(recvbuf), 0);
        if(0 >= ret) {
            perror("recv error");
            close(connfd);
            break;
        }

        // 将读取到的数据以字符串形式打印出来
        printf("from client: %s\n", recvbuf);

        // 如果读取到"exit"则关闭套接字退出程序
        if (0 == strncmp("exit", recvbuf, 4)) {
            printf("server exit...\n");
            close(connfd);
            break;
        }
    }

    /* 关闭套接字 */
    close(sockfd);
    exit(EXIT_SUCCESS);
}

客户端连接到服务器之后,客户端会向服务器(也就是本程序)发送数据,在我们服务器应用程序中会读取客户端发送的数据并将其打印出来,

SERVER_PORT 宏指定了本服务器绑定的端口号,这里我们将端口号设置为 8888,端口不能与其它服务器的端口号发生冲突,不常用的端口号通常大于 5000。

2.编写客户端程序

客户端的功能是连接上小节所实现的服务器,连接成功之后向服务器发送数据,发送的数据由用户输入

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define SERVER_PORT		8888          	//服务器的端口号
#define SERVER_IP   	"192.168.5.9"	//服务器的IP地址

int main(void)
{
    struct sockaddr_in server_addr = {0};
    char buf[512];
    int sockfd;
    int ret;

    /* 打开套接字,得到套接字描述符 */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (0 > sockfd) {
        perror("socket error");
        exit(EXIT_FAILURE);
    }

    /* 调用connect连接远端服务器 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);  //端口号
    inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);//IP地址

    ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (0 > ret) {
        perror("connect error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("服务器连接成功...\n\n");

    /* 向服务器发送数据 */
    for ( ; ; ) {
        // 清理缓冲区
        memset(buf, 0x0, sizeof(buf));

        // 接收用户输入的字符串数据
        printf("Please enter a string: ");
        fgets(buf, sizeof(buf), stdin);

        // 将用户输入的数据发送给服务器
        ret = send(sockfd, buf, strlen(buf), 0);
        if(0 > ret){
            perror("send error");
            break;
        }

        //输入了"exit",退出循环
        if(0 == strncmp(buf, "exit", 4))
            break;
    }

    close(sockfd);
    exit(EXIT_SUCCESS);
}

SERVER_IP 和 SERVER_PORT 指的是服务器的 IP 地址和端口号,服务器的 IP 地址根据实际情况进行设置,服务器应用程序示例代码 30.4.1 中我们绑定的端口号为 8888,所以在客户端应用程序中我们也需要指定 SERVER_PORT 为 8888。

注意:

客户端和服务端要确定可以互ping,我ubuntu的ip是192.168.5.12,子网掩码是255.255.255.0,那么程序里面的ip就应该是192.168.5.x。我这里设为了192.168.5.9。

然后再开发板界面,设置临时ip:

cpp 复制代码
ifconfig eth0 192.168.5.9

就可以发现开发板的IP已经变成了192.168.5.9。

上机测试:

将客户端和服务端的程序编译:

将ubuntu作为客户端,开发板作为服务端,

注意:客户端的程序文件最后在ubuntu上运行,所以不能用交叉编译工具,而是用gcc指令,因为交叉编译工具编译完,就代表你这个文件需要在其他环境进行运行了。

利用scp指令将服务端文件传至开发板

先开启服务器,在开发板执行服务端文件:

ubuntu执行客户端:

连接成功

客户端发送字符串给服务端,服务端可以解收,成功!

相关推荐
CV学术叫叫兽几秒前
一站式学习:害虫识别与分类图像分割
学习·分类·数据挖掘
传而习乎2 分钟前
Linux:CentOS 7 解压 7zip 压缩的文件
linux·运维·centos
soulteary4 分钟前
突破内存限制:Mac Mini M2 服务器化实践指南
运维·服务器·redis·macos·arm·pika
我们的五年12 分钟前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
IT果果日记33 分钟前
ubuntu 安装 conda
linux·ubuntu·conda
一棵开花的树,枝芽无限靠近你35 分钟前
【PPTist】添加PPT模版
前端·学习·编辑器·html
Python私教36 分钟前
ubuntu搭建k8s环境详细教程
linux·ubuntu·kubernetes
羑悻的小杀马特1 小时前
环境变量简介
linux
VertexGeek1 小时前
Rust学习(八):异常处理和宏编程:
学习·算法·rust
小陈phd1 小时前
Vscode LinuxC++环境配置
linux·c++·vscode