TCP是传输层的协议
TCP头部格式
- 源/目的端口:表示数据从哪个进程发送,发送到哪个进程去
- 序号:发送端发送数据包的编号
- 确认号:已经确认接收到的数据的编号(只有当ACK为1时,确认号才有用)
-
URG:紧急指针是否有效
ACK:确认号是否有效
PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
RST:对方要求重新建立连接,把携带RST标识的称为复位报文段
SYN:请求建立连接,把携带SYN标识的称为同步报文段
FIN:通知对方,要关闭连接了,把携带FIN标识的称为结束报文 - 16位窗口大小:进行流量窗口控制
- 16位校验和:检验数据是否一致
- 16位紧急指针:标识哪部分数据是紧急数据
TCP为什么安全可靠:
1.在通信前建立三次握手连接
SYN
SYN+ACK
ACK
2.在通信过程中通过序列号和确认号保障数据传输的完整性
本次发送序列号:上次收到的确认号
本次发送确认号:上次接收到的序列号 + 实际接收的数据长度
在传输过程中使用滑动窗口实现流量控制
3.在通信结束时使用四次挥手结束连接保障数据传输的完整性
UDP和TCP的区别:
1.UDP和TCP都是传输层的协议
2.UDP实现机制简单、资源开销小、不安全不可靠
3.TCP实现机制复杂、资源开销大、安全可靠
4.UDP是无连接的、TCP有连接的、UDP是以数据包形式传输、TCP是以流的方式传输
TCP通信实现流程
需要用到的接口
TCP发端:
1. socket():
创建一个套接字(socket descriptor),用于网络通信。通常使用如下代码:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
其中,AF_INET 表示使用IPv4协议族,SOCK_STREAM 表示使用面向连接的TCP协议。
2. connect():
尝试与服务端建立连接。需要提供服务端的IP地址和端口号。例如:struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:
发送链接请求
参数:
sockfd:套接字文件描述符
addr:目的地址存放空间首地址
addrlen:IP地址的大小
返回值:
成功返回0
失败返回-1
3.send ():
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:
发送数据
参数:
sockfd:文件描述符
buf:发送数据空间首地址
len:发送数据的长度
flags:属性默认为0
返回值:
成功返回实际发送字节数
失败返回-1
recv
4.recv():
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:
接收数据
参数:
sockfd:套接字文件描述符
buf:存放数据空间首地址
len:最大接收数据的长度
flags:属性默认为0
返回值:
成功返回实际接收字节数
失败返回-1
如果对方退出,返回0
5. close ():
close(sockfd);
销毁套接字文件描述符
int CreateTcpClient(char *pip, int port)
{
int ret = 0;
int sockfd = 0;
struct sockaddr_in seraddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(pip);
ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
perror("fail to connect");
return -1;
}
return sockfd;
}
struct sockaddr_in seraddr;
struct sockaddr_in seraddr;是C语言中声明一个 struct sockaddr_in 类型变量 seraddr的语句。
struct sockaddr_in是一个用于存储Internet(IPv4)协议地址信息的标准结构体,广泛应用于网络编程,特别是在使用TCP或UDP套接字时。
struct sockaddr_in结构体通常包含以下几个成员:
其中:
在实际使用中,struct sockaddr_in
通常用于以下场景:
例如,创建一个表示服务器监听地址的 struct sockaddr_in变量:
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET; // IPv4地址家族
seraddr.sin_port = htons(SERVER_PORT); // 将主机字节序端口号转换为网络字节序
seraddr.sin_addr.s_addr = inet_addr(pip); // 将点分十进制IP地址字符串转换为网络字节序整数
// 然后可以将 seraddr 传递给 bind() 函数
bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
TCP收端:
1. socket
2. bind
绑定套接字到本地的IP地址和端口,以便客户端能够找到并连接。
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:
在套接字上绑定一个IP地址和端口号
参数:
sockfd:套接字文件描述符
addr:绑定IP地址空间首地址
addrlen:绑定IP地址的长度
返回值:
成功返回0
失败返回-1
3. listen
int listen(int sockfd, int backlog);
功能:
监听客户端发送的连接请求
该函数不会阻塞
参数:
sockfd:套接字文件描述符
backlog:允许等待的尚未被处理的三次握手请求的最大个数
返回值:
成功返回0
失败返回-1
4. accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:
处理等待连接队列中的第一个连接请求
该函数具有阻塞功能(如果没有人发送链接请求,会阻塞等待)
参数:
socket:套接字文件描述符
address:存放IP地址的空间首地址
addrlen:存放IP地址大小空间首地址
返回值:
成功返回一个新的文件描述符
失败返回-1
5. send
6. recv
7.close
TCP通信 写一个客户端和服务端两者建立通信
/*************************************************************************
> File Name: head.h
> Author: yas
> Mail: rage_yas@hotmail.com
> Created Time: 2024年04月01日 星期一 20时19分06秒
************************************************************************/
#ifndef _HEAD_H
#define _HEAD_H
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#endif
服务端
/*************************************************************************
> File Name: recv.c
> Author: yas
> Mail: rage_yas@hotmail.com
> Created Time: 2024年04月01日 星期一 22时58分52秒
************************************************************************/
#include"head.h"
int main(void)
{
int ret = 0;
int sockfd = 0;
int confd = 0;
char tmpbuff[1024] = {0};
ssize_t nsize = 0;
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(50000);
seraddr.sin_addr.s_addr = INADDR_ANY;//允许任何ip地址连接
sockfd = socket(AF_INET,SOCK_STREAM,0);//创建一个基于IPV4的tcp套接字
if(-1 == sockfd)
{
perror("fail to socket");
return -1;
}
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //将创建的套接字绑定到指定的服务器地址 seraddr
if(-1 == ret)
{
perror("fail to bind");
return -1;
}
ret = listen(sockfd,10);//将套接字设置为监听模式 允许10个客户端连接请求排队
if(-1 == ret)
{
perror("fail to listen");
return -1;
}
confd = accept(sockfd,NULL,NULL);//接受一个客户端的连接请求 返回一个新的套接字文件描述符confd 用于与该客户端通信
if(-1 == confd)
{
perror("fail to accept");
return -1;
}
nsize = recv(confd,tmpbuff,sizeof(tmpbuff),0); //接收客户端数据
if(-1 == nsize)
{
perror("fail to recv");
return -1;
}
printf("RECV:%s\n",tmpbuff);
memset(tmpbuff,0,sizeof(tmpbuff)); //清零 tmpbuff中的数据
fgets(tmpbuff,sizeof(tmpbuff),stdin); //会有输入阻塞 等待用户输入
tmpbuff[strcspn(tmpbuff,"\n")] ='\0'; // 消除 由fgets 自动产生的末尾的换行符
nsize = send(confd,tmpbuff,strlen(tmpbuff),0);//等待用户输入并发送给客户端
if(-1 == nsize)
{
perror("fail to send");
return -1;
}
close(confd);
close(sockfd);
return 0;
}
客户端
/*************************************************************************
> File Name: send.c
> Author: yas
> Mail: rage_yas@hotmail.com
> Created Time: 2024年04月01日 星期一 20时25分08秒
************************************************************************/
/***
*客户端
*/
#include"head.h"
int main(void)
{
int ret = 0;
int sockfd = 0;
struct sockaddr_in seraddr;
char tmpbuff[1024] = {0};
ssize_t nsize =0;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//初始化服务器地址
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(50000);
seraddr.sin_addr.s_addr = inet_addr("192.168.1.151"); //本机的ip地址
/* 连接服务器*/
ret = connect(sockfd,(struct sockaddr*)&seraddr,sizeof(seraddr));//建立tcp连接
if(-1 == ret)
{
perror("fail to connect");
return -1;
}
/*等待用户输入并发送给服务器*/
gets(tmpbuff);
nsize = send(sockfd,tmpbuff,strlen(tmpbuff),0);//发送数据
if(-1 == nsize)
{
perror("fail to send");
return -1;
}
/*
*接收服务器数据
* */
memset(tmpbuff,0,sizeof(tmpbuff));
nsize = recv(sockfd,tmpbuff,sizeof(tmpbuff),0);//接收数据
if(-1 == nsize)
{
perror("fail to recv");
return -1;
}
printf("RECV:%s\n",tmpbuff);
close(sockfd);
return 0;
}
实现效果
通过TCP实现文件发送接收
发送端
/*************************************************************************
> File Name: send.c
> Author: yas
> Mail: rage_yas@hotmail.com
> Created Time: 2024年04月01日 星期一 23时43分33秒
************************************************************************/
#include"head.h"
int main(void)
{
int ret = 0;
int sockfd =0;
struct sockaddr_in recvaddr;
char tmpbuff[1024] = {0};
ssize_t nsize = 0;
ssize_t nret = 0;
int fd = 0;
printf("Enter filename:\n");
gets(tmpbuff);
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("fail to socket");
return -1;
}
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(50000);
recvaddr.sin_addr.s_addr = inet_addr("192.168.1.151");
ret = connect(sockfd,(struct sockaddr*)&recvaddr,sizeof(recvaddr));
if(-1 == ret)
{
perror("fail to connect");
return -1;
}
nsize = send(sockfd,tmpbuff,strlen(tmpbuff),0);
if(-1 == nsize)
{
perror("fail to send");
return -1;
}
sleep(1);
//打开文件
fd = open(tmpbuff,O_RDONLY);
if(-1 == fd)
{
perror("fail to open");
return -1;
}
//循环读取数据并发送
while(1)
{
memset(tmpbuff,0,sizeof(tmpbuff));//确保读取之前tmpbuff 已经清零
nret = read(fd,tmpbuff,sizeof(tmpbuff));// 使用read读取 tmpbuff大小(1024)字节的数据
if(nret<0)
{
break;
}
nsize = send(sockfd,tmpbuff,nret,0);
if(-1 == nsize)
{
perror("fail to send");
return -1;
}
}
close(fd);
close(sockfd);
}
接收端
/*************************************************************************
> File Name: recv.c
> Author: yas
> Mail: rage_yas@hotmail.com
> Created Time: 2024年04月02日 星期二 00时01分51秒
************************************************************************/
#include"head.h"
int main(void)
{
int ret = 0;
int sockfd = 0;
int confd = 0;
ssize_t nsize =0;
struct sockaddr_in recvaddr;
char tmpbuff[1024] = {0};
int fd = 0;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("fail to socket");
return -1;
}
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(50000);
recvaddr.sin_addr.s_addr = INADDR_ANY;
ret = bind(sockfd,(struct sockaddr*)&recvaddr,sizeof(recvaddr));
if(-1 == ret)
{
perror("fail to bind");
return -1;
}
ret = listen(sockfd,10);
if(-1 == ret)
{
perror("fail to listen");
return -1;
}
confd = accept(sockfd,NULL,NULL);
if(-1 == confd)
perror("fail to accept");
return -1;
}
nsize = recv(confd,tmpbuff,sizeof(tmpbuff),0);
if(-1 == nsize)
{
perror("fail to recv");
return -1;
}
//打开文件 如果文件不存在则创建 如果文件之前存在 则清空 ,写入模式
fd = open(tmpbuff,O_WRONLY|O_CREAT|O_TRUNC,0664);
if(-1 == fd)
{
perror("fail to open");
return -1;
}
//循环接收数据并写入文件
while(1)
{
nsize = recv(confd,tmpbuff,sizeof(tmpbuff),0);
if(-1 == nsize)
{
perror("fail to recv");
return -1;
}
else if(0 == nsize)
{
break;
}
write(fd,tmpbuff,nsize);
}
close(fd);
close(confd);
close(sockfd);
return 0;
}