Linux 网络编程

news2024/11/26 23:39:36

套接字(Socket):

通过网络实现跨机通信

作用:一种文件描述符传输层的文件描述符

整个编程中,需要着重注意htonl/htons、ntohl/ntohs、inet_addr等

TCP的C/S实现


循环服务器模型

TCP服务器实现过程

1.创建套接字:

初始化结构体struct sockaddr_in

2.给套接字绑定ip地址和端口号:bind函数

#include <netinet/in.h>

3.将套接字文件描述符,从主动变为被动文件描述符(做监听准备——listen函数)

4.accept函数:被动监听客户的连接并响应        【实现三次握手(无客户端连接,则会阻塞)】

5.服务器调用read(recv)和write(send)函数————SIGPIPE忽略

recv的返回值等于0的时候,证明客户端已经关闭

6.注意事项:

        字节序转换:

发送数据:将主机端序转为网络端序

接收数据:将网络端序转为主机端序

7.调用close或者shutdown关闭TCP连接

close:

缺点1:一次性将读写都关闭——只想关写(读),打开写(读),就实现不了

缺点2:如果多个文件猫述符指向了同一个连接时。如果只close关闭了其中某个文件猫述符时只要其它的fd还打开着,那么连接不会被断开。直到所有的描述符都被close后才断开连接
出现多个描述指向同一个连接的原因可能两个:
1.通过dup方式复制出其它描述符
2.子进程维承了这个描述符,所以子进程的描述符也指向了连接

shutdown:

可以全关掉

  • shutdown(套接字描述符)会关闭整个套接字的发送和接收功能,不管是否有连接建立。
  • shutdown(一个连接描述符)只会关闭与该连接相关的发送和接收功能,不会影响其他连接或套接字描述符。
  • close(套接字描述符)会完全关闭套接字描述符,释放与之相关的资源,并将描述符标记为无效。
  • close(一个连接描述符)的概念上并不存在,连接描述符通常是由套接字描述符派生出来的,因此关闭连接描述符时实际上是关闭了套接字描述符。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h> //*******//
#include <netinet/in.h> //*******//
#include <arpa/inet.h>  //*******//
#include <unistd.h>
#include <signal.h>
int sockfd;
void my_exit(int sig)
{
    shutdown(sockfd, SHUT_RDWR);
    close(sockfd);
    printf("shutdown socket done\n");
    exit(0);
}
void handle(int sig) // SIGPIPE的信号处理函数————以观察是否产生了SIGPIPE信号
{
    if (sig == SIGPIPE)
    {
        printf("SIGPIPE is going\n");
    }
}

int main(int argc, char **argv)
{
    signal(SIGINT, my_exit);
    signal(SIGPIPE, handle);
    signal(SIGPIPE, SIG_IGN);

    // 1.
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socked is error");
        exit(-1);
    }
    printf("socket success\n");

    // setsockopt函数
    int i;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));

    // 2.
    struct sockaddr_in sockaddr_in1;
    sockaddr_in1.sin_family = AF_INET; // IPV4
    // “5555”可以用宏定义
    sockaddr_in1.sin_port = htons(4443);                         // 正确的做法是使用htons函数将主机字节序转换为网络字节序————htons而非htonl因为端口号是16位
    sockaddr_in1.sin_addr.s_addr = inet_addr("192.168.106.128"); //*****//
    if (bind(sockfd, (struct sockaddr *)&sockaddr_in1, sizeof(sockaddr_in1)) < 0)
    {
        perror("bind error");
        exit(-1);
    }
    printf("bind success\n");

    // 3.
    if (listen(sockfd, 20) < 0)
    {
        perror("listen error");
        exit(-1);
    }
    printf("listen success\n");

    // 4.
    struct sockaddr_in addr2;
    int len_addr2 = sizeof(addr2);

    while (1)
    {
        // 强制类型转换
        int sock_fd1 = accept(sockfd, (struct sockaddr *)&addr2, &len_addr2); // 每来一个客户端的连接请求,就会生成一个描述符,只要知道这个描述符,就能通过此通信
        if (sock_fd1 < 0)
        {
            perror("accept error");
            exit(-1);
        }
        printf("client ip = %s ,port = %d\n", inet_ntoa(addr2.sin_addr), ntohs(addr2.sin_port)); // 1.inet_ntoa把ip地址转换为字符————2.把网络的转换为主机的

        // 5.
        char buffer[1024] = {0};
        int recv_t = recv(sock_fd1, buffer, sizeof(buffer) - 1, 0);
        printf("recv_t : %d   ", recv_t);
        if (recv_t < 0)
        {
            perror("recv error");
            exit(-1);
        }
        else if (recv_t == 0) // recv的返回值为零的时候,证明客户端关闭了!
        {
            printf("client is closed\n");
        }
        else
        {
            printf("recv :%s\n", buffer);
            while (1)
            {
                memset(buffer, 0, sizeof(buffer));
                scanf("%s", buffer);

                // int w_t = send(sock_fd1, buffer, strlen(buffer), 0);
                int w_t = send(sock_fd1, buffer, strlen(buffer), MSG_NOSIGNAL); // MSG_NOSIGNAL:表示此操作不愿被SIGPIPE信号断开;或注册信号处理函数
                if (w_t < 0)
                {
                    perror("send data error");
                    exit(-1);
                }
            }
        }
        shutdown(sock_fd1, SHUT_RDWR);
    }
    return 0;
}
8.setsockopt函数

主要用在服务器端:

【当有客服端连接到服务器的时候,此时服务器端按下ctrl + c,断开连接,此时需要等待2MSL,才能再次用原来的ip和端口号新建客户端,为了去除这种等待2MSL的】

SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上

setsockopt(server_sockfd,SOL_SOCKET,SO_REUSEADDR,&j,sizeof(j));-CSDN博客

9.在socket()和bind()调用之间,使用下列代码——防止客户端关闭时,要等2MSL的时间

TCP客服端的实现过程

client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    if (argc != 3)
    {
        perror("input error");
        exit(-1);
    }
    int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd < 0)
    {
        perror("socket error");
        exit(-1);
    }

    struct sockaddr_in addr_in1;
    addr_in1.sin_family = AF_INET;
    addr_in1.sin_addr.s_addr = inet_addr(argv[1]);
    addr_in1.sin_port = htons(atoi(argv[2]));

    if (connect(socket_fd, (struct sockaddr *)&addr_in1, sizeof(addr_in1)) == 0)
    {
        printf("connect ok\n");
    }
    else
    {
        printf("connect error\n");
    }
    while (1)
    {
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        scanf("%s", buffer);
        write(socket_fd, buffer, strlen(buffer) + 1);

        memset(buffer, 0, sizeof(buffer));
        read(socket_fd, buffer, sizeof(buffer));
        printf("%s", buffer);
    }

    return 0;
}

UDP的C/S实现

UDP协议没有建立连接特性,所以UDP协议没有自动记录对方IP和端口的特点,每次发
送数据时,必须亲自指定对方的IP和端口,只有这样才能将数据发送给对方。

 

UDP通信过程

1.调用socket创建套接字文件

2.bind绑定固定的ip和端口

3.调用sendto和recvfrom函数,发送和接收数据

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char **argv)
{
    // if (argc != 3)
    // {
    //     printf("input error\n");
    //     exit(-1);
    // }
    // 1.
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0)
    {
        perror("socket error");
        exit(-1);
    }
    // 2.
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(5554);
    if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind error");
        exit(-1);
    }
    // 3.
    struct sockaddr_in addr2;
    int addr2_len = sizeof(addr2);
    addr2.sin_family = AF_INET;
    addr2.sin_addr.s_addr = inet_addr(argv[1]);
    addr2.sin_port = htons(atoi(argv[2]));

    while (1)
    {
        char buffer[1024];
        scanf("%s", buffer);
        int ret = sendto(sock_fd, buffer, strlen(buffer) + 1,
                         0, (struct sockaddr *)&addr2, addr2_len);
        if (ret < 0)
        {
            perror("sendto error");
            exit(-1);
        }
    }
    return 0;
}


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char **argv)
{
    // 1.
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0)
    {
        perror("socket error");
        exit(-1);
    }
    // 2.
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(5555);
    if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind error");
        exit(-1);
    }
    // 3.
    struct sockaddr_in addr2;
    memset(&addr2, 0, sizeof(addr2));
    int addr2_len = sizeof(addr2);
    char buffer[1024] = {0};
    while (1)
    {
        int ret = recvfrom(sock_fd, buffer, sizeof(buffer),
                           0, (struct sockaddr *)&addr2, &addr2_len);
        if (ret < 0)
        {
            perror("recvfrom error");
            exit(-1);
        }
        printf("from ip = %s ,from port = %d \n", inet_ntoa(addr2.sin_addr), ntohs(addr2.sin_port));
        printf("recv message = %s \n", buffer);
    }
    return 0;
}

运行结果:

广播


一个人发,然后其它所有人都接收,这就是广播。


广播只能在局域网内部有效,广播数据是无法越过路由器的,也就是说路由器就是广播数据的边界。 广播只能在局域网内部有效,广播数据是无法越过路由器的,也就是说路由器就是广播数据的边界。


实现方法:

1.广播的发数据,不需要绑定自己的IP地址

2.ip地址写成广播地址;例如:192.168.1.255

3.接收端的ip地址,不能设置为固定ip,要指定为htons(INADDR_ANY)

4.接收方需要setsockopt函数,设置套接字文件可以重复绑定

   addr1.sin_addr.s_addr = htons(INADDR_ANY);//为什么是htons和htonl!!!!
广播发送:无需bind

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0)
    {
        perror("socket error");
        exit(-1);
    }
    //*****************************************************************************//
    int j = 1;
    setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, (void *)&j, sizeof(j));
    //*****************************************************************************//
    struct sockaddr_in addr1;
    addr1.sin_family = AF_INET;
    addr1.sin_addr.s_addr = inet_addr("127.0.0.255");
    addr1.sin_port = htons(5555);

    while (1)
    {
        char buffer[1024] = {0};
        scanf("%s", buffer);
        sendto(sock_fd, buffer, sizeof(buffer),
               0, (struct sockaddr *)&addr1, sizeof(addr1));
    }
    return 0;
}



接受广播:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0)
    {
        perror("socket error");
        exit(-1);
    }

    int j = 1;
    setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));

    struct sockaddr_in addr1;
    addr1.sin_family = AF_INET;
    //*****************************************************************************//
    addr1.sin_addr.s_addr = htons(INADDR_ANY);//为什么是htons和htonl!!!!
    //*****************************************************************************//
    addr1.sin_port = htons(5555);

    int ret = bind(sock_fd, (struct sockaddr *)&addr1, sizeof(addr1));
    if (ret < 0)
    {
        perror("bind error");
        exit(-1);
    }

    struct sockaddr_in addr2;
    int len = sizeof(addr2);
    memset(&addr2, 0, sizeof(addr2));
    char buffer[1024];
    while (1)
    {
        memset(buffer, 0, sizeof(buffer));
        recvfrom(sock_fd, buffer, sizeof(buffer),
                 0, (struct sockaddr *)&addr2, &len);
        printf("recv %s\n", buffer);
    }
    return 0;
}

组播

把一些ip设置为一个组,给这些组,发消息(后续在QT里涉及到

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

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

相关文章

Web 中间件怎么玩?

本次主要是聊聊关于 web 中间件&#xff0c; 分为如下四个方面 什么是 web 框架中间件 为什么要使用 web 中间件 如何使用及其原理 哪些场景需要使用中间件 开门见山 web 中间件是啥 Web 框架中的中间件主要指的是在 web 请求到具体路由之前或者之后&#xff0c;会经过一个或…

MyBatis 映射文件(Mapper XML):配置与使用

MyBatis 映射文件&#xff08;Mapper XML&#xff09;&#xff1a;配置与使用 MyBatis是一个强大的Java持久化框架&#xff0c;它允许您将SQL查询、插入、更新和删除等操作与Java方法进行映射。这种映射是通过MyBatis的映射文件&#xff0c;通常称为Mapper XML文件来实现的。本…

正点原子lwIP学习笔记——MQTT协议

1. MQTT简介 MQTT是一种基于客户端服务端架构的发布/订阅模式的消息传输协议。他的设计思想是轻巧、开放、简单、规范&#xff0c;易于实现。这些特点使得他对很多场景来说都是很好的选择&#xff0c;尤其是对于受限的环境如机器与机器的通信&#xff08;M2M&#xff09;以及物…

python根据命令行参数动态导入模块或文件

需求 在命令行运行一个 python 文件&#xff0c;同时传入自定义参数&#xff1a; $ python main.py --nodeTable --actioncreate --data"{name: test2, is_sys_obj: False, encoding: UTF8,datconnlimit: -1, variables: []"希望 main.py 接收命令行参数&#xff0…

1.6.C++项目:仿mudou库实现并发服务器之channel模块的设计

项目完整版在&#xff1a; 文章目录 一、channel模块&#xff1a;事件管理Channel类实现二、提供的功能三、实现思想&#xff08;一&#xff09;功能&#xff08;二&#xff09;意义&#xff08;三&#xff09;功能设计 四、代码&#xff08;一&#xff09;框架&#xff08;二…

快速上手 Docker Swarm:构建分布式容器集群、轻松管理节点和服务

什么是Docker Swarm Docker Swarm 是 Docker 的内置编排工具&#xff0c;它允许将多个 Docker 主机组成一个集群&#xff0c;并以统一的方式管理和部署容器化应用程序。Swarm 提供了高可用性、伸缩性和容错能力&#xff0c;使得应用程序能够在集群中弹性地运行和扩展。 Docke…

唤醒手腕 Matlab 游戏编程常用技术知识点详细教程(更新中)

Figure 窗口初始化 figure 使用默认属性值创建一个新的图窗窗口。生成的图窗为当前图窗。f figure(___) 返回 Figure 对象。可使用 f 在创建图窗后查询或修改其属性。figure(f) 将 f 指定的图窗作为当前图窗&#xff0c;并将其显示在其他所有图窗的上面。 figure(n) 查找 Nu…

8、Docker-compose容器编排

一、Docker compose 是什么 Compose 是 Docker 公司推出的一个工具软件&#xff0c;可以管理多个 Docker 容器组成一个应用。你需要定义一个 YAML 格式的配置文件docker-compose.yml&#xff0c;写好多个容器之间的调用关系。然后&#xff0c;只要一个命令&#xff0c;就能同…

cadence SPB17.4 S032 - 使用room来放置元件

文章目录 cadence SPB17.4 S032 - 使用room来放置元件概述笔记在orcad中设置子原理图的ROOM号码在空的Allegro工程中, 放入板框在allegro中建立room备注补充 - ROOM还得留着END cadence SPB17.4 S032 - 使用room来放置元件 概述 如果在allegro中直接手工或自动放置元件, 放好…

scala基础入门

一、Scala安装 下载网址&#xff1a;Install | The Scala Programming Language ideal安装 &#xff08;1&#xff09;下载安装Scala plugins &#xff08;2&#xff09;统一JDK环境&#xff0c;统一为8 &#xff08;3&#xff09;加载Scala &#xff08;4&#xff09;创建工…

单调队列 - 滑动窗口

154. 滑动窗口 - AcWing题库 O(1)求窗口中的最大值/最小值 #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define endl \nusing namespace std;typedef pair<int, int> PII; typedef long long ll; typedef long double l…

gdb的使用

目录 gdb工具的使用 代码调试相关指令 运行程序指令 r 显示代码的指令 l 给代码打断点 b 查看断点位置 info b 执行代码到断点处停止 关闭断点 d断点编号 关闭某个断点&#xff0c;但不删除 disable编号 打开某个断点 enable断点编号 逐过程调试代码 n 逐语句调试代码 s 查看…

【外设】拓展坞接入外设一直弹窗报错问题

外设相关&#xff1a;多功能拓展坞安装后使用正常但计算机一直弹窗报错 1.基本配置信息&#xff1a; 拓展坞&#xff1a;绿联6合1 笔记本&#xff1a;lenovo &#xff08;硬件:13^i5;系统&#xff1a;win11&#xff09; 外设&#xff1a;1键1鼠1显示器1硬盘 2.存在的问题&am…

LNK2001: unresolved external symbol __imp___std_init_once_begin_initialize 问题解决

LNK2001: unresolved external symbol __imp___std_init_once_begin_initialize 解决 文章目录 问题背景方法一&#xff1a;使用预编译指令方法二&#xff1a;使用相同的环境 参考链接附录 问题背景 Visual Studio 2019 对 CMakeLists.txt 的支持不是很好&#xff0c;使用 “文…

【单片机】12-串口通信和RS485

1.通信有关的常见概念 区分&#xff1a;串口&#xff0c;COM口&#xff0c;UART&#xff0c;USART_usart和串口区别-CSDN博客 串口、COM口、UART口, TTL、RS-232、RS-485区别详解-CSDN博客 1.什么是通信 &#xff08;1&#xff09;人和人之间的通信&#xff1a;说话&#xff…

java项目之校园餐厅管理(ssm源码+文档)

项目简介 校园餐厅管理实现了以下功能&#xff1a; 管理员&#xff1a;个人中心、商家管理、用户管理、菜系类别管理、校园美食管理、在线下单管理、美食论坛、系统管理。商家前台&#xff1a;首页、校园美食、论坛信息、新闻资讯、我的、跳转到后台、客服。商家&#xff1a;…

第4讲:vue内置命令(文本插值,属性绑定,v-text,v-html)

MVVM 什么是MVVM&#xff1f; MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化&#xff0c;让我们将视图 UI 和业务逻辑分开。 View层&#xff1a; 视图层 在我们前端开发中&#xff0c;通常就是 DOM 层。 主要的作用是…

Error: Activity class {xxx.java} does not exist

git切换到不同的branch之后&#xff0c;报下面的错误&#xff1a; Error: Activity class {xxx.java} does not exist 解决方案&#xff1a; 首先clean 然后会删除build目录 然后点击&#xff1a;Invalidate Caches Android Studio重启&#xff0c;然后重新build即可。

四、2023.9.30.C++面向对象end.4

文章目录 49、 简述一下什么是常函数&#xff0c;有什么作用&#xff1f;50、 说说什么是虚继承&#xff0c;解决什么问题&#xff0c;如何实现&#xff1f;51、简述一下虚函数和纯虚函数&#xff0c;以及实现原理&#xff1f;52、说说纯虚函数能实例化吗&#xff0c;为什么&am…

Python实验二

1&#xff1a;编程计算 1&#xff5e;100 偶数的和。 要求&#xff1a;输出结果为整数&#xff0c;宽度为 8&#xff0c;居中对齐&#xff0c;空白处填充符号“*”。 提示&#xff1a; range(2,101,2)生成的整数列表从 2 开始到 100 结束&#xff0c;步长为 2&#xff0c;即 …