1.socket编程
1.1计算机网络的分类
//简单认识一下,详细可以自己学习计算机网络
1.1.1按地理范围分类
1)个人区域网(PAN)
(1)范围:通常几米到十几米
(2)用途:连接个人设备
(3)示例:蓝牙,红外等
2)局域网(LAN)
(1)范围:通常是一个建筑,办公室或校园
(2)用途:在小范围内实现高速数据传输和资源共享
(3)示例:以太网,WiFi,我们平时用的就是局域网,而局域网又通过接入网络来上网
3)域域网(MAN)
(1)范围:覆盖一个或多个相邻城市
(2)用途:连接多个局域网,提供域域范围内的数据传输服务
(3)示例:光纤网络、有线电视、DSL
4)广域网
(1)范围:覆盖国家、洲际甚至全球
(2)用途:连接多个局域网,和域域网,实现长距离数据传输
(3)示例:互联网、企业专用网
1.1.2按网络拓扑分类
1)总线型拓扑
所有设备共享一条通信介质(总线)
(1)优点:总线简单,成本低
(2)缺点:总线故障会让全部瘫痪
2)星型拓扑
所有设备通过单独的通信链路连接到一个中央节点(如交换机)
(1)优点:易于管理和拓展,单个设备故障不会影响整个网络
(2)缺点:中央节点故障会让整个瘫痪
3)环形拓扑
每个设备连接到两个相邻设备,形成一个环。
(1)优点:数据传输延迟低,适合实时通信
(2)缺点:布线复杂,成本高
1.1.3按网络用途分类
1)互联网
全球范围内连接各种网络的集合
用途:提供全球范围的通信和信息共享
2)企业内部网
仅限于企业或组织内部使用的网络
用途:提供内部资源共享和通信
3)外联网
连接企业内部网与外部合作伙伴或客户的网络
用途:在安全受控的环境下,与外部实体进行通信共享
6.1.4按网络的交换技术分类
1)电路交换网络
通信路径在传输前建立,并在通信期间保持不变
示例:传统电话网络
2)分组交换网络
数据分成小包(分组)传输,每个分组可以通过不同的路径到达目的地。
3)报文交换网络
整个消息作为一个单元传输。通过节点存储并转发
示例:早期的电报网络
1.1.5按网络的通信方式
1)广播网络
一个节点发送的数据报可以被网络中所有节点接收到
示例:以太网,WiFi
2)点对点网络
数据包从一个节点直接发送到目标节点
示例:点对点协议
1.2计算机网络分层模型
1.2.1相关概念
计算机网络分层模型是网络通信的基础框架,为了简化网络设计,确保不同网络技术直接的兼容性和互操作性。与分层模型相关的概念包括分层、实体、协议、接口和服务
1)分层
分层是将网络通信过程划分为多个层次的过程。每一层关注网络通信的一个特定方面。最著名的是OSI模型,包含七层以及TCP/IP模型,通常被认为包含四层。
2)实体
在分层模型中,每一层都有实体,这些实体指的是执行特定层次功能的硬件或软件组件。同一层内的实体可以在不同的机器上,通过遵循相同层的协议进行通信。
例如,两台计算机上的传输层实体可以是负责建立端到端连接的软件。
同一层次的实体为对等实体。
3)协议
协议是一套规则和标准,用于控制同一层的实体如何互相通信。协议定义了通信的格式,时序,错误处理等。
4)接口
在分层模型中,定义相邻两层如何通信。定义了其交互规范和方式,
SAP是接口概念的一部分,具体了接口在实现服务的作用。为接口提供具体的实施机制,使上层可以访问下层提供的服务
5)服务
下层为紧邻的上层提供给功能调用。
上层通过SAP访问下层服务
1.2.2OSI七层模型
1)物理层
负责传输原始的比特流,涉及物理设备及介质。
2)数据链路层
确保物理链路传输没有问题。提供了如帧同步,流量控制和错误检测等功能
3)网络层
负责数据包从源到目的地的传输和路由选择,定义了地址和路由的概念如ip协议等
4)传输层
提供端到端的数据传输服务,保证数据的完整性,定义了如TCP、UDP协议(核心是端口号)
5)会话层
管理会话,控制建立,维护和终止会话
6) 表示层
处理数据的表示、编码和解码,如加密和解密
7) 应用层
提供网络服务给终端用户的应用程序,如HTTP,FTP,SMTP等协议
(一般会话层、表示层、应用层一块用)
1.2.3五层模型
一般在教学中使用,把会话层、表示层、应用层合并一起,其他不变。一般不怎么这么用 !
1.2.4TCP/IP四层模型
TCP/IP四层模型,又称互联网协议套件。按照功能标准组织互联网及类似计算机网络使用的一系列通信协议的框架。基础协议包括TCP、UDP、IP。常用这个模型
1)应用层
(1)定义
用户最直接交互的部分。使用下层提供的服务来创建用户数据,将这些数据传输给同一台机器或远程机器上的其他应用
(2)协议
这一层包括SMTP、FTP、SSH、HTTP等,实现客户端与服务器之间的通信和数据交换
2)传输层
(1)定义
管理端到端的通信,主要职责是为不同主机的应用提供数据传输,同时保证数据的完整性和可靠型
(2)协议
在传输层,主要协议有TCP,提供顺序的、可靠的、双向的连接流,并管理报文段的发,确保无错误,不丢失、不重复、按序到达;以及UDP,他提供一种无法连接的服务,数据以数据报的形式发送不保证顺序或响应,速度快
3)互联网层
(1)定义
处理跨网络界限的数据包交换,负责将数据报文段从源地址路由到目的地址,这层抽象了实际的物理网络拓扑结构,并且定义了如何在各种网络结构中发送和接收数据包。
网络层的IP协议是构成Internet的基础。Internet上的主机都是通过IP地址来互相识别。I它不保证传输数据的可靠性,可能出现丢包等情况。
(2)协议
主要协议是IP,定义了数据包的路由方式和网络地址。其他重要的协议包括ICMP,用于错误报告和网络诊断
4) 链路层
(1)定义
链路层确保网络层传来的IP数据报可以在网络的物理链接上进行传输。它还负责处理与物理网络链接相关的问题,例如MAC地址寻址、帧同步、错误检测和校正。
(2)协议
它包括了在物理网络链接中使用的所有协议,如以太网、Wi-Fi以及PPP。
1.2IP地址与域名
1.2.1 IP地址
IP地址(Internet Protocol Address,互联网协议地址)是分配给连接到计算机网络的每个设备的唯一标识符,用于在网络中进行通信。IP地址使数据包在网络上能够找到其目标位置,确保数据从源设备传输到目标设备。
点分十进制表示方法 192.168.1.25----->192 << 24|168 << 16 | 1 << 8| 25
当前用的是IPV4
1.2.2 域名
简单来说他就是公网IP。
1.3端口号
端口号是用来区分一台计算机上的不同应用的
16为整数。0-1023一般是一些比较著名的端口,我们可以在1025-50000中选择
1.4 CS模式与BS模式
简单讲:
CS:客户端/服务器模式
BS:浏览器/服务器
1.5 TCP协议与UDP协议
具体的比较复杂可以看其他的。TCP较为稳定,一般用TCP
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a83255ddf78e4d6e988d8fd03ec06d62.png)
共有三次握手四次挥手
1.6 Linux下实现TCP通信
1.6.1 socket原理
1.6.2 socket编程
整体步骤如下:
1、socket:创建套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建套接字
参数:
1)domain:通信域,用来选择协议家族,我们通常使用的是IPV4,对应的是AF_INET
2)type:
①SOCK_STREAM:TCP,字节流/数据流
②SOCK_DGRAM:UDP,数据报
3)指定域socket一起使用的特性的协议。通常一个协议家族只支持一个协议,所以此参数通常为0.
返回值:成功返回Socket描述符,失败返回 -1。
2、bind():绑定地址
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:绑定IP和端口
参数:
1)sockfd:套接字的文件描述符,由socket函数创建返回。
2)addr:绑定的地址,不同协议家族的地址形式不同,对于IPV4来说,地址由IP和端口组成。
返回值:成功返回 0,失败返回 -1
IPV4中地址的保存形式是(man ip(7)):
struct sockaddr_in
{
sa_family_t sin_family; /*协议家族: AF_INET */
in_port_t sin_port; /* 端口号,网络字节序------htons(端口号)可以进行转化 */
struct in_addr sin_addr; /* IP地址 */
};
/* IP地址封装在in_addr结构体中,是一个32位的整数 */
struct in_addr {
uint32_t s_addr; /* ip地址,32位整数,网络字节序 */
};
可以将点分十进制表示的IP转换成32位整数,函数:
int inet_aton(const char *cp, struct in_addr *inp); //把点分十进制IP地址字符串转化成struct in_addr类型,存储于输出参数中。(网络字节序)
in_addr_t inet_addr(const char *cp); //把点分十进制IP地址字符串转化成一个32位整数(网络字节序)
in_addr_t inet_network(const char *cp); //把点分十进制IP地址字符串转化成一个32位整数(本地字节序)
char *inet_ntoa(struct in_addr in); //把struct in_addr类型的IP地址转换成点分十进制形式。
绑定本地地址,系统给我提供了一个定义好的宏,INADDR_ANY,可绑定所有本地网络通信接口。
网络字节序的转换函数:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //长整型数据转换:把主机字节序转换为网络字节序
uint16_t htons(uint16_t hostshort); //短整型数据转换:把主机字节序转换为网络字节序
uint32_t ntohl(uint32_t netlong); //长整型数据转换:把网络字节序转为主机字节序
uint16_t ntohs(uint16_t netshort); //短整型数据转换:把网络字节序转为主机字节序
数据接收:
ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags); //比read多了一个flag参数,可传递MSG_DONTWAIT实现非阻塞读取
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
注意:读数据的时候,分两种情况,正常数据和挥手信号都能让read从阻塞中唤醒,我们需要判断一下接收到的数据的长度,如果长度大于0就是正常数据,等于0就是挥手信号,如果收到的是挥手信号,那么就要及时的关闭套接字,不能想套接字发数据了。
数据发送:
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int sockfd, const void *buf, size_t len, int flags); //比write多了一个flag
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
3.listen() - 监听连接请求(仅适用于TCP)。
int listen(int sockfd, int backlog);
sockfd: Socket描述符。
backlog: 排队的最大连接数。
返回值:成功返回 0,失败返回 -1。
4.accept() - 接受连接请求(仅适用于TCP)。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd: 监听的Socket描述符。
addr: 客户端地址信息。
addrlen: addr结构体的大小。
返回值:成功返回新的Socket描述符,失败返回 -1。
5.connect() - 发起连接(客户端使用)。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd: Socket描述符。
addr: 服务器的地址信息。
addrlen: addr结构体的大小。
返回值:成功返回 0,失败返回 -1。
与bind类似
6.send() 和 recv() - 发送和接收数据。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd: Socket描述符。
buf: 要发送或接收的数据缓冲区。
len: 数据长度。
flags: 发送或接收的标志位,通常为 0。
返回值:成功返回发送或接收的字节数,失败返回 -1。
7.close() - 关闭Socket连接。
int close(int sockfd);
sockfd: 要关闭的Socket描述符。
返回值:成功返回 0,失败返回 -1。
示例
写一个服务端程序socket_w
//通过socket套接字实现多进程tcp通信
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
int sockfd;
void *func(void *th);
void handle_signal(int sig);
int main()
{
int newsock;
struct sockaddr_in sock_add;
struct sockaddr address;
int addlen =sizeof(address);
pthread_t client;
//代码完成之后发现,服务端无法关闭,用ctrl+c后,端口还会占用一段时间
//添加处理函数
signal(SIGINT,handle_signal);
//创建套接字
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("socket");
_exit(EXIT_FAILURE);
}
//配置ip与端口号
sock_add.sin_family=AF_INET;
sock_add.sin_port=htons(7080);
sock_add.sin_addr.s_addr=INADDR_ANY;
//绑定
if(bind(sockfd,(struct sockaddr *)&sock_add,sizeof(sock_add))<0)
{
perror("bind");
_exit(EXIT_FAILURE);
}
//监听
if(listen(sockfd,3)<0)
{
perror("listen");
_exit(EXIT_FAILURE);
}
//实现多进程通信
while(1)
{
//接收连接请求
if((newsock=accept(sockfd,&address,&addlen))<0)
{
perror("accept");
_exit(EXIT_FAILURE);
}
printf("accept a new\n");
//创建线程
pthread_create(&client,NULL,&func,&newsock);
}
}
//既然创建线程那就必须要有线程函数了
void *func(void *th)
{
pthread_t thread_id = pthread_self();
//接收传过来的socket标识符
int sock=*(int*)th;
//创建数组接收
char buf[100];
while(1)
{
//把数组清零
memset(buf,0,sizeof(buf));
//读通过tcp传的信息到数组
if((read(sock,buf,100))<=0)
{
perror("close process");
break;
}
//当收到quit时关闭线程
if(strstr(buf,"quit") != NULL) break;
printf("%s\n",buf);
//回复ok表示已接收
write(sock,"ok",5);
//用来实现服务端实现发送数据到客户端
if(strstr(buf,"sendmode") != NULL)
{
while (1)
{
memset(buf,0,sizeof(buf));
//打印当前进程号,区分不同客户端
printf("Thread ID: %lu received: %s\n", thread_id, buf);
scanf("%s",buf);
if(strstr(buf,"quit")!=NULL)
{
break;
}
write(sock,buf,sizeof(buf));
write(sock,"\n",2);
}
}
}
//关闭
close(sock);
}
void handle_signal(int sig)
{
if(sig==SIGINT)
{
printf("\nServer shutting down...\n");
close(sockfd); // 关闭服务器套接字
exit(0);
}
}