网络编程,使用select()进行简单服务端与客户端通信

这里在Ubuntu环境下演示

一般流程

服务端常用函数:

  1. socket():创建一个新的套接字。
  2. bind():将套接字与特定的IP地址和端口绑定。
  3. listen():使套接字开始监听传入的连接请求。
  4. accept():接受一个传入的连接请求,并创建一个新的套接字用于与客户端通信。
  5. send() 或 write():向连接的客户端发送数据。
  6. recv() 或 read():接收来自客户端的数据。
  7. close():关闭与客户端通信的套接字。

流程通常是:

socket() -> bind() -> listen() -> accept() -> send()或write() / recv()或read() -> close() 

客户端常用函数:

  1. socket():创建一个新的套接字。
  2. connect():尝试与服务端建立连接。
  3. send() 或 write():向服务端发送数据。
  4. recv() 或 read():接收来自服务端的数据。
  5. close():关闭与服务端通信的套接字。

流程通常是:

 socket() -> connect ->  send()或write() / recv()或read() -> close() 

简单函数介绍

socket()

socket用于创建一个套接字。

所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口

                                                                                                                ——百度百科

Linux中socket的返回类型是整形,Windows中返回的是SOCKET类型。

    int socket(int af,int type,int protocol);//windows中的返回类型是SOCKET

socket的三个参数:

   //例如一次初始化:

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

   

    //三个参数:
    /*
        (1)地址族:
     * AF_INET表示使用IPv4(Internet Protocol version 4)地址。
     * 它指定了套接字将在IPv4网络上使用。
     * 与之对应的是AF_INET6,用于IPv6地址。
        (2)套接字类型(Socket Type):
     * SOCK_STREAM表示套接字是一个面向连接的、可靠的、基于字节流的套接字,通常用于TCP(Transmission Control Protocol)连接。
     * 与之对应的是SOCK_DGRAM,表示一个无连接的、固定最大长度消息、不可靠的套接字,通常用于UDP(User Datagram Protocol)连接。
        (3)协议:
     * 这个参数通常设置为0,表示让系统自动选择适合指定地址族和套接字类型的协议。
     * 在大多数情况下,对于AF_INET和SOCK_STREAM的组合,系统会选择TCP协议。
     * 同样地,对于AF_INET和SOCK_DGRAM的组合,系统会选择UDP协议。
     */

connect()

connect 函数用于建立客户端和服务器之间的连接。

它属于套接字编程接口,用于将一个套接字与远程服务器的特定套接字地址关联起来,从而初始化一个连接。

connect函数原型

int connect(SOCKET s,const struct sockaddr *name,int namelen);

其中,struct sockaddr的类型:

    struct sockaddr {  sa_family_t sin_family;//地址族char sa_data[14];             }; 

这个类型需要获取到服务器的IP与端口,但是不好进行操作,所以我们要用到跟其作用一致的结构体——struct sockaddr_in:

struct sockaddr_in
{sa_family_t sin_family;//地址族uint16_t sin_port;  //端口struct in_addr sin_addr;  //32位IPchar sin_zero[8];   
}

   //例如,对struct sockaddr_in进行一次初始化

    struct sockaddr_in server_addr;


    //给该结构体清空
    memset(&server_addr, 0, sizeof(server_addr));
    //初始化,要拿到主机的ip和端口
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(ip);

   

那么connect传参类似如下:

if (connect(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Connect failed");
        // 连接失败
    }

send()

send() 是一个用于通过套接字(socket)发送数据的核心函数

send()函数原型:

int send(SOCKET s,const char *buf,int len,int flags);

其中

 //——最后一个参数flags用于控制信息发送方式,这里0是默认发送方式
 //send返回的是实际发送的字节数,失败则返回-1

简单客户端实现代码

客户端实现代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>int tcp_echo_client_start(const char *ip, int port)
{printf("tcp echo client, ip: %s, port:%d\n", ip, port);int s = socket(AF_INET, SOCK_STREAM, 0);//1、创建套接字//如果创建套接字失败if(s < 0){perror("tcp echo client: open socket error");return -1;}//2、用connect函数向服务器建立连接struct sockaddr_in server_addr;//给该结构体清空memset(&server_addr, 0, sizeof(server_addr));//初始化,要拿到主机的ip和端口server_addr.sin_family = AF_INET;server_addr.sin_port = htons(port);server_addr.sin_addr.s_addr = inet_addr(ip);if (connect(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("Connect failed");// 处理错误}//3、从键盘中读取输入,并发送数据char buf[128];printf("please input:");while(fgets(buf, sizeof(buf), stdin) != NULL){//发送,发送给套接字就是交给操作系统去管理if(send(s, buf, strlen(buf), 0) < 0){perror("write error");close(s);return -1;}//服务端收到消息会响应,需要接受数据memset(buf, 0, sizeof(buf));int len = recv(s, buf, sizeof(buf) - 1, 0);if(len < 0)perror("read error");//接受数据并打印printf("%s\n", buf);printf(">>\n");}//关闭close(s);//Windows中采用closesocket()函数return 0;
}int main()
{tcp_echo_client_start("192.168.74.1", 8080);return 0;
}

服务端实现

bind()

bind() 函数用于将一个 套接字(socket) 绑定到特定的 IP 地址和端口号,通常用于服务器端设置监听地址

bind()函数原型:

int bind(int sockfd,                     // 套接字文件描述符(由 socket() 创建)const struct sockaddr *addr,    // 指向 sockaddr 结构体的指针(存储地址信息)socklen_t addrlen               // sockaddr 结构体的长度
);

//成功返回0,失败返回-1

sockfd:

  • 由 socket() 创建的套接字描述符(如 int sockfd = socket(AF_INET, SOCK_STREAM, 0);)。

addr:

  • 指向 struct sockaddr 的指针,存储 IP 地址 + 端口号

  • 实际使用时通常用 struct sockaddr_in(IPv4)或 struct sockaddr_in6(IPv6),并强制转换为 sockaddr*

listen()

listen() 函数用于将 TCP 套接字 设置为 被动监听模式,等待客户端发起连接请求(connect()

#include <sys/socket.h>int listen(int sockfd,     // 已绑定地址的套接字描述符(由 socket() 创建 + bind() 绑定)int backlog     // 允许排队的最大未完成连接数(直接影响并发处理能力)
);

//成功返回0,失败返回-1

sockfd:

  • 必须是已通过 bind() 绑定到某个 IP + 端口 的 流式套接字(如 SOCK_STREAM,即 TCP 套接字)。

  • 如果未绑定,系统会随机分配一个端口(但服务器通常需要固定端口,所以必须显式 bind())。

backlog:

  • 定义 已完成三次握手但未被 accept() 取走的连接队列的最大长度(即“等待处理的连接”)。

  • 不同操作系统对 backlog 的实现有差异,但通常遵循以下规则:

    • Linux:实际队列长度 = min(backlog, /proc/sys/net/core/somaxconn)(默认值通常为 128)。

    • Windows:直接使用 backlog,但最大值由系统限制。

    • 推荐值

      • 高并发服务器:128 或更高(需调整系统参数 somaxconn)。

      • 简单测试:5~10

accept()

accept() 函数用于 从已监听的 TCP 套接字中接受一个客户端的连接请求,并返回一个新的套接字描述符

#include <sys/socket.h>//成功:返回一个新的 套接字描述符(int),专门用于与客户端通信。
//失败:返回-1
int accept(int sockfd,                     // 已调用 listen() 的监听套接字struct sockaddr *addr,          // 用于存储客户端地址信息(可选,可设为 NULL)socklen_t *addrlen              // 客户端地址结构体的长度(输入输出参数)
);

服务端

在Ubuntu中实现的:

#include <sys/socket.h>
#include <error.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>  // 包含close函数的声明
#include <pthread.h>
#include <sys/poll.h>void *client_thread(void *arg)
{int clientfd = *(int *)arg;while(1){char buffer[128] = {0};int count = recv(clientfd, buffer, 128, 0);  if(count == 0){break;}send(clientfd, buffer, count, 0); // 只发送接收到的数据长度printf("clientfd: %d, count: %d, buffer:%s\n", clientfd, count, buffer);  }close(clientfd);  //关闭return NULL;
}int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);//绑定IPstruct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(struct sockaddr_in));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);serveraddr.sin_port = htons(8080);// 使用 `bind` 函数将套接字绑定到指定的地址和端口上。如果绑定失败,程序将打印错误信息并退出。if(-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))){perror("bind");return -1;}listen(sockfd, 10);    //使用 `listen` 函数使套接字进入监听状态,等待传入连接,队列长度设置为 10。fd_set rfds, rset;FD_ZERO(&rfds);    //将其清空FD_SET(sockfd, &rfds);int maxfd = sockfd;while(1) {rset = rfds;int nready = select(maxfd + 1, &rset, NULL, NULL, NULL);//timeout参数设置为NULL,就是一直等待if(FD_ISSET(sockfd, &rset)){struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("accept\n");FD_SET(clientfd, &rfds);if(clientfd > maxfd)maxfd = clientfd;}for(int i = 0; i <= maxfd; i++){if(FD_ISSET(i, &rset) && i != sockfd){//读char buffer[128] = {0};int count = recv(i, buffer, 128, 0);  if(count == 0){printf("disconnect\n");FD_CLR(i, &rfds);close(i);}else{send(i, buffer, count, 0);printf("clientfd: %d, count: %d, buffer:%s\n", i, count, buffer);  }}}}getchar();  // 等待用户输入以便保持程序运行  return 0;
}

演示效果

先编译:

运行server端后,可以查看8080端口,查看其状态

netstat -tulnp | grep 8080

然后在客户端中连接服务端:

在客户端这边使用键盘输入:

客户端断开后,服务端提示:

不过服务端依旧在LISTEN:

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

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

相关文章

智能决策支持系统的基本概念与理论体系

决策支持系统是管理科学的一个分支&#xff0c;原本与人工智能属于不同的学科范畴&#xff0c;但自20世纪80年代以来&#xff0c;由于专家系统在许多方面取得了成功&#xff0c;于是人们开始考虑把人工智能技术用于计算机管理中来。在用计算机所进行的各种管理中&#xff0c;如…

驱动开发系列55 - Linux Graphics QXL显卡驱动代码分析(二)显存管理

一:概述 前面介绍了当内核检测到匹配的PCI设备后,会调用 qxl_pci_probe 初始化设备,其中会调用qxl_device_init 来初始化设备,为QXL设备进行内存映射,资源分配,环形缓冲区初始化,IRQ注册等操作,本文展开说说这些细节,以及介绍下QXL的显存管理。 二:QXL设备初始化细节…

洛谷 P1495:【模板】中国剩余定理(CRT)/ 曹冲养猪

【题目来源】 https://www.luogu.com.cn/problem/P1495 https://www.acwing.com/problem/content/225/ 【题目描述】 自从曹冲搞定了大象以后&#xff0c;曹操就开始捉摸让儿子干些事业&#xff0c;于是派他到中原养猪场养猪。可是曹冲满不高兴&#xff0c;于是在工作中马马虎…

配置和使用持久卷

配置和使用持久卷 文章目录 配置和使用持久卷[toc]一、PV与PVC的持久化存储机制二、PV和PVC的生命周期三、创建基于NFS的PV1.准备NFS共享目录2.创建PV 四、基于PVC使用PV1.创建PVC2.使用PVC 五、基于StorageClass实现动态卷制备1.获取NFS服务器的连接信息2.获取nfs-subdir-exte…

FreeRTOS菜鸟入门(十)·消息队列

目录 1. 基本概念 2. 数据存储 3. 运作机制 4. 阻塞机制 4.1 出队阻塞 4.2 入队阻塞 5. 操作示意图 5.1 创建队列 5.2 向队列发送第一个消息 5.3 向队列发送第二个消息 5.4 从队列读取消息 6. 消息队列控制块 7. 消息队列常用函数 7.1 消息队列创建…

java 洛谷题单【算法2-2】常见优化技巧

P1102 A-B 数对 解题思路 输入读取与初始化&#xff1a; 使用 Scanner 读取输入。n 表示数组的长度&#xff0c;c 表示目标差值。使用一个 HashMap 存储数组中每个数字及其出现的次数&#xff0c;方便快速查找。数组 a 用于存储输入的数字。 构建哈希映射&#xff1a; 遍历数…

视频转GIF

视频转GIF 以下是一个使用 Python 将视频转换为 GIF 的脚本&#xff0c;使用了 imageio 和 opencv-python 库&#xff1a; import cv2 import imageio import numpy as np """将视频转换为GIF图参数:video_path -- 输入视频的路径gif_path -- 输出GIF的路径fp…

计算机网络:详解TCP协议(四次握手三次挥手)

目录 1.Tcp协议介绍 1.1 Tcp协议层级 1.2 TCP协议的格式 2. 确认应答机制 2.1 确认应答 2.2 序号字段 2.3 捎带应答 3. 流量控制 4. 三次握手 四次挥手 4.1 认识标志位 4.2 简单认识 4.3 三次挥手 4.4 四次挥手 1.Tcp协议介绍 1.1 Tcp协议层级 计算机网络&#x…

小程序 IView WeappUI组件库(简单增删改查)

IView Weapp 微信小程序UI组件库&#xff1a;https://weapp.iviewui.com/components/card IView Weapp.png 快速上手搭建 快速上手.png iView Weapp 的代码 将源代码下载下来&#xff0c;然后将dict放到自己的项目中去。 iView Weapp 的代码.png 小程序中添加iView Weapp 将di…

用java实现一个简单的sql select 解析器,无需第三方依赖,完全从0开始

以下是一个简单的 SQL SELECT 解析器的 Java 实现&#xff0c;支持单表查询和基本条件过滤。代码包含词法分析和语法分析模块&#xff0c;并支持以下语法&#xff1a; SELECT column1, column2 FROM table WHERE column3 5 完整代码 1. Token 类型定义 (TokenType.java) pu…

阿里云 CentOS YUM 源配置指南

阿里云 CentOS YUM 源配置指南 在使用 CentOS 7 时&#xff0c;由于 CentOS 官方源停止维护等原因&#xff0c;yum install 命令可能会报错 “Cannot find a valid baseurl for repo: centos-sclo-rh/x86_64”。以下是通过更换阿里云源解决该问题的详细步骤。 一、备份原有配…

Learning vtkjs之ThresholdPoints

过滤器 阈值过滤器 介绍 vtkThresholdPoints - 提取满足阈值条件的点 vtkThresholdPoints 是一个过滤器&#xff0c;它从数据集中提取满足阈值条件的点。该条件可以采用三种形式&#xff1a; 1&#xff09;大于特定值&#xff1b; 2) 小于特定值&#xff1b; 3) 在特定值之间…

记录ruoyi-flowable-plus第一次运行流程报错

记录ruoyi-flowable-plus第一次运行流程报错 错误步骤 1.启动ruoyi-flowable-plus 正常登录后&#xff0c;打开流程分类然后点击新增按钮&#xff0c;新增了一个分类。增加成功后&#xff0c; 再点击流程分类&#xff0c;报错。 错误提示 org.springframework.cglib.core.C…

Java中的stream流介绍与使用

一、Stream 的基础概念 定义与特性 Stream 是单向数据流&#xff0c;对集合或数组进行高效处理&#xff0c;不存储数据&#xff0c;而是通过操作链生成新 Stream。不可变性&#xff1a;原始数据源不被修改&#xff0c;所有操作均返回新 Stream。延迟执行&#xff1a;中间操作&a…

OCR身份证识别(正反面)_个人证照OCR识别_开放API接口使用指南

一、接口简介 在数字化时代&#xff0c;快速准确地提取身份证信息变得尤为重要。**万维易源提供的“身份证OCR识别”API接口&#xff0c;能够快速提取二代居民身份证正反面的所有字段信息&#xff0c;包括姓名、性别、民族、出生日期、住址、身份证号、签发机关、有效期限等。…

25年新版潮乎盲盒系统源码 盲盒商城系统前端分享

盲盒系统市场的前景一直都很不错&#xff0c;最近很多问我有没有盲盒源码的客户&#xff0c;下面给大家分享一个新版潮乎盲盒源码&#xff01; 这款盲盒源码系统 前端Uniapp 后端使用了Laravel框架进行开发。Laravel是一个流行的PHP框架&#xff0c;具有强大的功能和易于使用的…

Transformer四模型回归打包(内含NRBO-Transformer-GRU、Transformer-GRU、Transformer、GRU模型)

Transformer四模型回归打包&#xff08;内含NRBO-Transformer-GRU、Transformer-GRU、Transformer、GRU模型&#xff09; 目录 Transformer四模型回归打包&#xff08;内含NRBO-Transformer-GRU、Transformer-GRU、Transformer、GRU模型&#xff09;预测效果基本介绍程序设计参…

Axure疑难杂症:利用中继器制作三级下拉菜单(逻辑判断进阶)

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:三级下拉菜单 主要内容:条件筛选时的逻辑判断思维,中继器使用 应用场景:复合条件下的下拉列表制作 案例展…

Nginx 核心功能之正反代理

目录 一、Nginx 二、正向代理 三、反向代理 四、Nginx 缓存 1. 缓存功能的核心原理和缓存类型 2. 代理缓存功能设置 五、Nginx rewrite和正则 &#xff08;1&#xff09;Nginx 正则 &#xff08;2&#xff09;nginx location &#xff08;3&#xff09;Rewrite &…

ssh连接云服务器记录

文章目录 1. 背景2. ssh连接2.1 win 下通过终端工具进行连接2.2 Linux下通过ssh指令连接2.3 ssh使用publickey来连接 ssh连接云服务器记录 1. 背景 最近开始接触docker技术、mysql技术&#xff0c;加上本人工作基本都在Linux下进行&#xff0c;因此需要一套Linux环境进行练习。…