TCP是TCP/IP协议族中一个比较重要的协议,这是一种可靠、建立链接、面向字节流的传输,工作在传输层。和TCP相对的不可靠、无链接、面向数据报的协议UDP,了解UDP客户端与服务器之间通信请戳UDP协议实现的服务器与客户端通信
TCP协议建立连接
首先我们通过一个大概的图来了解。
建立连接首先必须是服务器启动,这没什么好说的,服务器为被动方,客户端为主动方,当客户端发起请求建立连接,服务器被动接受,经过上图三次握手建立连接,注意这三次连接都是在操作系统内部实现的。
那么我们就来介绍建立连接的相关API
socket获取通信的文件描述符。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
端口号的绑定
#include <sys/socket.h>
int bind(int sock, const struct sockaddr* address, socklen_t address_len);
这俩个API在UDP协议实现的服务器与客户端通信中有详细的参数返回值介绍。
作为服务器首先要进行监听
#include <sys/socket.h>
int listen(int socket, int backog);
参数介绍:
socket: 为socket函数返回的文件描述符
backlog: 建立连接过程中等待建立的请求个数
返回值:
成功返回0,失败返回-1;
这里我们再介绍这个backlog参数的含义:
这就相当与我们去银行取钱,到了发现人比较多,这个时候就需要坐在凳子上等,那么这里的凳子就是backlog的含义,就是现在最大的等待处理的个数。
接受请求:
#include <sys.socket.h>
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
参数:
socket:文件描述符
address:输出型参数,用来接受对方的IP 端口号,是一个结构体。
address_len:是一个输入输出型参数,输入进去为当前address的大小,输出为实际的大小。
返回值:
返回值成功为一个文件描述符,失败为-1;
这里要解释一下,socket不是已经创建出一个文件描述符,怎么还有?
accept的文件描述符,使用来直接进行数据的发送与收取,处理请求。
前面socket创建的文件描述符,在listen函数中用,是为了来接受请求.
举个例子:
socket创建的文件描述符,就像一个饭店,拉客人(listen函数)就是在外面进行监听,是否有客人要来饭店,当listen接受到客人,就会带到饭店,然后交给服务员(accept)来进行对客人吃饭请求的处理。这时候accpet就会是这个客人的请求的处理。
客户端连接函数:
#include <sys/socket.h>
int connect(int socketfd, const struct sockaddr* addr, socklen_t addrlen);
参数:
socketfd: 文件描述符;
addr :为要建立连接服务器的IP、端口号、使用网络协议IPV4;
addrlen:addr结构体的大小;
参数:
建立成功返回0,失败返回-1;
相关API介绍完我们开始实现一个简单的客户端与服务器
我们实现最简单的没有业务处理逻辑的通信,就把接受到的消息,发送给客户端
服务器
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
void Response(int new_sock, struct sockaddr_in * client)
{
while (1)
{
char buf[512] = {0};
// 接受请求
ssize_t rd = read(new_sock, buf, sizeof(buf)-1);
if (rd < 0)
{
perror("read error\n");
return;
}
if (rd == 0)
{
printf("read done!\n");
return;
}
buf[rd] = '\0';
// 处理请求
printf("client say [%s:%d] # %s\n",inet_ntoa(client->sin_addr), ntohs(client->sin_port), buf);
// response 响应
write(new_sock, buf, strlen(buf));
}
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
printf("usage:./server_tcp [ip] [port]");
return 1;
}
int sock = socket(AF_INET, SOCK_STREAM, 0); // 文件描述符
if (sock < 0)
{
perror("sock error\n");
return 2;
}
// 绑定端口号
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);
server.sin_port = htons(atoi(argv[2]));
int b = bind(sock, (struct sockaddr*)&server, sizeof(server));
if (b < 0)
{
perror("bind error\n");
return 3;
}
// 监听
int l = listen(sock, 3); // 最大的连接数量
if (l < 0)
{
perror("listen error\n");
return 4;
}
printf("bind and listen .... \n");
// 接受请求处理
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_sock = accept(sock, (struct sockaddr*)&client, &len);
Response(new_sock, &client); // 处理函数
return 0;
}
客户端:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main(int argc, char* argv[])
{
if (argc != 3)
{
perror("usage:./client_tcp [ip] [port]");
return 1;
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror("scoket error\n");
return 2;
}
// 建立连接
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);
server.sin_port = htons(atoi(argv[2]));
socklen_t len = sizeof(server);
int co = connect(sock, (struct sockaddr*)&server, len);
if (co < 0)
{
perror("connect error\n");
return 3;
}
while (1)
{
char buf[512] = {0};
ssize_t rd = read(0, buf, sizeof(buf)-1);
if (rd < 0)
{
perror("read error\n");
return 4;
}
if (rd == 0)
{
printf("read done!\n");
return 5;
}
buf[rd-1] = '\0';
write(sock, buf, strlen(buf)); // 会阻塞的写与读
ssize_t sockrd = read(sock, buf, sizeof(buf)-1);
if (sockrd < 0)
{
perror("read error\n");
return 4;
}
if (sockrd == 0)
{
printf("read done!\n");
return 5;
}
buf[rd] = '\0';
printf("server say # %s\n", buf);
}
return 0;
}
结果:
客户端
服务器
TCP协议的的断开连接
建立连接需要三次交互,断开连接需要四次交互。
我们用图分析一下
当数据发送完客户端调用close 这时候就会发送 FIN 服务器内核立刻恢复ACK 服务器再调用close 发送FIN 客户端回复ACK 进入等待。一段时间后退出。