当前位置: 首页 > article >正文

TCP网络编程(一)—— 服务器端模式和客户端模式

这篇文章将会编写基本的服务器网络程序,主要讲解服务器端和客户端代码的原理,至于网络名词很具体的概念,例如什么是TCP协议,不会过多涉及。

首先介绍一下TCP网络编程的两种模式:服务器端和客户端模式:

        首先说明一下:黑色线代表状态的转换,红色线表示的是数据的传输,read 和 write 之间的循环表示:例如读取完数据,进入写入的状态,写入完再进入读取的状态,一直循环,实现了服务器和客户端之间的通信。


首先来解释一下服务器端:

int socket(int domain, int type, int protocol)

socket() 表示创建一个套接字。套接字是网络通信的基本数据结构,用于定义通信协议(如 TCP 或 UDP)和地址族(如 IPv4 或 IPv6)。通过套接字,服务器和客户端可以在网络上传输数据,可以把套接字理解为一个编程接口,利用套接字实现程序和网络的连接,像是用户层和传输层(TCP)中间的一个抽象层,有了套接字才可以向网络发送数据。

传入的内容是(协议族,套接字类型,默认协议(通常为0))
返回:成功返回套接字描述符,失败返回-1 

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

bind() 表示将套接字绑定到一个特定的地址和端口。绑定的地址和端口标识服务器,使客户端能够找到并连接到该服务。只有套接字还不够,我还要知道是哪个主机(IP)发送的,哪个应用程序(端口)发送的,端口可以理解为电脑通信的入口和出口。

传入的内容是:(套接字描述符,地址结构体的地址,地址结构体大小)
返回:成功返回0,失败返回-1。

int listen(int sockfd, int backlog)

listen() 表示将套接字转换为监听模式,并设置等待连接的队列长度。当多个客户端请求连接时,服务器会将这些请求加入队列,按顺序处理。

传入的内容是(套接字描述符,队列的长度)
返回:成功返回0,失败返回-1。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

accept() 表示等待接受客户端的连接请求,接收到请求,成功连接后,accept() 返回一个新的套接字,用于与该客户端通信,而原始监听套接字则继续处理新的连接请求。

传入的内容是:(套接字描述符,地址结构体的地址,地址结构体大小的地址)
返回:成功返回新的套接字描述符,失败返回-1 

ssize_t read(int sockfd, void *buf, size_t count)

read() 表示从套接字描述符中读取数据,用于接收客户端发送的消息。读取的数据存储在提供的缓冲区中。

传入的内容是:(套接字描述符,缓冲区指针(数组),要读取的字节数)
返回:成功返回实际读取的字节数,失败返回-1。

ssize_t write(int sockfd, const void *buf, size_t count)

write() 表示向套接字描述符中写入数据,用于向客户端发送响应数据。

传入的内容是:(套接字描述符,缓冲区指针(数组),要写入的字节数)
返回:成功返回实际写入的字节数,失败返回-1。

int close(int sockfd)

close()表示关闭套接字描述符。

传入的内容是:(套接字描述符)
返回:成功返回0,失败返回-1。 


接着解释一下客户端的新出现的函数:

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connet()表示客户端向服务器发起连接请求。客户端告诉操作系统需要连接到哪个服务器的哪个端口。

传入的内容是:(套接字描述符,地址结构体的地址,地址结构体大小)
返回:成功返回0,失败返回-1。

看完这些,你会发现:套接字描述符和文件描述符很像,都可以根据描述进行写入读取和各种其他操作,其实,这就是UNIX系统和类UNIX系统(Linux系统)的抽象资源管理方式,通过整数来标识系统中的资源,使用统一的接口设计,“一切皆文件”。

看到这里,你一定有几个问题:

1.为什么客户端少了bind()和listen()的操作?

2.为什么connect操作指向了accept操作之后?

3.地址结构体的地址addr是个什么东西?

4.为什么有的函数传addr大小,有的传addr大小的地址?

1.对于服务器端来说,服务器需要绑定到固定的端口这样客户端才能知道它,对于客户端来说,操作系统会在必要的时候分配临时的本地端口和地址,不需要再绑定端口。

2.因为服务器端的accept函数是阻塞的,等待客户端发起请求,当connect发送给服务器端请求之后,才会继续进行后面的读写操作。

3.addr的类型如下:有两个成员,分别是地址族,地址和端口信息,但是这不方便我们进行设置,所以一般采用 sockaddr_in 这个结构,最后在进行强制类型转换得到sockaddr,注意这两个结构体类型大小是一样的,只是结构不一样。

struct sockaddr {
    sa_family_t sa_family;    // 地址族,例如 AF_INET(IPv4)或 AF_INET6(IPv6)
    char sa_data[14];         // 地址和端口信息
};

下面是sockaddr_in结构体类型,可以清楚地看到每个成员的含义:

struct sockaddr_in {
    sa_family_t sin_family;   // 地址族,通常为 AF_INET(IPv4)
    uint16_t sin_port;        // 端口号(16 位),以网络字节序表示
    struct in_addr sin_addr;  // IP 地址(32 位)
    char sin_zero[8];         // 保留字段,填充用
};

4.可以看到accept函数的addrlen参数是 addr 大小(变量)的地址,但是connect和bind函数的addrlen参数是 addr 大小(变量)本身,这是因为accept不知道调用者提供的 addr 缓冲区的大小可能是IPv4可能是IPv6,所以需要地址地址。

我猜测可能和TCP的三次握手或者accept返回新的套接字或者客户端分配动态端口有关系,而connect和bind函数都是用已知的套接字进行操作,所以不会进行addr大小的改变,所以可以直接传值。

这就是TCP编程的两种模式,从下篇文章开始,我们将学习如何编写服务器端和客户端的代码。

这就是文章的所有内容了,希望对你有所帮助,如有错误欢迎指出。


http://www.kler.cn/a/457820.html

相关文章:

  • VBA 64位API声明语句第005讲
  • mysql连接时报错1130-Host ‘hostname‘ is not allowed to connect to this MySQL server
  • Maven项目集成SQL Server的完整教程:从驱动配置到封装优化
  • ES IK分词器插件
  • Dubbo扩展点加载机制
  • Spring Boot 3.3.4 升级导致 Logback 之前回滚策略配置不兼容问题解决
  • NestJS 微服务架构:从单体到分布式
  • 【开源免费】基于SpringBoot+Vue.JS租房管理系统(JAVA毕业设计)
  • 客户案例:基于慧集通平台集成打通小满CRM+金蝶云星空+钉钉
  • 2024年数字政府服务能力优秀创新案例汇编(附下载)
  • ubuntu卸载docker
  • 闲谭Scala(2)--安装与环境配置
  • 【Golang 面试题】每日 3 题(七)
  • 对话 Project Astra 研究主管:打造通用 AI 助理,主动视频交互和全双工对话是未来重点
  • 【数字化】华为一体四面细化架构蓝图
  • coturn docker 项目 搭建【一切正常】
  • MySql幻读问题
  • 工业大数据分析算法实战-day19
  • AE Dressler CESAR 1312 Generator Model User Manual
  • 健身房运动锻炼环境音效、健身房器材、健身房环境、过渡、电影制作后期音效 OcularSounds - CINEMATIC GYM SOUND FX
  • 高性能网络框架--fstack
  • Git如何添加子仓库
  • AE/PR/达芬奇模板:自动光标打字机文字标题移动效果动画模板预设
  • Ubuntu系统部署Mysql8.0后设置不区分大小写
  • openfeign-一些配置
  • 异步爬虫之协程的基本原理