目录
1、TCP概览
1.1 TCP基本特征
1.2 TCP通信流程基本原理
2、TCP编程的函数接口说明
3、TCP通讯测试代码
1、TCP概览
TCP全称 Transmition Control Protocol,即:传输控制协议。是面向连接的协议。通常,TCP 通信还会被冠以 可靠传输协议 的头衔。但请注意,这里的可靠并非指发出去的数据对方一定能收到(这是不可能的),而仅指TCP能使发送方可靠地知道对方是否收到了数据。
1.1 TCP基本特征
- 有连接:通信双方需要事先连接成功,方可传输数据
- 有确认:一方收到对端的任何数据,都会给另一方发回执确认
- 保证数据有序、不重复、丢失会重发
- 如果网络拥堵,会自动调节发送量
- 采用帧异步的流式通信方式(即通信双方每次的收发数据量不必相等)
简单来讲,TCP 类似于打电话,说话前需要花一定的时间接通电话,等到对方接听了之后双方才能开始通信,通信的过程中每个数据的传送,接收方都会给发送方回执确认,断开的时候也会互相通知以便于释放各自相关的资源。可以看出来,TCP 相对于 UDP 而言资源开销更大,提供更丰富的功能,TCP适合用在如下情形:
- 传输质量要求较高,不能丢失数据
- 大数据量的通信,以至于通信前后的连接和断开的开销可以忽略不计
- 用户登录、账户管理等相关的功能
1.2 TCP通信流程基本原理
TCP的通信流程跟打电话是几乎一样的,因此可以将通信的过程细分为主动发起连接者(客户端)和被动接受连接者(服务端)两方来分别讨论。
被动的服务端
- 建立TCP套接字sockfd,即通信端点
- 绑定套接字sockfd与网络地址,即IP+端口
- 设定套接字sockfd进入被动监听状态,即将套接字设定为监听套接字
- 静静等待远程客户端的连接请求
- 收到连接请求后,得到一个专用于收发数据的连接套接字connfd
- 使用连接套接字connfd与客户端通信
主动的客户端
- 建立TCP套接字sockfd,即通信端点
- 对服务端发起连接请求
- 若连接成功,则直接通过套接字sockfd与服务端通信
注意点:
- 在服务端中,监听套接字和连接套接字是严格区分的,不可混用
- 服务端所绑定的地址(IP+PORT)需要对外公开,否则客户端无法发起连接
- 客户端在发起连接前一般无需绑定地址,此时系统会为此连接自动分配恰当的地址资源
2、TCP编程的函数接口说明
TCP通讯的步骤如下:
客户端(client)
1、建立套接字(socket)
//2、绑定主机的IP地址和端口号(bind)(可以省略)
3、填充服务器的结构体,向服务器发起连接请求(connect)
4、聊天 -- 发送数据(send/write)
5、断开连接(close)
服务器(server)
1、建立套接字(socket)
2、填充服务器的结构体,绑定主机的IP地址和端口号(bind)
3、设置监听(listen)
4、阻塞等待接收客户端的连接(accept)新的套接字文件描述符
5、创建结构体存放客户端IP和端口,聊天 -- 接收数据 (recv/read)
6、断开连接(close)
函数接口说明如下:
1、建立套接字(socket)
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
函数作用:建立套接字,返回套接字文件描述符
函数参数:domain:你要选择哪一种地址族
PF_INET/AF_INET IPV4网络协议 PF ---> Protocol Family
PF_INET6/AF_INET6 IPV6网络协议 AF ---> Address Family
type:你要选择哪一种协议
SOCK_STREAM选择TCP -- 流式套接字 --->Stream Sockets
SOCK_DGRAM选择UDP -- 数据报套接字 --->Datagram Sockets
protocol:传0表示使用默认协议
返回值:成功:套接字文件描述符sockfd
失败:-1
2、绑定主机的IP地址和端口号(bind)
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
函数作用:绑定主机的IP地址和端口号
参数:sockfd:套接字文件描述符
addr:自己的IP地址和端口号
addelen:地址的大小长度
返回:成功
3、发起连接(connect)
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr * addr,socklen_t addrlen);
函数作用:发起连接
函数参数:sockfd:套接字文件描述符
addr:对方的IP地址和端口号
addrlen:地址的长度(sizeof(struct sockaddr_in)))
IPV4结构体
struct sockaddr_in
{
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
};
struct in_addr
{
in_addr_t s_addr;/in_addr_t为32位的unsigned int,该无符号整数采用大端字节序/
};
初始化 IP地址和端口号 --IPV4
struct sockaddr_in sereverAddr;
serverAddr.sin_family = PF_INET;
serverAddr.sin_port = htons(5000);
serverAddr.sin_addr.s_addr = inet_addr("主机IP地址");
4、聊天 -- 发送数据(send)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd,const void* buf,size_t len,int flags);
函数作用:用于网络中发送数据
参数:sockfd:套接字文件描述符
buf:你要发送的数据
len:你要发送数据的大小,以字节位单位
flags:一般默认为0
返回值:成功:发送的字节数
失败:-1
5、断开连接(close)
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);//将主机端口号转成网络端口号
uint16_t ntohs(uint16_t netshort);//将网络端口号转成主机端口号
说明:h代表主机(host) n代表网络(network) s代表端口号(short)
返回值:成功:要转换的字节序
失败:-1
从什么(h,n)端口号到(to)什么(n,h)端口号(s)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp); //将主机IP转成网络IP
char* inet_ntoa(struct in_addr in); //将网络IP转成主机IP
主机转网络addr(address) 网络转主机ntoa(network to address)
tcp的服务端需要设置端口复用
端口复用设置在服务器的代码里面
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int s,int level,int optname,const void* optval,socklen_toptlen);
函数作用:设置端口号可以复用
参数:s:套接字文件描述符
level:代表欲设置的网络层,一般设成SOL_SOCKET以存取socket层
optname:SO_REUSEADDR端口号复用
optval:代表欲设置的值,比如给他一个1,表示使能 端口号复用
optlen则为optval的长度 //所以设置端口号可以复用,这两条语句放在绑定bind之前
int optval = 1;文章来源:https://www.toymoban.com/news/detail-460231.html
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));文章来源地址https://www.toymoban.com/news/detail-460231.html
3、TCP通讯测试代码
//tcp_server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_PORT 60000 //-->1024 ~ 65535
#define SERVER_IP "192.168.5.184" //-->虚拟机ip或主机ip
/*
TCP的服务端的代码实现步骤
1、建立套接字
2、绑定(IP和端口号)
3、监听(listen)
4、阻塞连接(accept)
5、接收数据(recv,read)
6、关闭
*/
int main(int argc,char** argv)
{
//手动输入端口号和IP地址
// if(argc != 3)
// {
// perror("./a.out ip port");
// return -1;
// }
int ret = 0;
char buf[1024] = { 0 };
//1、建立套接字
//参数: 地址族 流式套接字 默认协议
int socketfd = socket(AF_INET,SOCK_STREAM,0);
if(socketfd == -1)
{
perror("socket fail");
return -1;
}
//设置端口复用
int optval = 1;
//参数 套接字文件描述符 网络层 端口复用 设置值 设置值的大小
setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));
//2、填充服务端结构体的端口和IP
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
//server_addr.sin_port = htons(atoi(argv[2])); //传参方式
server_addr.sin_port = htons(SERVER_PORT); //宏定义方式
//server_addr.sin_addr.s_addr = inet_addr(argv[1]); //传参方式
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); //宏定义方式
//3、绑定
//参数 套接字文件描述符 IP和端口号(旧结构体指针强转取地址)新结构体的大小
ret = bind(socketfd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror("bind fail");
return -1;
}
//4、监听
//参数 套接字文件描述符 支持的客户端连接最大数量
ret = listen(socketfd,20);
if(ret == -1)
{
perror("bind fail");
return -1;
}
//5、阻塞等待客户端连接
//printf("绑定服务器IP:%s 端口号PORT:%hu\n",argv[1],atoi(argv[2]));
printf("绑定服务器IP:%s 端口号PORT:%hu\n",SERVER_IP,SERVER_PORT);
struct sockaddr_in client_addr;
int len = sizeof(struct sockaddr_in);
//参数 套接字文件描述符 IP和端口号(旧结构体指针强转取地址)长度取址
//返回新的套接字文件描述符
int newClientfd = accept(socketfd,(struct sockaddr*)&client_addr,&len);
//6、拿到客户端的地址和端口号
char* ip = inet_ntoa(client_addr.sin_addr);
int port = ntohs(client_addr.sin_port);
printf("客户端的IP:%s 端口号:%d\n",ip,port);
//7、接收数据
while(1)
{
//缓存区清零
bzero(buf,sizeof(buf));
//接收数据,计算返回值(buf真实数据大小)
//参数 套接字文件描述符 缓存区 缓存区大小 默认属性
//使用返回的新套接字文件描述符
//ret = recv(newClientfd,buf,sizeof(buf),0);
ret = read(newClientfd,buf,sizeof(buf));
if(ret == 0)
{
printf("客户端掉线,服务器退出。。。\n");
return -1;
}
printf("收到数据:%s 大小:%d\n",buf,ret);
//做主动退出的判断条件
if(!strcmp(buf,"exit"))
break;
}
//8、关闭套接字
close(socketfd);
close(newClientfd);
return 0;
}
//tcp_client.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_PORT 60000 //-->1024 ~ 65535
#define SERVER_IP "192.168.5.184" //-->服务器的IP
/*
TCP的客户端的代码实现步骤
1、建立套接字
2、绑定(IP和端口号)(可有可无)
3、发送连接请求(connect)
4、发送数据(send,write)
5、关闭
*/
int main(int argc,char** argv)
{
//手动输入端口号和IP地址
// if(argc != 3)
// {
// perror("./a.out ip port");
// return -1;
// }
int ret = 0;
char buf[1024] = { 0 };
//1、建立套接字
//参数: 地址族 流式套接字 默认协议
int socketfd = socket(AF_INET,SOCK_STREAM,0);
if(socketfd == -1)
{
perror("socket fail");
return -1;
}
//2、填充服务端结构体的端口和IP
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
//server_addr.sin_port = htons(atoi(argv[2])); //传参方式
server_addr.sin_port = htons(SERVER_PORT); //宏定义方式
//server_addr.sin_addr.s_addr = inet_addr(argv[1]); //传参方式
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); //宏定义方式
//3、连接服务器
//参数 套接字文件描述符 IP和端口号(旧结构体指针强转取地址)新结构体的大小
ret = connect(socketfd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in));
if(ret < 0)
{
perror("connect fail");
return -1;
}
printf("连接服务器成功[%s][%d]\n",SERVER_IP,SERVER_PORT);
//printf("连接服务器IP:%s 端口号PORT:%hu\n",argv[1],atoi(argv[2]));
//4、与服务器之间发送数据
while(1)
{
//缓存区清零
bzero(buf,sizeof(buf));
//发送数据,计算返回值(buf真实数据大小)
scanf("%s",buf);
//参数 套接字文件描述符 缓存区 缓存区真实大小 默认属性
//ret = send(socketfd,buf,strlen(buf),0);
ret = write(socketfd,buf,strlen(buf));
if(ret == -1)
{
printf("服务端掉线,客户端发送数据失败。。。\n");
return -1;
}
printf("发送成功 ret:%d\n",ret);
//做主动退出的判断条件
if(!strcmp(buf,"exit"))
break;
}
//关闭套接字
close(socketfd);
return 0;
}
到了这里,关于关于c语言的tcp通讯详细讲解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!