传输控制协议
(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流
的传输层通信协议
。在前面讲解UDP时已经将一些预备知识和相关接口做了详解。接下来就TCP单独会用到的接口进行详解。
没讲的接口老铁可以去看-》【Linux网络编程必学!】——Linux_网络编程_UDP -》
link
listen()函数
:listen函数仅被TCP服务器调用,它的作用是将用sock创建的主动套接口转换成被动套接口,并等待来自客户端的连接请求。
#include
int listen(int sockfd,int backlog);
成功返回:0 失败返回:-1
套接口描述字
;以完成连接队列
和未完成连接队列
。未完成队列中存放的是TCP连接的三路握手未完成的连接,accept函数是从以连接队列中取连接返回给进程;当以连接队列为空时,进程将进入睡眠状态。accept()函数
:accept函数由TCP服务器调用,从已完成连接队列头返回一个已完成连接,如果完成连接队列为空,则进程进入睡眠状态。
#include
int accept(int listenfd, struct sockaddr *client, socklen_t * addrlen);
成功返回:非负描述字 失败返回:-1
套接口描述字
;指向连接方的套接口地址结构和该地址结构的长度
;该函数返回的是一个全新的套接口描述字;如果对客户段的信息不感兴趣,可以将第二和第三个参数置为空。connect()函数
:当用socket建立了套接口后,可以调用connect为这个套接字指明远程端的地址;如果是字节流套接口,connect就使用三次握手
建立一个连接;如果是数据报套接口,connect仅指明远程端地址,而不向它发送任何数据。#include
int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);
成功返回:0 失败返回:-1
套接口描述字
;第二和第三个参数分别是一个指向套接口地址结构的指针和该结构的大小。send()/recv()
:TCP套接字提供了send()和recv()函数,用来发送和接收操作。这两个函数与write()和read()函数很相似,只是多了一个附加的参数。
send()函数用于数据的发送
。#include
#include < sys/socket.h >
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
成功返回:返回写出的字节数 失败返回:-1
前3个参数与write()相同,参数flags是传输控制标志。
recv()函数用于数据的发送
。#include
#include < sys/socket.h >
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
成功返回:返回读入的字节数 失败返回:-1
前3个参数与read()相同,参数flags是传输控制标志。
write()和read()函数在前面博客已经讲解过
-》linksocket()/bind()/close()在前面的博客也已讲解过
-》link总共发送3个包
。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。
进行三次握手:
第一次握手
:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN
。此时客户端处于 SYN_SEND 状态。第二次握手
:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s)
。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。第三次握手
:客户端收到 SYN 报文之后,会发送一个 ACK 报文
,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。发送第一个SYN的一端将执行主动打开(active open),接收这个SYN并发回下一个SYN的另一端执行被动打开(passive open),在socket编程中,客户端执行connect()时,将触发三次握手。
为什么需要三次握手,两次不行吗?
第一次握手
:客户端发送网络包,服务端收到了。第二次握手
:服务端发包,客户端收到了。第三次握手
:客户端发包,服务端收到了。客户端或服务器均可主动发起挥手动作。
第一次挥手
:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。第二次挥手
:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。第三次挥手
:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。第四次挥手
:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。收到一个FIN只意味着在这一方向上没有数据流动。客户端执行主动关闭并进入TIME_WAIT是正常的,服务端通常执行被动关闭,不会进入TIME_WAIT状态。
挥手为什么需要四次?
tcpClient.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
class tcpClient
{
public:
tcpClient(std::string _ip = "127.0.0.1",int _port = 8080)
:svr_ip(_ip)
,svr_port(_port)
{
}
void initClient()
{
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
std::cerr << "sock error" << std::endl;
exit(2);
}
//bind不需要 客户端!!!由系统自动进行绑定
//也不需要监听,服务器需要监听
//也不需要accept
//面向链接,需要链接
struct sockaddr_in svr;
svr.sin_family = AF_INET;
svr.sin_port = htons(svr_port);
svr.sin_addr.s_addr = inet_addr(svr_ip.c_str());
if(connect(sock,(struct sockaddr*)&svr,sizeof(svr)) !=0)
{
std::cout<<"connect error! "<<std::endl;
exit(3);
}
//connect success!
}
void start()
{
char msg[64];
while(1)
{
std::cout<< "Please enter:";
fflush(stdout);
size_t s = read(0,msg,sizeof(msg)-1);
if(s > 0)
{
msg[s]=0;
send(sock,msg,strlen(msg),0);
size_t ss = recv(sock,msg,sizeof(msg)-1,0);
if(ss > 0)
{
msg[ss] = 0;
std::cout<< "server echo: "<< msg << std::endl;
}
}
}
}
~tcpClient()
{
close(sock);
}
private:
int svr_port;
std::string svr_ip;
int sock;
};
tcpServer.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BACKLOG 5
class tcpServer
{
public:
tcpServer(int _port)
:port(_port)
,lsock(-1)
{
}
void initServer()
{
signal(SIGCHLD,SIG_IGN);
lsock = socket(AF_INET,SOCK_STREAM,0);
if(lsock < 0)
{
std::cerr<< " socket error " <<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr= INADDR_ANY;
if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
std::cerr<<"bind error "<<std::endl;
exit(3);
}
if(listen(lsock,BACKLOG) < 0)
{
std::cerr<< "bind error " << std::endl;
exit(4);
}
}
void service(int sock)
{
char buffer[1024];
while(1)
{
size_t s = recv(sock,buffer,sizeof(buffer)-1,0);
if(s > 0)
{
buffer[s] = 0;
std::cout << "client: "<< buffer;
send(sock, buffer,strlen(buffer),0);
}
else if(s == 0)
{
std::cout<<" Client quit! " << std::endl;
close(sock);
break;
}
else
{
std::cout<< "recv client data error !" << std::endl;
break;
}
}
close(sock);
}
void start()
{
sockaddr_in endpoint;
while(1)
{
socklen_t len = sizeof(endpoint);
int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
if(sock < 0)
{
std::cerr<<"accept error"<<std::endl;
continue;
}
std::string cli_info = inet_ntoa(endpoint.sin_addr);//四字节Ip改为点分十进制
cli_info += " : ";
cli_info += std::to_string(ntohs(endpoint.sin_port));
std::cout << cli_info<< std::endl;
std::cout << "get a new link!"<< "sock: " << sock <<std::endl;
pid_t id = fork();
if(id == 0)
{
close(lsock);
service(sock);
exit(0);
}
close(sock);
//waitpid(-1,NULL,0);
}
}
~tcpServer()
{
}
private:
int port;
int lsock;
};
tcpClient.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
class tcpClient
{
public:
tcpClient(std::string _ip = "127.0.0.1",int _port = 8080)
:svr_ip(_ip)
,svr_port(_port)
{
}
void initClient()
{
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
std::cerr << "sock error" << std::endl;
exit(2);
}
//bind不需要 客户端!!!由系统自动进行绑定
//也不需要监听,服务器需要监听
//也不需要accept
//面向链接,需要链接
struct sockaddr_in svr;
svr.sin_family = AF_INET;
svr.sin_port = htons(svr_port);
svr.sin_addr.s_addr = inet_addr(svr_ip.c_str());
if(connect(sock,(struct sockaddr*)&svr,sizeof(svr)) !=0)
{
std::cout<<"connect error! "<<std::endl;
exit(3);
}
//connect success!
}
void start()
{
char msg[64];
while(1)
{
std::cout<< "Please enter:";
fflush(stdout);
size_t s = read(0,msg,sizeof(msg)-1);
if(s > 0)
{
msg[s]=0;
send(sock,msg,strlen(msg),0);
size_t ss = recv(sock,msg,sizeof(msg)-1,0);
if(ss > 0)
{
msg[ss] = 0;
std::cout<< "server echo: "<< msg << std::endl;
}
}
}
}
~tcpClient()
{
close(sock);
}
private:
int svr_port;
std::string svr_ip;
int sock;
};
tcpServer.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BACKLOG 5
class tcpServer
{
public:
tcpServer(int _port)
:port(_port)
,lsock(-1)
{
}
void initServer()
{
signal(SIGCHLD,SIG_IGN);
lsock = socket(AF_INET,SOCK_STREAM,0);
if(lsock < 0)
{
std::cerr<< " socket error " <<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr= INADDR_ANY;
if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
std::cerr<<"bind error "<<std::endl;
exit(3);
}
if(listen(lsock,BACKLOG) < 0)
{
std::cerr<< "bind error " << std::endl;
exit(4);
}
}
static void service(int sock)
{
char buffer[1024];
while(1)
{
size_t s = recv(sock,buffer,sizeof(buffer)-1,0);
if(s > 0)
{
buffer[s] = 0;
std::cout << "client: "<< buffer;
send(sock, buffer,strlen(buffer),0);
}
else if(s == 0)
{
std::cout<<" Client quit! " << std::endl;
close(sock);
break;
}
else
{
std::cout<< "recv client data error !" << std::endl;
break;
}
}
close(sock);
}
static void *serviceRoutine(void *arg)
{
pthread_detach(pthread_self());
std::cout<<"create a new thread for IO" << std::endl;
int tsock = *(int *)arg;
service(tsock);
}
void start()
{
sockaddr_in endpoint;
while(1)
{
socklen_t len = sizeof(endpoint);
int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
if(sock < 0)
{
std::cerr<<"accept error"<<std::endl;
continue;
}
std::string cli_info = inet_ntoa(endpoint.sin_addr);//四字节Ip改为点分十进制
cli_info += " : ";
cli_info += std::to_string(ntohs(endpoint.sin_port));
std::cout << cli_info<< std::endl;
std::cout << "get a new link!"<< "sock: " << sock <<std::endl;
pthread_t tid;
pthread_create(&tid, nullptr, serviceRoutine, (void*)&sock);//bug
// pid_t id = fork();
// if(id == 0)
// {
// close(lsock);
// service(sock);
// exit(0);
// }
// close(sock);
//waitpid(-1,NULL,0);
}
}
~tcpServer()
{
}
private:
int port;
int lsock;
};
区别
:
应用场景
:
什么是面向连接,什么是面向无连接
:
TCP 为什么是可靠连接
: