目录
一、网络模型
1、服务器/客户端模型
2、C/S与B/S区别
3、P2P模型
二、TCP(传输控制协议)
(一)TCP概述
(二)TCP的特征(面问高频问题)
1、有链接
三次握手:建立连接;四次挥手:断开连接
2、可靠传输(因为有链接)
3、TCP流式套接字
4、扩展:黏包问题
(三)TCP(相关编程)
1、TCP服务端主干函数及调用顺序
(1)socket函数
(2)bind函数
(3)listen函数
(4)accept函数
(5)recv函数
(7)close函数
2、TCP客户端主干函数及调用顺序
connect函数
3、代码示例
(1)服务端
(2)客户端
(四)练习:复制文件(收发结构体)
1、stat函数
2、服务端代码
3、客户端代码
三、远程登录工具
1、telnet (一般局域网远程登录,不加密(明文))
2、ssh2(远程登录,加密)
一、网络模型
1、服务器/客户端模型
(1)C/S:client server
(2)B/S:browser server
(3)P2P:peer to peer
2、C/S与B/S区别
(1)客户端不同:B/S模型中B为通用客户端,C/S模型中C为专用客户端;
(2)使用协议不同:
B/S模型:用HTTP(超文本传输协议)
C/S模型:自定义(自由度更高)
(3)功能角度:
B/S模型:功能设计相对简单
C/S模型:功能设计复杂
(4)资源角度:
B/S模型:所有资源都由服务端发过来
C/S模型:资源可认为是本地的(事先下好的)
3、P2P模型
下载软件、文件、视频可用(既是客户端也干了服务器的事(提供服务))
二、TCP(传输控制协议)
(一)TCP概述
TCP 是 TCPIIP 体系中非常复杂的一个协议。下面介绍TCP最主要的特点。
(1)TCP是面向连接的运输层协议。这就是说,应用程序在使用TCP协议之前,必须先建立 TCP 连接。在传送数据完毕后,必须释放已经建立的TCP连接。这就是说,应用进程之间的通信好像在“打电话”:通话前要先拨号建立连接,通话结束后要挂机释放连接。
(2)每一条 TCP连接只能有两个端点(endpoint),每一条 TCP连接只能是点对点的(一对一)。
(3)TCP提供可靠交付的服务。也就是说,通过TCP连接传送的数据,无差错、不丢失、不重复、并且按序到达。
(4)TCP提供全双工通信。TCP允许通信双方的应用进程在任何时候都能发送数据。TCP连接的两端都设有发送缓存和接收缓存,用来临时存放双向通信的数据。在发送时应用程序在把数据传送给 TCP的缓存后,就可以做自己的事,而TCP在合适的时候把数据发送出去。在接收时,TCP把收到的数据放入缓存,上层的应用进程在合适的时候读取缓存中的数据。
(5)面向字节流。TCP 中的“流”(stream)指的是流入到进程或从进程流出的字节序列。“面向字节流”的含义是:虽然应用程序和 TCP的交互是一次一个数据块(大小不等),但TCP把应用程序交下来的数据看成仅仅是一连串的无结构的字节流。TCP并不知道所传送的字节流的含义。TCP不保证接收方应用程序所收到的数据块和发送方应用程序所发出的数据块具有对应大小的关系(例如,发送方应用程序交给发送方的TCP共10个数据块,但接收方的 TCP可能只用了4个数据块就把收到的字节流交付给了上层的应用程序)。但接收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样。当然,接收方的应用程序必须有能力识别收到的字节流,把它还原成有意义的应用层数据。
注:三次握手的第一次封装在accept()函数和connect()函数中;
四次挥手双方都调用close()函数,双方都把close()函数走完才认为四次挥手结束。
(二)TCP的特征(面问高频问题)
1、有链接
先确定链路,建立连接(在整个通信过程中一直保持,除非双方断开连接)
扩展:
三次握手:建立连接;四次挥手:断开连接
三次握手:
第一次握手 TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT 同步已发送状态
第二次握手 TCP服务器收到请求报文后,如果同意连接,则会向客户端发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了 SYN-RCVD 同步收到状态
第三次握手 TCP客户端收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED已建立连接状态 触发三次握手。
四次挥手:
第一次挥手 客户端发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态
第二次挥手 服务器端接收到连接释放报文后,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT 关闭等待状态
第三次挥手 客户端接收到服务器端的确认请求后,客户端就会进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文,服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
第四次挥手 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态,但此时TCP连接还未终止,必须要经过2MSL后(最长报文寿命),当客户端撤销相应的TCB后,客户端才会进入CLOSED关闭状态,服务器端接收到确认报文后,会立即进入CLOSED关闭状态,到这里TCP连接就断开了,四次挥手完成。
1-3三次握手(建立连接),4-7处理数据(收发),7-10四次挥手(断开连接)
第8步:ACK1022(第一种情况:之前请求的数据未发完,先应答,说明收到断开,套接字进入半连接状态;第二种情况:双方数据量没那么大,服务器没什么要发的,第八步是有可能被省略的)
SYN:请求连接
ACK:应答
编号:协商双方起始编号
2、可靠传输(因为有链接)
(1)应答机制
(2)超时重传
3、TCP流式套接字
(1)流式套接字:全双工(既能收也能发)(因为是双缓冲区(发送缓冲区和接收缓冲区))
(2)流式套接字示意图:
第一种:recv(100)一把收完(拿走)
第二种:recv(3)一个一个取,不会丢
(3)流式套接字(两个)特性:
数据本身是连续的(无边界);
有顺序的(数据会有序到达)(发和收的次序可不对应)
4、扩展:黏包问题
(1)(双方发数据事先未约定好)数据发送过去接收方没办法正常按发送方那边拆开包;
(2)原因:数据无边界;
(3)解决方案
a.设计分隔符(加分隔符(自己设定))
b.固定大小(典型代表:struct(结构体))
c.自定义协议
eg:AA CC 04 1 2 3 4 CRC1 CRC2 BB DD
AA CC:开头标志
BB DD:结尾标志
04:标识数据长度(自定义)
1 2 3 4:正文4个字节
(三)TCP(相关编程)
1、TCP服务端主干函数及调用顺序
server调用顺序:socket()-->bind()--->listen()-->accept()-->recv()-->write()-->close()
(1)socket函数
函数原型:int socket(int domain, int type, int protocol);
功能:程序向内核提出创建一个基于内存的套接字描述符
参数:domain 地址族,PF_INET == AF_INET ==>互联网程序
PF_UNIX == AF_UNIX ==>单机程序
type 套接字类型:
SOCK_STREAM 流式套接字 ===》TCP
SOCK_DGRAM 用户数据报套接字===>UDP
SOCK_RAW 原始套接字 ===》IP
protocol 协议 ==》0 表示自动适应应用层协议。
返回值:成功 返回申请的套接字id;
失败 -1;注:监听套接字
eg:int listfd = socket(AF_INET,SOCK_STREAM,0);
监听套接字只有一个,功能与3次握手有关
(2)bind函数
函数原型:int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
功能:如果该函数在服务器端调用,则表示将参数1相关的文件描述符文件与参数2 指定的接口地址关联,用于从该接口接受数据。
如果该函数在客户端调用,则表示要将数据从参数1所在的描述符中取出并从
参数2所在的接口设备上发送出去。
注意:如果是客户端,则该函数可以省略,由默认接口发送数据。
参数:sockfd 之前通过socket函数创建的文件描述符,套接字id
my_addr 是物理接口的结构体指针。表示该接口的信息。socklen_t addrlen: 参数2 的长度。
struct sockaddr 通用地址结构
{
u_short sa_family; 地址族
char sa_data[14]; 地址信息
};
转换成网络地址结构如下:
struct _sockaddr_in ///网络地址结构
{
u_short sin_family; 地址族
u_short sin_port; ///地址端口
struct in_addr sin_addr; ///地址IP
char sin_zero[8]; 占位
};
struct in_addr
{
in_addr_t s_addr;
}
返回值:成功 0;失败 -1
(3)listen函数
函数原型: int listen(int sockfd, int backlog);
功能:在参数1所在的套接字id上监听等待链接。
参数:sockfd 套接字id
backlog 允许链接的个数。
返回值:成功 0;失败 -1
(4)accept函数
函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:从已经监听到的队列中取出有效的客户端链接并接入到当前程序。
参数:sockfd 套接字id
addr 如果该值为NULL ,表示不论客户端是谁都接入。
如果要获取客户端信息,则事先定义变量并传入变量地址,函数执行完毕将会将客户端 信息存储到该变量中。
addrlen: 参数2的长度,如果参数2为NULL,则该值也为NULL;
如果参数不是NULL,&len;
一定要写成len = sizeof(struct sockaddr);返回值:成功 返回一个用于通信的新套接字id;从该代码之后所有通信都基于该id
失败 -1;注:通信套接字
a.在服务端方面来说,通信套接字代表客户端;
b.有几个客户端就有几个通信套接字;
(5)recv函数
函数原型:ssize_t recv(int sockfd, void *buf, size_t len,int flags);
功能:从指定的sockfd套接字中以flags方式获取长度为len字节的数据到指定的buff内存中。
参数:sockfd:如果服务器则是accept的返回值的新fd
如果客户端则是socket的返回值旧fd
buff 用来存储数据的本地内存,一般是数组或者动态内存。
len 要获取的数据长度
flags 获取数据的方式,0 表示阻塞接受。返回值: >0 实际收到的字节数
==0 表示对方断开
<0 (-1,出错)
(6)send函数
函数原型:int send(int sockfd, const void *buf, size_t len, int flags);
功能:从buf所在的内存中获取长度为len的数据以flags方式写入到sockfd对应的套接字中。
参数:sockfd:如果是服务器则是accept的返回值新fd
如果是客户端则是sockfd的返回值旧fd
msg 要发送的消息
len 要发送的消息长度
flags 消息的发送方式。
返回值:成功 发送的字符长度;失败 -1
(7)close函数
close(conn); //关闭通信套接字(走的4次挥手),代表不同客户端;
close(listfd); //关闭监听套接字(新用户找不到端口和IP。之前连上的不影响)
2、TCP客户端主干函数及调用顺序
client调用顺序:socket()-->connect()-->send()-->recv()-->close()
connect函数
函数原型:int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:该函数固定有客户端使用,表示从当前主机向目标主机发起链接请求。
参数:sockfd 本地socket创建的套接子id
addr 远程目标主机的地址信息。
addrlen: 参数2的长度。
返回值:成功 0;失败 -1
3、代码示例
(1)服务端
#include <arpa/inet.h> // 包含IP地址处理函数(如inet_addr)
#include <netinet/in.h> // 包含网络地址结构体(如sockaddr_in)
#include <netinet/ip.h> // 包含IP协议相关定义
#include <stdio.h> // 标准输入输出头文件
#include <stdlib.h> // 标准库头文件(含内存分配、exit等)
#include <string.h> // 字符串处理头文件
#include <sys/socket.h> // 套接字编程核心头文件
#include <sys/types.h> // 数据类型定义头文件
#include <time.h> // 时间处理头文件
#include <unistd.h> // 包含Unix系统函数(如close、sleep)typedef struct sockaddr*(SA); // 定义SA为指向sockaddr结构体的指针类型,简化后续代码书写
int main(int argc, char** argv) // 主函数,argc为参数个数,argv为参数数组
{
// 监听套接字
int listfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP监听套接字(AF_INET:IPv4,SOCK_STREAM:TCP协议,0:默认协议)
if (-1 == listfd) // 检查套接字创建是否失败
{
perror("socket"); // 打印错误信息(显示"socket"及具体错误)
return 1; // 失败则退出程序,返回1表示错误
}// man 7 ip (注释:提示查看ip协议手册)
struct sockaddr_in ser, cli; // 定义服务器(ser)和客户端(cli)的网络地址结构体
bzero(&ser, sizeof(ser)); // 清空服务器地址结构体(置零)
bzero(&cli, sizeof(cli)); // 清空客户端地址结构体(置零)ser.sin_family = AF_INET; // 设置地址族为IPv4
ser.sin_port = htons(50000); // 设置端口号(htons:主机字节序转网络字节序)
ser.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置IP地址(本地回环地址,inet_addr:字符串转网络字节序IP)int ret = bind(listfd, (SA)&ser, sizeof(ser)); // 将套接字绑定到指定地址和端口
if (-1 == ret) // 检查绑定是否失败
{
perror("bind"); // 打印绑定错误信息
return 1; // 失败则退出
}// 三次握手的排队数 (注释:提示backlog参数含义,即未完成连接队列的最大长度)
listen(listfd, 3); // 开始监听,允许最多3个未完成连接排队socklen_t len = sizeof(cli); // 初始化客户端地址结构体长度(用于accept函数)
// 通信套接字
int conn = accept(listfd, (SA)&cli, &len); // 接受客户端连接,返回新的通信套接字conn
if (-1 == conn) // 检查连接接受是否失败
{
perror("accept"); // 打印接受连接错误信息
return 1; // 失败则退出
}while (1) // 循环处理客户端通信(死循环,直到客户端断开或出错)
{
char buf[256] = {0}; // 定义接收缓冲区,初始化为0
// ret >0 实际收到的字节数;==0 表示对方断开;-1 出错。(注释:recv返回值含义)
ret = recv(conn, buf, sizeof(buf), 0); // 从客户端接收数据(conn:通信套接字,buf:缓冲区,sizeof(buf):最大接收字节数,0:默认参数)
if(ret <=0) // 处理接收失败或客户端断开
{
break; // 退出循环
}
printf("cli:%s\n", buf); // 打印客户端发送的内容time_t tm; // 定义时间变量
time(&tm); // 获取当前时间(存储到tm中)
struct tm * info = localtime(&tm); // 将时间转换为本地时间结构体(包含时分秒等信息)sprintf(buf, "%s %d:%d:%d\n", buf, info->tm_hour, info->tm_min, info->tm_sec); // 将客户端消息与当前时间拼接成新字符串,存入buf
send(conn, buf, strlen(buf), 0); // 将拼接后的字符串发送回客户端(conn:通信套接字,buf:数据,strlen(buf):数据长度,0:默认参数)
}close(conn); // 关闭通信套接字
close(listfd); // 关闭监听套接字
// system("pause"); // 注释:Windows下暂停程序,Linux无需此操作
return 0; // 程序正常退出,返回0
}
(2)客户端
#include <arpa/inet.h> // 包含IP地址处理函数(如inet_addr)
#include <netinet/in.h> // 包含网络地址结构体(如sockaddr_in)
#include <netinet/ip.h> // 包含IP协议相关定义
#include <stdio.h> // 标准输入输出头文件
#include <stdlib.h> // 标准库头文件(含内存分配、exit等)
#include <string.h> // 字符串处理头文件
#include <sys/socket.h> // 套接字编程核心头文件
#include <sys/types.h> // 数据类型定义头文件
#include <time.h> // 时间处理头文件(注:代码中未实际使用time.h相关功能,可能为冗余包含)
#include <unistd.h> // 包含Unix系统函数(如close、sleep)typedef struct sockaddr*(SA); // 定义SA为指向sockaddr结构体的指针类型,简化后续代码书写
int main(int argc, char** argv) // 主函数,argc为参数个数,argv为参数数组
{
int conn = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字(AF_INET:IPv4,SOCK_STREAM:TCP协议,0:默认协议)
if (-1 == conn) // 检查套接字创建是否失败
{
perror("socket"); // 打印错误信息(显示"socket"及具体错误)
return 1; // 失败则退出程序,返回1表示错误
}// man 7 ip (注释:提示查看ip协议手册)
struct sockaddr_in ser, cli; // 定义服务器(ser)和客户端(cli)的网络地址结构体(注:cli未实际使用)
bzero(&ser, sizeof(ser)); // 清空服务器地址结构体(置零)
bzero(&cli, sizeof(cli)); // 清空客户端地址结构体(置零,注:此处冗余,因未使用cli)ser.sin_family = AF_INET; // 设置地址族为IPv4
ser.sin_port = htons(50000); // 设置端口号(htons:主机字节序转网络字节序,对应服务器端口)
ser.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置服务器IP地址(本地回环地址)int ret = connect(conn, (SA)&ser, sizeof(ser)); // 向服务器发起连接请求(conn:套接字,&ser:服务器地址,sizeof(ser):地址长度)
if (-1 == ret) // 检查连接是否失败
{
perror("connect"); // 打印连接错误信息
return 1; // 失败则退出
}while (1) // 循环发送/接收数据(死循环,直到服务器断开或出错)
{
char buf[256] = {0}; // 定义缓冲区,初始化为0
strcpy(buf, "this is tcp test"); // 向缓冲区写入固定字符串(作为发送内容)
send(conn, buf, strlen(buf), 0); // 向服务器发送数据(conn:套接字,buf:数据,strlen(buf):数据长度,0:默认参数)ret = recv(conn, buf, sizeof(buf), 0); // 接收服务器返回的数据
if (ret <= 0) // 处理接收失败或服务器断开(ret=0:对方断开;ret=-1:出错)
{
break; // 退出循环
}
printf("ser:%s", buf); // 打印服务器返回的内容
fflush(stdout); // 刷新输出缓冲区(确保立即打印,避免缓存延迟)
sleep(1); // 休眠1秒(控制发送频率,避免高频请求)
}close(conn); // 关闭套接字,释放资源
// system("pause"); // 注释:Windows下暂停程序,Linux无需此操作
return 0; // 程序正常退出,返回0
}
(四)练习:复制文件(收发结构体)
1、stat函数
包含头文件及函数原型:
功能:得到文件大小
2、服务端代码
#include <arpa/inet.h> // 包含IP地址处理函数(如inet_addr)
#include <fcntl.h> // 包含文件操作标志(如O_WRONLY、O_CREAT)
#include <netinet/in.h> // 包含网络地址结构体(如sockaddr_in)
#include <netinet/ip.h> // 包含IP协议相关定义
#include <stdio.h> // 标准输入输出头文件
#include <stdlib.h> // 标准库头文件(含内存分配、exit等)
#include <string.h> // 字符串处理头文件
#include <sys/socket.h> // 套接字编程核心头文件
#include <sys/types.h> // 数据类型定义头文件
#include <time.h> // 时间处理头文件(注:代码中未实际使用,可能为冗余包含)
#include <unistd.h> // 包含Unix系统函数(如close、write)typedef struct sockaddr*(SA); // 定义SA为指向sockaddr结构体的指针类型,简化后续代码书写
// 定义数据传输包结构体PACK
typedef struct {
char filename[256]; // 文件名(存储接收文件的名称)
char buf[1024]; // 数据缓冲区(存储文件数据块)
int buf_len; // 本次缓冲区有效数据长度
int total_len; // 文件总长度
} PACK;int main(int argc, char** argv) // 主函数,argc为参数个数,argv为参数数组
{
// 创建监听套接字
int listfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP监听套接字(IPv4,TCP协议,默认参数)
if (-1 == listfd) { // 检查套接字创建失败
perror("socket"); // 打印错误信息
return 1;
}// man 7 ip (注释:提示查看ip协议手册)
struct sockaddr_in ser, cli; // 定义服务器(ser)和客户端(cli)的网络地址结构体
bzero(&ser, sizeof(ser)); // 清空服务器地址结构体(置零)
bzero(&cli, sizeof(cli)); // 清空客户端地址结构体(置零)ser.sin_family = AF_INET; // 设置地址族为IPv4
ser.sin_port = htons(50000); // 设置端口号(主机字节序转网络字节序)
ser.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置本地回环IP地址int ret = bind(listfd, (SA)&ser, sizeof(ser)); // 绑定套接字到指定地址和端口
if (-1 == ret) { // 检查绑定失败
perror("bind");
return 1;
}// 三次握手排队数(backlog,未完成连接队列最大长度)
listen(listfd, 3); // 开始监听,允许最多3个未完成连接socklen_t len = sizeof(cli); // 初始化客户端地址结构体长度(用于accept)
// 接受客户端连接,获取通信套接字
int conn = accept(listfd, (SA)&cli, &len);
if (-1 == conn) { // 检查连接接受失败
perror("accept");
return 1;
}int first_flag = 0; // 标志位:0表示未接收文件头,1表示已接收
int fd = -1; // 文件描述符(用于写入文件)
int current_size = 0; // 已接收数据总长度
int total_size = 0; // 文件总长度while (1) { // 循环接收文件数据,直到传输完成
PACK pack; // 定义PACK结构体变量(存储单个数据包)
bzero(&pack, sizeof(pack)); // 清空结构体(置零)
ret = recv(conn, &pack, sizeof(pack), 0); // 接收客户端发送的数据包if (0 == first_flag) { // 首次接收(处理文件头信息)
first_flag = 1; // 标记已接收文件头
// 打开文件(O_WRONLY:只写,O_TRUNC:清空文件(若存在),O_CREAT:不存在则创建,权限0666)
fd = open(pack.filename, O_WRONLY | O_TRUNC | O_CREAT, 0666);
if (-1 == fd) { // 检查文件打开失败
perror("open");
return 1;
}
total_size = pack.total_len; // 获取文件总长度
}if (0 == pack.buf_len) { // 若本次数据长度为0(客户端发送结束标志)
break; // 退出循环
}write(fd, pack.buf, pack.buf_len); // 将数据写入文件
current_size += pack.buf_len; // 累计已接收数据长度
printf("%d/%d\n", current_size, total_size); // 打印接收进度(已接收/总长度)bzero(&pack, sizeof(pack)); // 清空结构体(注:此处可能冗余,因后续未再使用pack)
strcpy(pack.buf, "go on"); // 向pack.buf写入"go on"(注:代码中注释了发送操作,实际未发送)
// send(conn, &pack, sizeof(pack), 0); // 注释:原计划发送确认信息,但代码中未执行
}close(conn); // 关闭通信套接字
close(listfd); // 关闭监听套接字
close(fd); // 关闭文件
// system("pause"); // 注释:Windows下暂停程序,Linux无需此操作
return 0;
}
3、客户端代码
#include <arpa/inet.h> // 包含IP地址处理函数(如inet_addr)
#include <fcntl.h> // 包含文件操作标志(如O_RDONLY)
#include <netinet/in.h> // 包含网络地址结构体(如sockaddr_in)
#include <netinet/ip.h> // 包含IP协议相关定义
#include <stdio.h> // 标准输入输出头文件
#include <stdlib.h> // 标准库头文件(含内存分配、exit等)
#include <string.h> // 字符串处理头文件
#include <sys/socket.h> // 套接字编程核心头文件
#include <sys/stat.h> // 包含文件状态结构体stat(用于获取文件属性)
#include <sys/types.h> // 数据类型定义头文件(重复包含,可优化)
#include <time.h> // 时间处理头文件(注:代码中未实际使用,冗余包含)
#include <unistd.h> // 包含Unix系统函数(如close、read、usleep)typedef struct sockaddr*(SA); // 定义SA为指向sockaddr结构体的指针类型,简化后续代码书写
// 定义数据传输包结构体PACK(与服务器端一致)
typedef struct {
char filename[256]; // 文件名(存储待发送的文件名)
char buf[1024]; // 数据缓冲区(存储文件数据块)
int buf_len; // 本次缓冲区有效数据长度
int total_len; // 文件总长度
} PACK;int main(int argc, char** argv) // 主函数,argc为参数个数,argv为参数数组
{
int conn = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字(IPv4,TCP协议,默认参数)
if (-1 == conn) { // 检查套接字创建失败
perror("socket"); // 打印错误信息
return 1;
}// man 7 ip (注释:提示查看ip协议手册)
struct sockaddr_in ser, cli; // 定义服务器(ser)和客户端(cli)的网络地址结构体(cli未实际使用)
bzero(&ser, sizeof(ser)); // 清空服务器地址结构体(置零)
bzero(&cli, sizeof(cli)); // 清空客户端地址结构体(置零,冗余,因未使用cli)ser.sin_family = AF_INET; // 设置地址族为IPv4
ser.sin_port = htons(50000); // 设置服务器端口号(主机字节序转网络字节序)
ser.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置服务器IP地址(本地回环地址)int ret = connect(conn, (SA)&ser, sizeof(ser)); // 向服务器发起连接请求
if (-1 == ret) { // 检查连接失败
perror("connect");
return 1;
}PACK pack; // 定义PACK结构体变量(用于封装文件数据)
strcpy(pack.filename, "1.png"); // 设置待发送的文件名(路径可写全,此处为相对路径或绝对路径,代码中实际使用绝对路径/home/linux/1.png)struct stat st; // 定义文件状态结构体(用于获取文件大小)
ret = stat("/home/linux/1.png", &st); // 获取文件状态信息(通过绝对路径获取)
if (-1 == ret) { // 检查stat调用失败(如文件不存在)
perror("stat");
return 1;
}
pack.total_len = st.st_size; // 存储文件总长度到PACK结构体int fd = open("/home/linux/1.png", O_RDONLY); // 以只读方式打开文件
if (-1 == fd) { // 检查文件打开失败
perror("open");
return 1;
}while (1) { // 循环读取文件数据并发送,直到文件读取完毕
pack.buf_len = read(fd, pack.buf, sizeof(pack.buf)); // 从文件读取数据到缓冲区,获取实际读取长度
send(conn, &pack, sizeof(pack), 0); // 向服务器发送整个PACK结构体(包含文件名、数据、长度等)if (pack.buf_len <= 0) { // 处理读取结束(buf_len=0)或出错(buf_len=-1)
break; // 退出循环
}bzero(&pack, sizeof(pack)); // 清空PACK结构体(注:发送后清空,后续循环重新填充,逻辑合理)
// recv(conn, &pack, sizeof(pack), 0); // 注释:原计划接收服务器确认,但代码中未执行
usleep(1000 * 10); // 休眠10毫秒(控制发送频率,避免网络拥塞)
}close(conn); // 关闭套接字
close(fd); // 关闭文件
// system("pause"); // 注释:Windows下暂停程序,Linux无需此操作
return 0;
}
三、远程登录工具
1、telnet (一般局域网远程登录,不加密(明文))
远程登录工具,默认都是系统安装。
使用格式: telnet ip地址 端口
eg: telnet 192.168.1.1 8888
注意:如果没有写端口,则默认登录23 号端口。
2、ssh2(远程登录,加密)
3、注:
sudo ufw status //查看防火墙状态
sudo ufw disable //关闭防火墙
sudo apt-get install openssh-server openssh-client //安装工具命令