通信流程:
服务端代码;
// TCP 通信的服务器端 #include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main() { // 1.创建socket(用于监听的套接字) int lfd = socket(AF_INET, SOCK_STREAM, 0); if(lfd == -1) { perror("socket"); exit(-1); } // 2.绑定 struct sockaddr_in saddr; saddr.sin_family = AF_INET; // inet_pton(AF_INET, "192.168.193.128", saddr.sin_addr.s_addr); saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0 表示任意地址 saddr.sin_port = htons(9999); int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)); if(ret == -1) { perror("bind"); exit(-1); } // 3.监听 ret = listen(lfd, 8); if(ret == -1) { perror("listen"); exit(-1); } // 4.接收客户端连接 struct sockaddr_in clientaddr; int len = sizeof(clientaddr); int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len); if(cfd == -1) { perror("accept"); exit(-1); } // 输出客户端的信息 char clientIP[16]; inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP)); unsigned short clientPort = ntohs(clientaddr.sin_port); printf("client ip is %s, port is %d\n", clientIP, clientPort); // 5.通信 char recvBuf[1024] = {0}; while(1) { // 获取客户端的数据 int num = read(cfd, recvBuf, sizeof(recvBuf)); if(num == -1) { perror("read"); exit(-1); } else if(num > 0) { printf("recv client data : %s\n", recvBuf); } else if(num == 0) { // 表示客户端断开连接 printf("clinet closed..."); break; } char * data = "hello,i am server"; // 给客户端发送数据 write(cfd, data, strlen(data)); } // 关闭文件描述符 close(cfd); close(lfd); return 0; }
客户端
// TCP通信的客户端 #include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main() { // 1.创建套接字 int fd = socket(AF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); exit(-1); } // 2.连接服务器端 struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; inet_pton(AF_INET, "192.168.193.128", &serveraddr.sin_addr.s_addr); serveraddr.sin_port = htons(9999); int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); if(ret == -1) { perror("connect"); exit(-1); } // 3. 通信 char recvBuf[1024] = {0}; while(1) { char * data = "hello,i am client"; // 给客户端发送数据 write(fd, data , strlen(data)); sleep(1); int len = read(fd, recvBuf, sizeof(recvBuf)); if(len == -1) { perror("read"); exit(-1); } else if(len > 0) { printf("recv server data : %s\n", recvBuf); } else if(len == 0) { // 表示服务器端断开连接 printf("server closed..."); break; } } // 关闭连接 close(fd); return 0; }
编译:
运行结果:
TCP 是一种面向连接的单播协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓的“连接”,其实是客户端和服务器的内存里保存的一份关于对方的信息,如 IP 地址、端口号等。
TCP 可以看成是一种字节流,它会处理 IP 层或以下的层的丢包、重复以及错误问题。在连接的建立过程中,双方需要交换一些连接的参数。这些参数可以放在 TCP 头部。
TCP 提供了一种可靠、面向连接、字节流、传输层的服务,采用三次握手建立一个连接。采用四次挥手来关闭一个连接。
三次握手的目的是保证双方互相之间建立了连接。
三次握手发生在客户端连接的时候,当调用connect(),底层会通过TCP协议进行三次握手。
建立连接的主动方是客户端;
为什么不能两次握手:各自确认双方都能收发数据,
第一次握手:客户端确认客户端的发送数据功能是可以的;
第二次握手:服务端确认客户端的发送数据和服务端的接收数据功能是可以的;
第三次握手:收到数据后,客户端确认服务端的发送功能,接收功能,客户端的接收功能是可以的,发送ack给服务端表示自己的接收数据功能没有问题;
总共确认八个功能;
还可以避免历史连接,节省资源
为什么不能四次握手
中间一次握手可以代替两次握手,服务端syn和ack的数据可以一起发送;
第一次握手:
1.客户端将SYN标志位置为1
2.生成一个随机的32位的序号seq=J , 这个序号后边是可以携带数据(数据的大小)
第二次握手:
1.服务器端接收客户端的连接: ACK=1
2.服务器会回发一个确认序号: ack=客户端的序号 + 数据长度 + SYN/FIN(按一个字节算)
3.服务器端会向客户端发起连接请求: SYN=1
4.服务器会生成一个随机序号:seq = K
第三次握手:
1.客户单应答服务器的连接请求:ACK=1
2.客户端回复收到了服务器端的数据:ack=服务端的序号 + 数据长度 + SYN/FIN(按一个字节算)
滑动窗口(Sliding window)是一种流量控制技术。早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况,同时发送数据,导致中间节点阻塞掉包,谁也发不了数据,所以就有了滑动窗口机制来解决此问题。滑动窗口协议是用来改善吞吐量的一种技术,即容许发送方在接收任何应答之前传送附加的包。接收方告诉发送方在某一时刻能送多少包(称窗口尺寸)。
TCP 中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0时,发送方一般不能再发送数据报。
滑动窗口是 TCP 中实现诸如 ACK 确认、流量控制、拥塞控制的承载结构。
四次挥手发生在断开连接的时候,在程序中当调用了close()会使用TCP协议进行四次挥手。
客户端和服务器端都可以主动发起断开连接,谁先调用close()谁就是发起。
因为在TCP连接的时候,采用三次握手建立的的连接是双向的,在断开的时候需要双向断开。
为什么要四次挥手,FIN表示我要断开连接,为防止ack M+1和FIN中有数据未传输完,所以需要四次挥手;