Day37 socket、TCP、UDP

news/2024/5/18 13:38:33 标签: tcp/ip, udp, 网络协议

socket类型

流式套接字(SOCK_STREAM) TCP

提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。

数据报套接字(SOCK_DGRAM) UDP

提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。

原始套接字(SOCK_RAW)

可以对较低层次协议如IP、ICMP直接访问。
在这里插入图片描述
服务器:
1.创建流式套接字(socket())------------------------> 有手机
2.指定本地的网络信息(struct sockaddr_in)----------> 有号码
3.绑定套接字(bind())------------------------------>绑定电话
4.监听套接字(listen())---------------------------->待机
5.链接客户端的请求(accept())---------------------->接电话
6.接收/发送数据(recv()/send())-------------------->通话
7.关闭套接字(close())----------------------------->挂机

客户端:
1.创建流式套接字(socket())----------------------->有手机
2.指定服务器的网络信息(struct sockaddr_in)------->有对方号码
3.请求链接服务器(connect())---------------------->打电话
4.发送/接收数据(send()/recv())------------------->通话
5.关闭套接字(close())--------------------------- >挂机

函数接口

socket

int socket(int domain, int type, int protocol);
//作用:创建一个socket通信描述符
domain:指定通信的域(通信协议)
    AF_UNIX, AF_LOCAL   本地通信
    AF_INET  ipv4
    AF_INET6  ipv6
type:指定socket的类型
    SOCK_STREAM:流式套接字,接下来我们的通信使用TCP协议
    SOCK_DGRAM:数据报套接字,接下来我们的通信使用UDP协议
protocol:填0    

返回值:如果成功,返回创建的描述符,如果失败,返回-1

connect

int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);
作用:请求连接服务器
参数:
sockfd:上面socket接口得到的描述符
addr:相当于服务器的地址(IP+port)
addrlen:地址的长度,因为前面的地址是可变的,所以要通过参数来协定地址的长度
返回值:
0 -1

sockaddr结构体

//从bind接口的帮助文档中拿到
struct sockaddr {
               sa_family_t sa_family;
               char        sa_data[14];
           }
上述地址结构是一个通用结构,我们在用实际协议进行通信的时候,需要转换成相应协议的结构体。
用man 7 ip来查看ipv4对应的结构体

struct sockaddr_in {
   sa_family_t    sin_family; /* 地址协议族,=socket接口第一个参数 */
   in_port_t      sin_port;   /* 指定端口,端口要用网络字节序(大端) */
   struct in_addr sin_addr;   /* IP地址 */
};

/* Internet address. */
struct in_addr {
   uint32_t       s_addr;     /* address in network byte order */
};

bind

int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);
作用:绑定服务器地址:IP和端口,相当于对外公开自己的IP和端口,客户端就可以连接了
addr:绑定的IP和端口结构体,注意绑定的是自己的IP和端口
    IP:取真实的某个网卡的地址,也可以直接写0.0.0.0
addrlen:地址的大小  

listen

int listen(int sockfd, int backlog);
作用:进入监听状态,以便接收客户端的连接
sockfd:上面的服务器描述符
backlog:同时能处理的客户端的连接数量,写个5 10都可以
返回值:0 -1

accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
接收客户端连接,注意,这是一个阻塞接口,如果没有新的连接过来,那么会等待
sockfd:上面的服务器描述符
addr:客户端的地址(来电显示)
addrlen:客户端地址的长度,是入参,传入然后可能会被接口修改

返回值:如果成功,会返回一个非负的整数,代表接收的新的连接的描述符

recv/send

//recv和send是网络的专用接口,比起read和write只是多了一个flags参数,flag一般情况下会取0,代表阻塞接受和发送
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
返回值:
    >0:接收的字节数
    <0:失败
    =0:代表对端退出了
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

close

int close(int fd);
关闭套接字连接

示例代码

TCP客户端

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>

int main(int argc, char const *argv[])
{
    //创建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
    {
        perror("socket err");
        return -1;
    }

    //连接到服务器
    struct sockaddr_in server_addr;
    int len = sizeof(server_addr);
    
    //指定连接的服务器的地址(IP+port)
    memset(&server_addr, 0, len);
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
    server_addr.sin_addr.s_addr = inet_addr("192.168.0.109");

    int ret = connect(fd, (struct sockaddr *)&server_addr, len);
    if(ret < 0)
    {
        perror("connect err");
        return -1;
    }

    printf("conect success\n");

    char buf[64] = {0};
    while (1)
    {
        memset(buf, 0, 64);
        //从终端接收用户输入,发给服务器,等待服务器的数据,打印
        gets(buf);
        if(strcmp(buf, "quit") == 0)
        {
            break;
        }

        send(fd, buf, 64, 0);

        memset(buf, 0, 64);
        ret = recv(fd, buf, 64, 0);
        if(ret > 0)
        {
            printf("recv data = %s\n", buf);
        }
        else if(ret == 0)
        {
            printf("peer exit\n");
            break;
        }
        else
        {
            perror("recv err\n");
            return -1;
        }
    }

    close(fd);
    

    return 0;
}

TCP服务器

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>

int main(int argc, char const *argv[])
{
    //创建服务器的套接字
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(server_fd < 0)
    {
        perror("socket err");
        return -1;
    }

    //初始化服务器地址
    struct sockaddr_in server_addr, client_addr;
    int len = sizeof(server_addr);
    
    memset(&server_addr, 0, len);
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
#if 0    
    server_addr.sin_addr.s_addr = inet_addr("192.168.0.194");
#else
    server_addr.sin_addr.s_addr = INADDR_ANY;
#endif
    //绑定套接字的地址
    int ret = bind(server_fd, (struct sockaddr *)&server_addr, len);
    if(ret < 0)
    {
        perror("bind err");
        return -1;
    }

    //启动监听
    ret = listen(server_fd, 5);
    if(ret < 0)
    {
        perror("listen err");
        return -1;
    }

    //接收连接
    int clientfd = accept(server_fd, (struct sockaddr *)&client_addr, &len);
    if(clientfd < 0)
    {
        perror("accept err");
        return -1;        
    }

    //打印客户端的地址,注意端口要转成主机字节序
    printf("recv a new connect, ip = %s, port=%d\n",\
            inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

    char buf[64] = {0};

    while (1)
    {
        memset(buf, 0, 64);
        //一旦新的连接过来后,后续通信统统使用新的描述符
        len = read(clientfd, buf, 64);
        if(len > 0)
        {
            printf("recv data = %s\n", buf);
        }
        else if(len == 0)
        {
            printf("client exit\n");
            break;
        }
        else
        {
            perror("recv err\n");
            return -1;
        }
    }
    
    close(clientfd);
    close(server_fd);

    return 0;
}

UDP

TCP和UDP的异同点

相同点:

同属于传输层协议

不同点:

TCP:流式套接字,面向连接的,可靠的通信
UDP:数据报套接字,无连接的,不可靠的通信

通信流程

在这里插入图片描述

接口

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);
作用:接收UDP对端发来的消息
sockfd:描述符
buf:接收缓冲区
len:缓冲区的长度
flags:0,阻塞接收
src_addr:收到消息后,对端的地址存到这里(IP+PORT)
addrlen:src_addr缓冲区长度

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
            const struct sockaddr *dest_addr, socklen_t addrlen);
作用:发送消息给UDP对端
sockfd:描述符
buf:发送缓冲区
len:发送的长度
flags:0,阻塞发送
dest_addr:发送的目的地址(IP+PORT)
addrlen:dest_addr的长度

PS:UDP的端口和TCP的端口是独立的!!

代码示例

UDP服务器

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define KB 1024

int main(int argc, char const *argv[])
{
    //创建服务器的套接字
    int server_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(server_fd < 0)
    {
        perror("socket err");
        return -1;
    }

    //初始化服务器地址
    struct sockaddr_in server_addr, client_addr;
    int len = sizeof(server_addr);
    
    memset(&server_addr, 0, len);
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
#if 0 
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
#else
    server_addr.sin_addr.s_addr = INADDR_ANY;
#endif
    //绑定套接字的地址
    int ret = bind(server_fd, (struct sockaddr *)&server_addr, len);
    if(ret < 0)
    {
        perror("bind err");
        return -1;
    }

    //接收消息,收到客户端消息,然后把客户端的消息再返回给客户端
    char buf[KB] = {0};
    struct sockaddr_in cli_addr;

    socklen_t addrlen = sizeof(struct sockaddr_in);
    while (1)
    {
        memset(buf, 0, KB);
        //服务器一定是先接收的
        len = recvfrom(server_fd, buf, KB, 0,
                (struct sockaddr *)&cli_addr, &addrlen);
        if(len < 0)
        {
            perror("recv err");
            return -1;
        }

        printf("recv from %s--%d,data=%s\n", inet_ntoa(cli_addr.sin_addr),
                ntohs(cli_addr.sin_port), buf);

        //接收成功,而且对端地址存储到了cli_addr中
        sendto(server_fd, buf, len, 0,
            (struct sockaddr *)&cli_addr, addrlen);
    }

    close(server_fd);
    return 0;
}

UDP客户端

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define KB 1024

int main(int argc, char const *argv[])
{
    //创建套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0)
    {
        perror("socket err");
        return -1;
    }

    //连接到服务器
    struct sockaddr_in server_addr;
    int len = sizeof(server_addr);
    
    //指定连接的服务器的地址(IP+port)
    memset(&server_addr, 0, len);
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    char buf[KB] = {0};
    socklen_t addrlen = sizeof(struct sockaddr_in);
    while (1)
    {
        memset(buf, 0, KB);
        gets(buf);
        sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&server_addr, addrlen);

        memset(buf, 0, KB);
        len = recvfrom(fd, buf, KB, 0, NULL, NULL);
        printf("recv from server data = %s\n", buf);
    }
    
    close(fd);

    return 0;
}


http://www.niftyadmin.cn/n/5411293.html

相关文章

risc-v编译pytorch

准备 su进入root&#xff0c;避免遇到权限不足情况 python3 -m venv -xxx (创建的就是一个python环境和pytorch无关&#xff0c;可以安装任意环境的pytorch&#xff09; source进入虚拟环境 sudo apt-get install libopenblas-dev libblas-dev m4 cmake cython3 ccache 修改…

探索云原生世界:Serverless 技术的崛起与应用

在云原生架构的浪潮中&#xff0c;Serverless 技术正逐渐崭露头角&#xff0c;成为开发者们极具吸引力的选择。Serverless 的出现颠覆了传统的服务器管理模式&#xff0c;为应用开发带来了全新的可能性。 Serverless 架构是一种无需管理服务器的新型架构&#xff0c;它允许开发…

【操作系统原理】第一章课后习题

前言 课本&#xff1a; 操作系统原理&#xff08;第五版&#xff09;[费翔林&#xff0c;骆斌编著]习题&#xff1a; 主要习题内容是第一章到第六章&#xff0c;具体内容如下表 章节内容链接第一章思考题1&#xff0c;3&#xff0c;7、应用题7&#xff0c;12(1)~(4)null第二章…

商城|商城小程序|基于微信小程序的智慧商城系统设计与实现(源码+数据库+文档)

商城小程序目录 目录 基于微信小程序的智慧商城系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1、用户信息管理 2、 商品信息管理 3、公告信息管理 4、论坛信息管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 …

SpringBoot集成ElasticSearch(ES)

ElasticSearch环境搭建 采用docker-compose搭建&#xff0c;具体配置如下&#xff1a; version: 3# 网桥es -> 方便相互通讯 networks:es:services:elasticsearch:image: registry.cn-hangzhou.aliyuncs.com/zhengqing/elasticsearch:7.14.1 # 原镜像elasticsearch:7.…

vue2源码分析-vue入口文件global-api分析

文章背景 vue项目开发过程中,首先会有一个初始化的流程,以及我们会使用到很多全局的api,如 this.$set this.$delete this.$nextTick,以及初始化方法extend,initUse, initMixin , initExtend, initAssetRegisters 等等那它们是怎么实现,让我们一起来探究下吧 源码目录 global-…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:PanGesture)

拖动手势事件&#xff0c;当滑动的最小距离超过设定的最小值时触发拖动手势事件。 说明&#xff1a; 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 接口 PanGesture(value?: { fingers?: number; direction?: PanDir…

[AIGC] Maven的生命周期和Spring Boot的结合使用

在介绍Maven和Spring Boot结合使用之前, 了解Maven生命周期是非常必要的。Maven生命周期是构建过程中应遵循的一组步骤。每个步骤都代表了一个阶段&#xff0c;大致可以分为以下三部分&#xff1a; clean阶段: 清理上一次的构建结果。default阶段&#xff08;构建阶段&#xf…