【计算机网络】TCP和UDP的封装以及案例

TCP和UDP的封装以及案例

  • 背景知识
  • TCP实现
  • UDP实现
  • 封装Network
  • 用NetWork再次实现TCP和UDP
  • 小知识点

背景知识

TCP:传输控制协议(Transmission Control Protocol)
UDP:用户数据报协议 (User Datagram Protocol)
底层遵循TCP\IP协议,在系统层上以Socket接口方式呈现
在这里插入图片描述
使用到的函数

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

功能:创建socket对象
domain:指定协议族
AF_INET 基于IPv4地址通信(常用)
AF_INET6 基于IPv6地址通信
AF_UNIX 基于本地通信
type:指定套接字类型
SOCK_STREAM 数据流协议 TCP
SOCK_DGRAM 数据报协议 UDP
protocol:指定协议
IPPROTO_TCP(TCP协议)
IPPROTO_UDP(UDP协议)
写0可以不指定(常用)

//网络地址结构体类型
#include <netinet/in.h>
struct sockaddr_in {__kernel_sa_family_t sin_family; // AF_INET__be16 sin_port;           // 端口号  大端数据 用htons转struct in_addr sin_addr;   // IP地址 大端数据  用inet_addr转
};  
struct in_addr {__be32  s_addr;
};

192.168.1.23
192<24|168<16|1<8|23<0
点分十进制的小端转大端

大小端数据转换函数:

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
//功能:把4字节的本地字节序转换成网络字节序
uint16_t htons(uint16_t hostshort);
//功能:把2字节的本地字节序转换成网络字节序 (常用来给端口号小端转大端)
uint32_t ntohl(uint32_t netlong);
//功能:把4字节的网络字节序转换成本地字节序
uint16_t ntohs(uint16_t netshort);
//功能:把2字节的网络字节序转换成本地字节序

ip地址转换函数:

in_addr_t inet_addr(const char *cp);

功能:把字符串格式的点分十进制表示的ip地址转换成整数形式的ip地址(大端)

char *inet_ntoa(struct in_addr in);

功能:把整数形式的ip地址转换成字符串格式的点分十进制表示的ip地址

一个小demo,测试一下小端转大端的效果

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main(int argc, char** argv) 
{struct in_addr a;char b[16] = "192.168.1.6";int e = inet_aton(b, &a); //将b指向的字符串,转换成为in_addr 对应的ip地址printf("ret val = %d\n", e); //返回1为成功,0为失败printf("a=%#x\n",a.s_addr);printf("htonl(a)=%#x\n",htonl(a.s_addr)); //将32bit的数据从 主机字节序 转换为 网络字节序(小端->大端)printf("htons(a)=%#x\n",htons(a.s_addr)); //将16bit的数据从 主机字节序 转换为 网络字节序char *c = inet_ntoa(a);将 in_addr类型的ip地址转换成诸如 “192.168.1.66”格式的ip地址printf("c=%s\n", c);return 0;
}

在这里插入图片描述
网络地址相关结构体,函数

int listen(int sockfd, int backlog);

功能:监听socket,数据流通信时使用
sockfd:socket描述符
backlog:等待连接socket的排队数量,默认最大128
返回值:成功返回0,失败返回-1

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

功能:等待客户端连接
sockfd:受监听的socket描述符
addr:获取客户端的地址
addrlen:既是输入,也是输出 这里要取地址!!!
1、既告诉accept函数当前计算机地址结构体的字节数
2、同时也能获取客户端的地址结构体字节数
返回值:连接成功返回一个新的连接后的socket描述符,连接失败返回-1
注意:如果没有连接,则阻塞

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

功能:连接服务器
sockfd:socket描述符
addr:服务器的公网ip地址结构体指针
addrlen:地址结构体的字节数,用于区分 sockaddr_un还是sockaddr_in
功能:成功返回0,失败返回-1

注意:TCP收发数据可以继续使用read、write

ssize_t send(int sockfd,const void *buf, size_t len, int flags);

功能:TCP协议通信时专用的数据发送函数
sockfd:连接成功的socket描述符
buf:待发送数据的首地址
len:要发送的字节数
flags
0 阻塞发送(常用)
1 不阻塞发送
返回值:成功发送的字节数
-1 出现错误
0 连接断开

  ssize_t recv(int sockfd,void *buf,size_t len, int flags);

功能:TCP协议通信时专用的接收数据函数
sockfd:连接成功的socket描述符
buf:存储数据缓冲区内存首地址
len:缓冲区的大小
flags
0 阻塞接收(常用)
1 不阻塞接收
返回值:成功接收的字节数
-1 出现错误
0 连接断开

在这里插入图片描述

  ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

功能:UDP协议发送数据
sockfd:socket描述符
buf:待发送数据内存首地址
len:待发送数据的字节数
flags:是否阻塞 一般写0阻塞即可
dest_addr:通信目标的地址
addrlen:地址结构体的字节数
返回值:成功发送的字节数
0 通信关闭
-1 出现错误

   ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

功能:UDP协议接收数据
sockfd:socket描述符
buf:存储接收数据的缓冲区内存首地址
len:缓冲区的字节数
flags:是否阻塞 一般写0阻塞即可
src_addr:用于存储发送者的地址
addrlen:既是输入,也是输出
1、既告诉函数当前src_addr结构体的字节数
2、同时也能实际接收到发送者的地址结构体字节数
成功:成功接收到的字节数
0 通信关闭
-1 出现错误

TCP实现

TCP服务端

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>typedef struct sockaddr* SP;//服务端void server(int cli_fd){char buf[4096] = {};size_t buf_size = sizeof(buf);for(;;){//接收数据//	int ret = read(cli_fd,buf,buf_size);int ret = recv(cli_fd,buf,buf_size,0);if(ret <= 0 || strcmp("quit",buf) == 0){printf("客户端%d退出\n",cli_fd);break;}printf("form %d recv:%s bits:%d\n",cli_fd,buf,ret);//响应请求//把传过来的数据拼接“:return”后返回客户端strcat(buf,":return");
//		ret = write(cli_fd,buf,strlen(buf)+1);ret = send(cli_fd,buf,strlen(buf)+1,0);if(ret <= 0){printf("客户端%d退出",cli_fd);break;}}//关闭close(cli_fd);exit(0);
}int main(int argc, const char* argv[])
{//创建套接字int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){perror("socket");return EXIT_FAILURE;}//准备自己的地址struct sockaddr_in addr = {};addr.sin_family = AF_INET;addr.sin_port = htons(9997);addr.sin_addr.s_addr = inet_addr("127.0.0.1");//绑定socklen_t addrlen = sizeof(addr);if(bind(sockfd,(SP)&addr,addrlen)){perror("bind");return EXIT_FAILURE;}//监听if(listen(sockfd,5)){perror("listen");return EXIT_FAILURE;}for(;;){//等待连接struct sockaddr_in src_addr = {};int cli_fd = accept(sockfd,(SP)&src_addr,&addrlen);if(cli_fd < 0){perror("accept");continue;}//创建进程服务if(fork() == 0){server(cli_fd);		}}return 0;
}

TCP客户端

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>typedef struct sockaddr* SP;//客户端int main(int argc, const char* argv[])
{//创建套接字int cli_fd = socket(AF_INET,SOCK_STREAM,0);if(cli_fd < 0){perror("socket");return EXIT_FAILURE;}//准备自己的地址struct sockaddr_in addr = {};addr.sin_family = AF_INET;addr.sin_port = htons(9997);addr.sin_addr.s_addr = inet_addr("127.0.0.1");socklen_t addrlen = sizeof(addr);//连接服务器if(connect(cli_fd,(SP)&addr,addrlen)){perror("connect");return EXIT_FAILURE;}char buf[4096] = {};size_t buf_size = sizeof(buf);for(;;){printf(">>>>");scanf("%s",buf);//发送数据//	int ret = write(cli_fd,buf,strlen(buf)+1);int ret = send(cli_fd,buf,strlen(buf)+1,0);if(ret <= 0){printf("服务器正在升级,请稍后重试\n");break;}if(strcmp("quit",buf) == 0){printf("结束通信\n");break;}//接收响应
//		ret = read(cli_fd,buf,sizeof(buf));ret = recv(cli_fd,buf,sizeof(buf),0);if(ret <= 0){printf("服务器正在升级,请稍候重试\n");break;}printf("read:%s bits:%d \n",buf,ret);}//关闭close(cli_fd);return 0;
}

代码编译

gcc 01tcp.c -o server
gcc 02tcp.c -o client

运行效果
在这里插入图片描述

UDP实现

UDP服务端

UDP客户端

代码编译
运行效果

封装Network

从上述的操作可以看出,TCP和UDP存在很多重复代码,如何后续需要搭建服务器,那么我们可以将重复的部分封装成一个共享库,方便后续调用实现

network.h头文件

#ifndef NETWORK_H
#define NETWORK_H
#include <stdio.h>
#include <stdbool.h>
#include <netinet/in.h>typedef struct NetWork
{int type; //通信协议的类型 TCP/UDPint sock_fd; //socket描述符struct sockaddr_in addr; //通信地址socklen_t addrlen; //通信地址字节数bool issvr; //判断是否是服务器
}NetWork;typedef struct sockaddr *SP;//分配内存,创建socket对象,初始化地址,绑定,监听,连接
NetWork *init_nw(int type,short port,const char *ip,bool issvr);//等待连接,只有type是SOCK_STREAM且是服务器才调用
NetWork *accept_nw(NetWork *svr_nw);//send或sendto
int send_nw(NetWork *nw,void *buf,size_t len);//recv或recvfrom
int recv_nw(NetWork *nw,void *buf,size_t len);//关闭socket对象,且释放资源
void close_nw(NetWork *nw);//获取ip地址
const char *getip_nw(NetWork *nw);#endif//NETWORK_H

network.c

#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "network.h"//分配内存,创建socket对象,初始化地址,绑定,监听,连接
NetWork *init_nw(int type,short port,const char *ip,bool issvr)
{//分配内存NetWork *nw = malloc(sizeof(NetWork));nw->sock_fd = socket(AF_INET,type,0);if(-1 == nw->sock_fd){free(nw);perror("socket");return NULL;}nw->addrlen = sizeof(struct sockaddr_in);bzero(&nw->addr,nw->addrlen);nw->addr.sin_family = AF_INET;nw->addr.sin_port = htons(port);nw->addr.sin_addr.s_addr = inet_addr(ip);nw->type = type;nw->issvr = issvr;if(issvr){//是服务器//TCP和UDP都需要绑定bindif(bind(nw->sock_fd,(SP)&nw->addr,nw->addrlen)){free(nw);perror("bind");return NULL;}//TCP需要监听listenif(SOCK_STREAM == type && listen(nw->sock_fd,50)){free(nw);perror("listen");return NULL;}}else if(SOCK_STREAM == type){//是客户端//TCP需要connect,UDP不需要任何操作if(connect(nw->sock_fd,(SP)&nw->addr,nw->addrlen)){free(nw);perror("connect");return NULL;}}return nw;
}//等待连接,TCP需要accept,UDP不需要任何操作
NetWork *accept_nw(NetWork *svr_nw)
{//为新的network分配内存并初始化NetWork *nw = malloc(sizeof(NetWork));nw->addrlen = svr_nw->addrlen;nw->type = SOCK_STREAM;nw->issvr = true;nw->sock_fd = accept(svr_nw->sock_fd,(SP)&nw->addr,&nw->addrlen);if(nw->sock_fd < 0){free(nw);perror("accept");return NULL;}return nw;
}//send或sendto
int send_nw(NetWork *nw,void *buf,size_t len)
{//TCPif(SOCK_STREAM == nw->type){return send(nw->sock_fd,buf,len,0);}//UDPelse{return sendto(nw->sock_fd,buf,len,0,(SP)&nw->addr,nw->addrlen);}
}//recv或recvfrom
int recv_nw(NetWork *nw,void *buf,size_t len)
{//TCPif(SOCK_STREAM == nw->type){return recv(nw->sock_fd,buf,len,0);}//UDPelse{return recvfrom(nw->sock_fd,buf,len,0,(SP)&nw->addr,&nw->addrlen);}
}//关闭socket对象,且释放资源
void close_nw(NetWork *nw)
{close(nw->sock_fd);free(nw);
}//获取ip地址
const char *getip_nw(NetWork *nw)
{return inet_ntoa(nw->addr.sin_addr);
}

用NetWork再次实现TCP和UDP

TCP服务端

#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include "network.h"
//TCP服务端int main(int argc, const char* argv[])
{//init_nw(类型,端口号,IP,是否服务器)NetWork *svr_nw = init_nw(SOCK_STREAM,atoi(argv[2]),argv[1],true);if(NULL == svr_nw){perror("init_nw");return EXIT_FAILURE;}//TCP是一对一连接的,所以要么父子进程,要么多线程才可以NetWork *nw = NULL;do{nw = accept_nw(svr_nw);if(NULL == nw){perror("accept_nw");close_nw(nw);return EXIT_FAILURE;}}while(fork());char buf[4096] = {};for(;;){int ret = recv_nw(nw,buf,sizeof(buf));if(ret <= 0 || 0 == strcmp("quit",buf)){printf("客户端%d退出\n",nw->sock_fd);close_nw(nw);break;}printf("form:%d recv_nw:%s bits:%d\n",nw->sock_fd,buf,ret);strcat(buf,":return");ret = send_nw(nw,buf,strlen(buf)+1);if(ret <= 0){printf("客户端%d退出\n",nw->sock_fd);close_nw(nw);break;}}close_nw(svr_nw);return 0;
}

TCP客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include "network.h"int main(int argc, const char* argv[])
{NetWork *nw = init_nw(SOCK_STREAM,atoi(argv[2]),argv[1],false);if(NULL == nw){perror("init_nw");return EXIT_FAILURE;}char buf[4096] = {};for(;;){printf(">>>>");scanf("%s",buf);int ret = send_nw(nw,buf,strlen(buf)+1);if(ret <= 0){printf("服务器正在升级,请稍后重试\n");break;}if(0 == strcmp("quit",buf)){printf("结束通信\n");break;}bzero(buf,sizeof(buf));ret = recv_nw(nw,buf,sizeof(buf));if(ret <= 0){printf("服务器正在升级,请稍后重试\n");break;}printf("read:%s bits:%d\n",buf,ret);}close_nw(nw);return 0;
}

代码编译

gcc tcp_s_nw.c network.c -o tcpS
gcc tcp_c_nw.c network.c -o tcpC

运行效果
在这里插入图片描述

小知识点

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

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

相关文章

AI的欺骗游戏:揭示多模态大型语言模型的易受骗性

人工智能咨询培训老师叶梓 转载标明出处 多模态大型语言模型&#xff08;MLLMs&#xff09;在处理包含欺骗性信息的提示时容易生成幻觉式响应。尤其是在生成长响应时&#xff0c;仍然是一个未被充分研究的问题。来自 Apple 公司的研究团队提出了MAD-Bench&#xff0c;一个包含8…

网站打不开怎么办,收藏以备不时之需

DNS设置示范教程 部分地区有使用移动网络的小伙伴们吐槽无法访问部分网站的情况&#xff0c;同样的网站&#xff0c;使用电信和联通的用户就能正常访问。 这其实有很大几率是由于运营商的网络问题导致的&#xff0c;容易出现网站打不开的结果。 要解决移动网络无法访问的情况…

(面试必看!)一些和多线程相关的面试考点

文章导读 引言考点1. CAS 指令&#xff08;重点&#xff09;一、什么是CAS二、CAS 的优点三、CAS 的缺点四、ABA问题五、相关面试题 考点2. 信号量&#xff08;semaphore&#xff09;一、基本概念二、信号量的主要操作三、信号量的应用四、相关面试题 考点3、CountDownLatch 类…

DHCP笔记

DHCP---动态主机配置协议 作用&#xff1a;为终端动态提供IP地址&#xff0c;子网掩码&#xff0c;网关&#xff0c;DNS网址等信息 具体流程 报文抓包 在DHCP服务器分配iP地址之间会进行广播发送arp报文&#xff0c;接收IP地址的设备也会发送&#xff0c;防止其他设备已经使用…

卓码软件测评:软件功能测试和非功能测试详情介绍

随着信息技术的不断发展&#xff0c;软件在我们日常生活与工作中扮演着越来越重要的角色。然而&#xff0c;软件质量的好坏直接关系到使用者的体验和企业的声誉。在软件开发过程中&#xff0c;功能测试和非功能测试作为保证软件质量的重要手段&#xff0c;受到了越来越多的关注…

古文:文天祥《正气歌》

原文 正气歌 【作者】文天祥 【朝代】宋 余囚北庭&#xff0c;坐一土室。室广八尺&#xff0c;深可四寻。单扉低小&#xff0c;白间短窄&#xff0c;污下而幽暗。当此夏日&#xff0c;诸气萃然&#xff1a;雨潦四集&#xff0c;浮动床几&#xff0c;时则为水气&#xff1b;涂泥…

YAML 语法规范

文章目录 YAML 语法规范一、简介二、基本语法三、高级语法四、示例解析五、注意事项YAML 语法规范 一、简介 YAML(YAML Ain’t Markup Language)是一种专门用来写配置文件的语言,具有简洁、易读、易解析等特点。YAML的设计理念是为人类和机器之间的沟通提供一种更加直观、…

Chiplet SPI User Guide 详细解读

目录 一. 基本介绍 1.1.整体结构 1.2. 结构细节与功能描述 二. 输入输出接口 2.1. IO Ports for SPI Leader 2.2. IO Ports for SPI Follower 2.3. SPI Mode Configuration 2.4. Leader IP和Follower IP功能图 三. SPI Programming 3.1. Leader Register Descripti…

基于FPGA的数字信号处理(19)--行波进位加法器

1、10进制加法是如何实现的&#xff1f; 10进制加法是大家在小学就学过的内容&#xff0c;不过在这里我还是帮大家回忆一下。考虑2个2位数的10进制加法&#xff0c;例如&#xff1a;15 28 43&#xff0c;它的运算过程如下&#xff1a; 个位两数相加&#xff0c;结果为5 8 1…

苹果 iCloud 钥匙串是什么?如何查看及对其进行设置?

在当今的数字世界中安全性和便利性是人们关注的两大重点。无论是社交媒体账户、还是网购平台等&#xff0c;几乎每个在线服务都需要登录账户。如何安全地管理和存储这些账户密码成为了用户们的一大挑战。 iCloud 钥匙串 我们先来看一看什么是 iCloud 钥匙串&#xff0c;iClou…

Redis:事务

1. 简介 可以一次性执行多个命令&#xff0c;本质是一组命令的集合。一个事务中的所有命令都会序列化&#xff0c;按顺序的串化执行&#xff0c;不允许被其他其他命令插入&#xff0c;不许加塞 即将要执行的命令放入队列中&#xff0c;此时该队列的所有命令就是一个事务&#x…

浏览器同源策略详解、主流的跨域解决方案、深入理解跨域请求概念及其根因

1. 什么是同源策略 跨域问题其实就是浏览器的同源策略造成的。 同源策略限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的重要的安全机制。同源指的是&#xff1a;协议、端口号、域名必须一致。 下表给出了与 URL http…

如何让你的C语言程序打印的log多一点色彩?(超级实用)

接着上一篇文章《由字节对齐引发的一场“血案“ 》 在平常的调试中&#xff0c;printf字体格式与颜色都是默认一致的。 如果可以根据log信息的重要程度&#xff0c;配以不同的颜色与格式&#xff0c;可以很方便的查找到要点。 1、printf字体显示语法说明 printf(“\033[显示…

Hive环境搭建(内置数据库)

实验目的】 1) 了解hive的作用 2) 熟练hive的配置过程&#xff08;内置数据库&#xff09; 【实验原理】 Hive的架构是由Client、Metastore、Driver、Compiler构成&#xff0c;执行流程是编译器可以将一个Hive QL转换成操作符&#xff0c;操作符是Hive中的最小处理单元。…

如何查看操作系统的性能指标:CPU、内存、磁盘、网络

目录 本系列专栏 CPU篇 CPU使用率&#xff1a;top CPU负载&#xff1a;uptime CPU核心使用情况&#xff1a;mpstat -P ALL 1 上下文切换&#xff1a;vmstat 1 CPU等待 IO时长&#xff1a;iostat -x 1 CPU的频率&#xff1a;lscpu 或者 cat /proc/cpuinfo | grep "cpu MHZ…

oracle读写时相关字符集详解

服务器端操作系统&#xff08;Oracle linux&#xff09;字符集 服务器端数据库字符集 客户端操作系统&#xff08;Oracle linux&#xff09;字符集 客户端工具sqlplus字符集 结论1&#xff1a;客户端工具sqlplus的会话&#xff0c;使用的字符集&#xff0c;是数据库字符集。…

Spring三级缓存是如何作用的

什么是三级缓存 singletonObjects&#xff1a; 一级缓存&#xff0c;用于保存实例化、注入、初始化完成的bean实例【完全体】earlySingletonObjects&#xff1a; 二级缓存&#xff0c;用于保存实例化完成的bean实例singletonFactories&#xff1a; 三级缓存&#xff0c;用于保…

java面向对象总结

java面向对象篇到这里就已经结束了&#xff0c;有什么不懂的地方可以逐一进行重新观看。希望大家能够从入门到起飞。 Java面向对象基础篇综合训练&#xff08;附带全套源代码及逐语句分析&#xff09;-&#xff1e;基于javabeen Java面向对象进阶篇综合训练&#xff08;附带全…

2024 Java 高分面试宝典 一站式搞定技术面

前言 每年9月和10月&#xff0c;被业界称为“金九银十”&#xff0c;这是人才市场一年中最活跃的时期。此时&#xff0c;企业为了来年的业务扩展&#xff0c;纷纷加大招聘力度&#xff0c;空缺岗位众多&#xff0c;招聘需求集中。同时&#xff0c;初秋的招聘活动也避开酷暑&am…

操作系统:高级IO

高级IO 1.关于IO IO的基本类型&#xff1a; I代表输入(Input): 从外部设备或来源&#xff08;如键盘、鼠标、文件、网络&#xff09;读取数据到计算机中。示例&#xff1a;用户键入的文本、从文件读取的数据、从网络接收到的数据包。 O代表输出(Output): 将计算机处理后的数据发…