TCP协议介绍
TCP协议头介绍:
(1)端口号[16bit]
我们知道,网络实现的是不同主机的进程间通信。在一个操作系统中,有很多进程,当数据到来时要提交给哪个进程进行处理呢?这就需要用到端口号。在TCP头中,有源端口号(Source Port)和目标端口号(Destination Port)。源端口号标识了发送主机的进程,目标端口号标识接受方主机的进程。
(2)序号[32bit]
序号分为发送序号(Sequence Number)和确认序号(Acknowledgment Number)。
发送序号:用来标识从 TCP源端向 TCP目的端发送的数据字节流,它表示在这个报文段中的第一个数据字节的顺序号。如果将字节流看作在两个应用程序间的单向流动,则 TCP用顺序号对每个字节进行计数。序号是 32bit的无符号数,序号到达 2
32- 1后又从 0开始。当建立一个新的连接时, SYN标志变 1,顺序号字段包含由这个主机选择的该连接的初始顺序号 ISN( Initial Sequence Number)。
确认序号:包含发送确认的一端所期望收到的下一个顺序号。因此,确认序号应当是上次已成功收到数据字节顺序号加 1。只有 ACK标志为 1时确认序号字段才有效。 TCP为应用层提供全双工服务,这意味数据能在两个方向上独立地进行传输。因此,连接的每一端必须保持每个方向上的传输数据顺序号。
(3)偏移[4bit]
这里的偏移实际指的是TCP首部的长度,它用来表明TCP首部中32 bit字的数目,通过它可以知道一个TCP包它的用户数据是从哪里开始的。这个字段占4bit,如4bit的值是0101,则说明TCP首部长度是5 * 4 = 20字节。 所以TCP的首部长度最大为15 * 4 = 60字节。然而没有可选字段,正常长度为20字节。
(4)Reserved [6bit]
目前没有使用,它的值都为0
(5)标志[6bit]
在TCP首部中有6个标志比特。他们中的多个可同时被置为1 。
URG 紧急指针(urgent pointer)有效
ACK 确认序号有效
PSH 指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满
RST 一般表示断开一个连接
例如:一个TCP的客户端向一个没有监听的端口的服务器端发起连接,wirshark抓包如下

可以看到host:192.168.63.134向host:192.168.63.132发起连接请求,但是host:192.168.63.132并没有处于监听对应端口的服务器端,这时
host : 192.168.63.132发一个RST置位的TCP包断开连接。
SYN 同步序号用来发起一个连接
FIN 发送端完成发送任务(即断开连接)
(6)窗口大小(window)[16bit]
窗口的大小,表示源方法最多能接受的字节数。。
(7)校验和[16bit]
校验和覆盖了整个的TCP报文段:TCP首部和TCP数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。
(8)紧急指针[16bit]
只有当URG标志置为1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。
三次握手:
TCP通信流程框图
SOCKE具体实现
客户端:
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
int main()
{
SOCKET clientsocket;
SOCKADDR_IN serveraddr;
SOCKADDR_IN clientaddr;
char buf[1024];
WSADATA wsa;
WSAStartup(MAKEWORD(2,0),&wsa); //初始化WS2_32.DLL
//创建套接字
if((clientsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) <= 0)
{
printf("套接字socket创建失败!\n");
return -1;
}
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(9102);
serveraddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//请求连接
printf("尝试连接中...\n");
if(connect(clientsocket, (SOCKADDR *)&serveraddr, sizeof(serveraddr)) != 0)
{
printf("连接失败!\n");
return -1;
}
printf("连接成功!\n");
//发送数据
printf("请输入发送给服务器的字符:\n");
scanf("%s", buf);
if(send(clientsocket, buf, strlen(buf)+1, 0)<=0)
{
printf("发送错误!\n");
}
//接收数据
while(1){
if(recv(clientsocket, buf, 1024, 0) <= 0)
{
printf("关闭连接!\n");
closesocket(clientsocket);
}
printf("接收来自服务器的信息: %s\n",buf);
break;
}
//关闭套接字
closesocket(clientsocket);
WSACleanup(); //释放WS2_32.DLL
return 0;
}
服务端:
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
int main()
{
SOCKET serversoc;
SOCKET clientsoc;
SOCKADDR_IN serveraddr;
SOCKADDR_IN clientaddr;
char buf[1024];
int len;
WSADATA wsa;
WSAStartup(MAKEWORD(2,0),&wsa); //初始化WS2_32.DLL
//创建套接字
if((serversoc = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) <= 0)
{
printf("套接字socket创建失败!\n");
return -1;
}
//命名协议,IP,端口
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(9102);
serveraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
//绑定套接字
if(bind(serversoc, (SOCKADDR *)&serveraddr, sizeof(serveraddr)) != 0)
{
printf("套接字绑定失败!\n");
return -1;
}
printf("开始监听...\n");
//监听请求
if(listen(serversoc, 1) != 0)
{
printf("监听失败!\n");
return -1;
}
len = sizeof(SOCKADDR_IN);
//接收请求
if((clientsoc = accept(serversoc, (SOCKADDR *)&clientaddr, &len))<=0)
{
printf("接受连接失败!\n");
return -1;
}
printf("连接成功\n");
//接收数据
while(1)
{
if(recv(clientsoc, buf, 1024, 0) <= 0)
{
printf("关闭连接!\n");
closesocket(clientsoc);
}
printf("接收来自客户端的信息: %s\n",buf);
break;
}
//发送数据
printf("请输入发送给客户端的字符:\n");
scanf("%s", buf);
//send to client
if(send(clientsoc, buf, strlen(buf)+1, 0)<=0)
{
printf("发送错误!\n");
}
WSACleanup(); //释放WS2_32.DLL
return 0;
}
参考文章
http://blog.youkuaiyun.com/lovecodeless/article/details/25061141
http://blog.youkuaiyun.com/mao834099514/article/details/52763490?locationNum=2&fps=1
http://blog.chinaunix.net/uid-26833883-id-3627644.html