目录
TCP介绍
TCP (Transmission Control Protocol) 是一种面向连接的协议,用于在计算机网络中传输数据。TCP 可以确保数据的可靠传输,即使在网络环境不稳定的情况下也能够保证数据的完整性和顺序。以下是 TCP 通信的一些特点:
- 面向连接:在 TCP 通信中,通信的双方必须先建立一个连接,然后才能进行数据的传输。连接的建立需要经过三次握手过程,确保双方都能够进行通信。
- 可靠传输:TCP 可以保证数据的可靠传输,它使用确认和重传机制来确保数据的完整性和顺序。每当发送方发送数据包时,接收方都会发送一个确认信息来表示已经收到数据。如果发送方没有收到确认信息,它会重新发送数据,直到接收方确认为止。
- 流量控制:TCP 可以根据接收方的处理能力来控制数据的发送速度,防止发送方发送过多的数据导致接收方无法处理。
- 拥塞控制:TCP 可以根据网络状况来调整发送方的数据发送速度,防止网络拥塞导致数据丢失和传输延迟。
总之,TCP 是一种可靠的协议,它在网络传输中发挥了重要的作用,特别是在需要确保数据完整性和顺序的场合,如 Web 浏览器、电子邮件、文件传输等应用中。
代码实现
server(服务器端)
/server*****/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
//1.使用socket函数-创建流式套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建IPv4的TCP套接字
if (sockfd < 0)
{
perror("scoket err");
return -1;
}
//socket第一个参数已经是AF_INET ipv4,是用bind函数得填充ipv4结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET; //设置地址族为IPv4
saddr.sin_port = htons(atoi(argv[1])); //端口占了两个字节,将小端转换为大端
saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); //绑定任意可用的IP地址
//2.绑定套接字,ip和端口
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind");
return -1;
}
else
{
printf("bind ok\n");
}
//3.监听-被动状态
if (listen(sockfd, 5) < 0) //设置监听队列大小为5
{
perror("listen err");
return -1;
}
//4.阻塞等待客户端链接
int accept_fd = accept(sockfd, NULL, NULL); //接受客户端的连接请求
if (accept_fd < 0)
{
perror("accept err");
return -1;
}
//5.循环接受消息
char arr[128];
ssize_t ret;
while (1)
{
ret = recv(accept_fd, arr, sizeof(arr), 0); //从客户端接收数据
if (ret < 0)
{
perror("recv err");
return -1;
}
else if (ret == 0)
{
perror("clien exit");
break;
}
else
{
write(1, arr, ret); //将接收到的数据输出到终端
}
}
close(sockfd); //关闭套接字
close(accept_fd);
return 0;
}
代码分析
具体分析如下:
第1行到第15行,定义了程序的主函数main(),并进行了如下操作:
使用socket函数创建一个IPv4的TCP套接字。
利用bind函数将套接字绑定到指定IP和端口上。
使用listen函数将套接字设置为监听状态,设置了监听队列的长度为5。
使用accept函数等待客户端连接请求,并返回连接套接字的文件描述符。
在一个while循环中循环接收客户端发送的消息,然后将接收到的消息输出到终端。
关闭套接字。
第17行到第22行,定义了一个IPv4的套接字地址结构体sockaddr_in,并设置IP地址和端口号。
第24行到第32行,利用bind函数将套接字绑定到指定IP和端口上,并检查绑定是否成功。
第35行到第41行,使用listen函数将套接字设置为监听状态,并设置监听队列的长度为5,如果监听失败则输出错误信息并退出程序。
第44行到第50行,使用accept函数阻塞等待客户端连接请求,如果接受失败则输出错误信息并退出程序,否则返回连接套接字的文件描述符。
第53行到第64行,在一个while循环中循环接收客户端发送的消息,如果接收失败则输出错误信息并退出程序,如果接收到的消息长度为0则说明客户端已经关闭连接,退出循环;否则将接收到的消息输出到终端。
第67行到第70行,关闭套接字并退出程序。
该程序实现了一个简单的TCP服务器,可以接收客户端的连接并循环接收客户端发送的消息,将接收到的消息输出到终端。
client(客户端)
/client**********/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
//1. 使用socket创建流式套接子
int socket_fd;
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0)
{
perror("socket_fd err"); // 打印错误信息
return -1;
}
//2. 请求链接
struct sockaddr_in cnt;
cnt.sin_family = AF_INET;
cnt.sin_port = htons(atoi(argv[1])); // 将输入的字符串参数转换为整数端口号
cnt.sin_addr.s_addr = inet_addr("0.0.0.0"); // 设置服务器的IP地址
if (connect(socket_fd, (struct sockaddr *)&cnt, sizeof(cnt)) < 0)
{
perror("connect err"); // 打印错误信息
return -1;
}
char buf[128];
int ch;
while (1)
{
ch = read(0, buf, 128); // 从标准输入读取用户输入的数据
if (send(socket_fd, buf, ch, 0) < 0) // 发送数据到服务器
{
perror("send err"); // 打印错误信息
return -1;
}
}
close(socket_fd); // 关闭套接字
return 0;
}
代码分析
这是一个TCP客户端代码,它的主要功能是连接到指定的服务器并发送数据。下面是代码的逐行分析:
socket_fd = socket(AF_INET, SOCK_STREAM, 0);:创建一个流式套接字。
cnt.sin_family = AF_INET;:设置服务器地址的协议族为IPv4。
cnt.sin_port = htons(atoi(argv[1]));:将从命令行参数获取的字符串端口号转换成网络字节序的整数端口号,并将其存储在 sockaddr_in 结构体的 sin_port 字段中。
cnt.sin_addr.s_addr = inet_addr("0.0.0.0");:设置服务器IP地址为 0.0.0.0,表示客户端可以连接到任何一个可用的IP地址。
connect(socket_fd, (struct sockaddr *)&cnt, sizeof(cnt)):连接到服务器。这个函数将套接字连接到 sockaddr_in 结构体中描述的IP地址和端口号。如果连接失败,将返回一个负数值。
ch = read(0, buf, 128);:从标准输入读取最多128个字节的数据,并将其存储在 buf 中。
send(socket_fd, buf, ch, 0):将读取的数据发送到服务器。如果发送失败,将返回一个负数值。
close(socket_fd);:关闭套接字。
在主循环中,该客户端将不断地从标准输入读取数据并将其发送到服务器,直到程序被强制终止。
结果展示