TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,当应用程序采用 TCP 发送消息时,虽然可以保证发送的顺序,但还是犹如没有任何间隔的数据流发送给接收端。TCP 为提供可靠性传输,实行“顺序控制”或“重发控制”机制。此外还具备“流控制(流量控制)”、“拥塞控制”、提高网络利用率等众多功能。
1.TCP协议
- TCP 与 UDP 的区别相当大。它充分地实现了数据传输时各种控制功能,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在 UDP 中都没有。
- 此外,TCP 作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。
- 根据 TCP 的这些机制,在 IP 这种无连接的网络上也能够实现高可靠性的通信( 主要通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现)。
2.三次握手
- TCP 提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好两端之间的准备工作。
- 所谓三次握手是指建立一个 TCP 连接时需要客户端和服务器端总共发送三个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发。
下面来看看三次握手的流程图:
- 第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
- 第二次握手:服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
- 第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
3.四次挥手
- 四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。
- 由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
下面来看看四次挥手的流程图:
- 中断连接端可以是客户端,也可以是服务器端。
- 第一次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态。意思是说"我客户端没有数据要发给你了",但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据。
- 第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端,你的请求我收到了,但是我还没准备好,请继续你等我的消息。这个时候客户端就进入FIN_WAIT_2 状态,继续等待服务器端的FIN报文。
- 第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端,好了,我这边数据发完了,准备好关闭连接了。服务器端进入LAST_ACK状态。
- 第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。服务器端收到ACK后,就知道可以断开连接了。客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。最终完成了四次握手。
4.上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况
具体流程如下图(同时挥手):
5.函数接口
(1)socket():创建套接字
函数原型:
int socket(int domain, int type, int protocol);
参数说明:
domain:协议族
AF_UNIX, AF_LOCAL 本地通信
AF_INET ipv4
AF_INET6 ipv6
type:套接字类型
SOCK_STREAM:流式套接字
SOCK_DGRAM:数据报套接字
protocol:协议 - 填0 自动匹配底层 ,根据type 系统默认自动帮助匹配对应协议
传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
返回值:
成功:文件描述符
失败:-1
(2)bind():绑定IP和端口
函数原型:
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数说明:
sockfd:文件描述符
addr:通用结构体,根据socket第一个参数选择的通信方式最终确定这需要真正填充传递的结构体是那个类型。强转后传参数。
addrlen:填充的结构体的大小
返回值:
成功:0
失败:-1
通用结构体:相当于预留一个空间
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
ipv4的结构体:
struct sockaddr_in {
sa_family_t sin_family; //协议族AF_INET
in_port_t sin_port; //端口
struct in_addr sin_addr;
};
struct in_addr {
uint32_t s_addr; //IP地址
};
本地址通信结构体:
struct sockaddr_un {
sa_family_t sun_family; //AF_UNIX
char sun_path[108]; //在本地创建的套接字文件的路径及名字
};
ipv6通信结构体:
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
struct in6_addr {
unsigned char s6_addr[16];
};
(3)listen():监听,将主动套接字变为被动套接字
函数原型:
int listen(int sockfd, int backlog);
参数说明:
sockfd:套接字
backlog:同时响应客户端请求链接的最大个数,不能写0,不同平台可同时链接的数不同,一般写6-8个
(队列1:保存正在连接)
(队列2,连接上的客户端)
返回值:
成功:0
失败:-1
(4)accept():阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,则accept()函数返回,返回一个用于通信的套接字文件;
函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:
Sockfd :套接字
addr:链接客户端的ip和端口号 如果不需要关心具体是哪一个客户端,那么可以填NULL; addrlen:结构体的大小 如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值:
成功:文件描述符; //用于通信
失败:-1
(5)recv():接收数据
函数原型:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数说明:
sockfd:acceptfd
buf:接受数据的缓冲区
len:接收数据的缓冲区大小
flags:一般填0,相当于read()函数
MSG_DONTWAIT:非阻塞
返回值:
< 0 失败出错 更新errno
==0 表示客户端退出
> 0 成功接收的字节个数
6.connect():用于连接服务器
函数原型:
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数说明:
sockfd:socket函数的返回值
addr:填充的结构体是服务器端的;
addrlen:结构体的大小
返回值 :
成功:0
失败:-1
7.send():发送数据
函数原型:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数说明:
sockfd:socket函数的返回值
buf:发送内容存放的地址
len:发送内存的长度 flags:如果填0,相当于write();
6.示例
服务端:
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc, char const *argv[])
{
if (argc != 2){
printf("please input %s <port>\n", argv[0]);
return -1;
}
int sockfd, acceptfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0){
perror("socket err.");
return -1;
}
struct sockaddr_in serveraddr, caddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[1]));
// serveraddr.sin_addr.s_addr=inet_addr(argv[1]);
//自动获取ip
//serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
// serveraddr.sin_addr.s_addr=INADDR_ANY; //0.0.0.0
serveraddr.sin_addr.s_addr = inet_addr("0.0.0.0");
socklen_t len = sizeof(caddr);
if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0){
perror("bind err.");
return -1;
}
if (listen(sockfd, 5) < 0){
perror("listen err.");
return -1;
}
while (1){
acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0){
perror("accept err.");
return -1;
}
printf("client:ip=%s port=%d\n",
inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
char buf[128];
pid_t pid = fork();
if (pid < 0){
perror("fork err.");
return -1;
}
else if (pid == 0){ //发送
int sendbyte;
while (1){
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
sendbyte = send(acceptfd, buf, sizeof(buf), 0);
if (sendbyte < 0){
perror("send error.");
return -1;
}
}
}
else{
int recvbyte;
while (1){
recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
if (recvbyte < 0){
perror("recv err.");
return -1;
}
else if (recvbyte == 0){
printf("client exit.\n");
kill(pid,SIGKILL);
wait(NULL);
break;
}
else{
printf("buf:%s\n", buf);
}
}
close(acceptfd);
}
}
close(sockfd);
return 0;
}
客户端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
if (argc != 3){
printf("please input %s <ip> <port>\n", argv[0]);
return -1;
}
//1.创建套接子
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0){
perror("socket error.");
return -1;
}
printf("sockfd=%d\n", sockfd);
//填充ipv4的通信结构体 服务器的ip和端口
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
//2.请求链接服务器
if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0){
perror("connect error.");
return -1;
}
//5.循环发送消息 通信
char buf[128];
pid_t pid = fork();
if (pid < 0){
perror("fork err.");
return -1;
}
else if (pid == 0){
int recvbyte;
while (1){
recvbyte = recv(sockfd, buf, sizeof(buf), 0);
if (recvbyte < 0){
perror("recv err.");
return -1;
}
printf("buf:%s\n", buf);
}
}
else{
int sendbyte;
while (1){
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
sendbyte = send(sockfd, buf, sizeof(buf), 0);
if (sendbyte < 0){
perror("send error.");
return -1;
}
if(strncmp(buf,"quit",4)==0){
kill(pid,SIGKILL);
wait(NULL);
exit(-1);
}
}
}
close(sockfd);
return 0;
}