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

网络编程(TCP连接)

一、服务器和客户端(单对单)

1、TCP服务器创建流程

1)socket(创建服务器套接字)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建不同类型的套接字用文件描述符
参数 domain:套接字所依赖的网络介质如果是 ipv4 就填入 AF_INET如果是 ipv6 就填入 AF_INET6如果是 域套接字 就填入 AF_LOCAL / AF_UNIX
参数 type:选择套接字的类型SCOK_STREAM:字节流套接字传输数据,连续,可靠,双向,数据量大SOCL_DGRAM:数据包套接字传输数据,不连续,不可靠,有长度要求,双向
参数 protocol:选择套接字所依赖的通信协议0:自动匹配,会根据参数 type 和参数 protocol 自动选择合适的通信协议
返回值:返回创建套接字文件描述符一般来说:AF_INET + SOCK_STREAM + 0 ,最终创建的是 TCP 协议的套接字AF_INET + SOCK_DGRAM  + 0 ,最终创建的是 UDP 协议的套接字 

2)定义struct sockaddr_in类型结构体

若为 ipv6 型的 ip地址 则使用结构体 struct sockaddr_in6

struct sockaddr_in {__kernel_sa_family_t  sin_family; /* 依赖的网络介质       */__be16        sin_port;           /* port端口号           */struct in_addr    sin_addr;       /* ip地址               */
};

3)使用bind函数命名套接字

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:往套接字中写入 ip地址 和 port端口号 (该操作被称为为套接字命名)
参数 sockfd:填写 套接字用文件描述法
参数 addr:通用套接字结构体指针,需要提前准备 struct sockaddr_in 类型的结构体
参数 addrlen:参数 addr 的字节长度

4)listen(设置服务器监听列表)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:将想要连接服务器的客户端加入监听列表,等待客服务器与其连接
参数 sockfd:套接字文件描述符
参数 backlog:监听列表的长度监听列表:所有等待服务器连接的客户端,都会在监听列表等待服务器连接客户端后,会将连接的客户端从监听列表移除若服务器只监听不连接,监听列表被塞满,则不会添加新的客户端到列表

5)accept(接受监听列表中客户端的连接)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:连接监听列表第一个客户端(阻塞函数)
参数 sockfd:接受客户端连接的服务器的套接字
参数 addr:结构体接收已连接的客户端套接字文件中的ip地址和port端口号,若填NULL则不接受连接
参数 addrlen:结构体addr的字节长度
返回值:返回连接的客户端的套接字,没有客户端可连接就阻塞

6)recv | read(接收数据)

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:通过套接字,读取套接字中ip地址所发送的数据
参数 sockfd:由socket创建的套接字文件描述符(要读取的目标)
参数 buf:将读取到的数据存入buf所指向的连续地址(所指向的数组)
参数 len:所读取数据的字节长度
参数 flags:设置函数的状态 阻塞 / 非阻塞0    :默认阻塞,没有接收到数据就阻塞MSG_DONTWAIT:非阻塞,没有读取到数据直接返回 0 
返回值:阻塞模式:返回接收到数据的字节数,若套接字损坏 返回 -1若服务器与客户端连接断开,则有阻塞函数变为非阻塞函数,并返回 0非阻塞模式:返回接收到数据的字节数,若服务器与客户端断开,返回 -1未接收到参数,则返回 0
=====================================================================
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
用法基本一致,IO篇也有详细介绍

7)服务器代码

2、TCP客户端创建流程

 1)socket(创建服务器套接字)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建不同类型的套接字用文件描述符
参数 domain:套接字所依赖的网络介质如果是 ipv4 就填入 AF_INET如果是 ipv6 就填入 AF_INET6如果是 域套接字 就填入 AF_LOCAL / AF_UNIX
参数 type:选择套接字的类型SCOK_STREAM:字节流套接字传输数据,连续,可靠,双向,数据量大SOCL_DGRAM:数据包套接字传输数据,不连续,不可靠,有长度要求,双向
参数 protocol:选择套接字所依赖的通信协议0:自动匹配,会根据参数 type 和参数 protocol 自动选择合适的通信协议
返回值:返回创建套接字文件描述符一般来说:AF_INET + SOCK_STREAM + 0 ,最终创建的是 TCP 协议的套接字AF_INET + SOCK_DGRAM  + 0 ,最终创建的是 UDP 协议的套接字 

2)定义struct sockaddr_in类型结构体

struct sockaddr_in {__kernel_sa_family_t  sin_family; /* 依赖的网络介质       */__be16        sin_port;           /* port端口号           */struct in_addr    sin_addr;       /* ip地址               */
};

 3)使用bind函数命名套接字

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:往套接字中写入 ip地址 和 port端口号 (该操作被称为为套接字命名)
参数 sockfd:填写 套接字用文件描述法
参数 addr:通用套接字结构体指针,需要提前准备 struct sockaddr_in 类型的结构体
参数 addrlen:参数 addr 的字节长度

4)connect(通过套接字申请连接服务器)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:连接套接字文件描述符指向的服务器
参数 sockfd:要连接的服务器的套接字文件描述符
参数 addr:结构体存储要连接的服务器的ip地址和port端口号
参数 addrlen:结构体addr的长度
返回值:成功连接返回 0 ,失败返回 -1

 5)send | write(发送数据)

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:通过套接字,向套接字中指向的ip地址发送数据
参数 sockfd:填入 socket创建的套接字文件描述符(发送的目标)
参数 buf:填入 要发送的数据的地址
参数 len:填入 要发送的字节的长度
参数 flag:设置函数的状态 阻塞 / 非阻塞0    :默认阻塞,发送数据给目标,目标的接收区满了,就会发送阻塞MSG_DONTWAIT:非阻塞,发送数据给目标,接收区满了,丢弃新发送的数据
=======================================================================
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
用法基本一致,IO篇也有详细介绍

6)客户端代码

3、TCP存在的问题

1)TCP协议的执行逻辑

将短时间内连续发送的数据存储在一个1500字节的缓存区,每次发送数据,其实时固定发送了1500个字节,这样的好处时大大的提高了发送数据的效率。这也出现了两个问题

2)粘包问题和截断问题

1. 短时间内连续发送的数据(小于1500字节),会被粘连在一起发送(接收端要拆分)

2. 如果要发送的数据总大小超过1500字节,那么超出1500字节的部分,会被截断,留着下次发送

3)解决粘包问题

#include <my_head.h>enum Type{TYPE_REGIST,TYPE_LOGIN
};typedef struct Pack{int size;enum Type type;char buf[4096];int used;// 记录一下当前pack包的buf用了几个字节
}pack_t;// 写一个函数,读取pack包里面的数据
// 参数 1:等待读取的pack包
char** read_data_from_pack(pack_t* pack){// packsize 是整个包的总大小char* buf = pack->buf;// 创建一个堆空间数组,用于向外返回数据//char** list = malloc(sizeof(char*)*20);char** list = calloc(20,sizeof(char*));// 申请堆空间变初始化int i = 0;int readed_size = 0;// 用来记录buf已经读取了多少个字节while(1){short size = *(short*)(buf+readed_size);//printf("size = %d\n",size);if(size == 0){break;}readed_size += 2;char temp[size+1];memset(temp,0,size+1);strncpy(temp,buf+readed_size,size);readed_size += size;//printf("temp = %s\n",temp);list[i] = calloc(1,size+1);strcpy(list[i],temp);i++;}return list;
}int main(int argc, const char *argv[])
{if(argc < 2){printf("请输入端口号\n");return 1;}short port = atoi(argv[1]);// "abc123" -> 0int server = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET;	addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr("0.0.0.0");if(bind(server,(struct sockaddr*)&addr,sizeof(addr)) == -1){perror("bind");return 1;}listen(server,10);struct sockaddr_in client_addr;int client_len = sizeof(client_addr);int client = accept(server,(struct sockaddr*)&client_addr,&client_len);printf("有客户端连接\n");while(1){int size = 0;int res = read(client,&size,4);// 先读取前4个字节的目的在于:知道一下客户端发来的包到底多大if(res == 0){printf("客户端断开连接\n");return 0;}// 到此为止,我们已经知道了客户端发来的数据包,总共size个字节pack_t pack = {0};pack.size = size;res = read(client,(char*)&pack+4,size-4);//printf("packsize = %d\n",size);//printf("type = %d\n",pack.type);// (char*)&pack+4 含义为:将读取的数据,码放在pack首地址偏移4个字节的位置处// size - 4含义为:整个数据包只有size个字节,前面74行已经读取了4个字节,现在只剩 size - 4个字节可读// 到目前为止,我们只是将客户端发来的数据,读取到了pack数据包里面而已// 还没有解包// 开始解包char** list = read_data_from_pack(&pack);printf("账号 = %s\n",list[0]);printf("密码 = %s\n",list[1]);}return 0;
}

二、服务器和多客户端(单对多)

1、多线程服务器

2、多进程服务器

3、多路文件IO

4、select模型

1)创建 select 模型的监视列表

2)操作 监视列表

3)select 模型的监视函数

4)select 模型代码

5)select 模型的优缺点

5、poll 模型

1)创建 poll 的监视列表

2)操作监视列表

3)poll 模型监视函数

4)poll 模型代码

http://www.dtcms.com/a/278997.html

相关文章:

  • .NET ExpandoObject 技术原理解析
  • LeetCode经典题解:206、两数之和(Two Sum)
  • OV-DINO:基于语言感知选择性融合的统一开放词汇检测
  • 深入解析ThreadLocal:线程隔离利器
  • C#自定义控件
  • c# 深度解析:实现一个通用配置管理功能,打造高并发、可扩展的配置管理神器
  • Typecho加密文章HTML结构自定义完全指南
  • 在 Windows 主机和 VMware 虚拟机中的 Ubuntu 系统之间实现复制粘贴,
  • Spring IoC 容器实战:从解耦到集成的 6 大核心应用场景
  • 【PTA数据结构 | C语言版】字符串插入操作(不限长)
  • 微前端框架深度对决:qiankun、micro-app、wujie 技术内幕与架构选型指南
  • Ubuntu-25.04 Wayland桌面环境安装Anaconda3之后无法启动anaconda-navigator问题解决
  • 如何降低AIGC的查重率?精选六个AIGC降重让论文更出色
  • Spring Boot项目结构解析:构建高效、清晰的代码框架
  • 【Python进阶】深度复制——deepcopy
  • STM32F1_Hal库学习EXTI
  • 苍穹外卖学习指南(java的一个项目)(老师能运行,但你不行,看这里!!)
  • 最近要上Android 15的高通平台,按照之前Android14的高通平台的裁剪APP的方法修改,发现一改编译之后就不能进系统
  • LLaMA.cpp HTTP 服务参数: --pooling 嵌入模型 池化类型详解
  • 笔试——Day7
  • Datawhale AI夏令营大模型 task2.1
  • QML 常用控件(二)
  • Qt小组件 - 3 imageLabel
  • 【CV综合实战】基于深度学习的工业压力表智能检测与读数系统【3】使用OpenCV读取分割后的压力表读数
  • 《C++内存泄漏8大战场:Qt/MFC实战详解 + 面试高频陷阱破解》
  • 机器学习中的朴素贝叶斯(Naive Bayes)模型
  • AI日报 - 2025年07月14日
  • 认识下计算机视觉中的人脸识别
  • 网络准入控制系统的作用解析,2025年保障企业入网安全第一道防线
  • 【邀请函】网易灵动露天矿山具身智能技术发布会,7月26日上海见