深入理解TCP协议及其源代码

TCP/IP数据包结构

序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。

确认号ack:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。

确认ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效

同步SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。

终止FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接

PS:ACK、SYN和FIN这些大写的单词表示标志位,其值要么是1,要么是0;ack、seq小写的单词表示序号。

  1. 字段含义
    URG 紧急指针是否有效。为1,表示某一位需要被优先处理
    ACK 确认号是否有效,一般置为1。
    PSH 提示接收端应用程序立即从TCP缓冲区把数据读走。
    RST 对方要求重新建立连接,复位。
    SYN 请求建立连接,并在其序列号的字段进行序列号的初始值设定。建立连接,设置为1
    FIN 希望断开连接。
    三次握手过程理解
    ![在这里插入图片描述]( https://img-blog.csdnimg.cn/20191226214654202.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwODAzMzk4,size_16,color_FFFFFF,t_70)

第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

client端代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define N 64

int main( int argc, char *argv[] )
{
	int sockfd;
	struct sockaddr_in servaddr, myaddr;
	char buf[N] = {0};

	if (argc < 3)
	{
		printf("usage : %s ip port\n", argv[0]);
		return 0;
	}

	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		perror("socket");
		exit(-1);
	}


#if 0
	memset(&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;
	myaddr.sin_port = htons(8000);
	myaddr.sin_addr.s_addr = inet_addr(argv[1]);

	if(bind(sockfd, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1)
	{
		error("bind");
		exit(-1);
	}

#endif

	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(atoi(argv[2]));
	servaddr.sin_addr.s_addr = inet_addr(argv[1]);

	if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
	{
		perror("socket");
		exit(-1);
	}


	printf(">");
	while(fgets(buf, N, stdin) != NULL)
	{
		send(sockfd, buf, strlen(buf), 0);
		memset(buf, 0, sizeof(buf));
		recv(sockfd, buf, N, 0);
		printf("%s\n", buf);

		printf(">");
	}

	close(sockfd);

	return 0;
}

server端代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define N 64

int main( int argc, char *argv[] )
{
	int listenfd, connfd;
	struct sockaddr_in myaddr, peeraddr;
	socklen_t len;
	char buf[N] = {0};
	ssize_t n;

	if(argc < 3)
	{
		printf("Usage: %s ip port\n", argv[0]);
		return 0;
	}

	if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		perror("socket");
		exit(-1);
	}

	memset(&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;
	myaddr.sin_port = htons(atoi(argv[2]));
	myaddr.sin_addr.s_addr = inet_addr(argv[1]);

	if(bind(listenfd, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1)
	{
		perror("bind");
		exit(-1);
	}

	if(-1 == listen(listenfd, 5))
	{
		perror("listen");
		exit(-1);
	}


	memset(&peeraddr, 0, sizeof(peeraddr));
	len = sizeof(peeraddr);

	while(1)
	{
		if((connfd = accept(listenfd, (struct sockaddr *)&peeraddr, &len)) == -1)
		{
			perror("accept");
			exit(-1);
		}

		printf("from %s:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

		while(1)
		{
			memset(buf, 0, sizeof(buf));
			n = recv(connfd, buf, N, 0);
			if( n == 0 )
			{
				break;
			}

			buf[n] = '\0';
			printf("n=%d %s", n, buf);

			send(connfd, buf, n, 0);
		}
		close(connfd);
	}

	return 0;
}

运行结果:
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20191226215533515.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwODAzMzk4,size_16,color_FFFFFF,t_70)

代码分析

 TCP编程的客户端一般步骤是:
  1、创建一个socket,用函数socket();
  2、设置socket属性,用函数setsockopt();* 可选
  3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
  4、设置要连接的对方的IP地址和端口等属性;
  5、连接服务器,用函数connect();
  6、收发数据,用函数send()和recv(),或者read()和write();
  7、关闭网络连接;

posted @ 2019-12-26 22:01  凌特逸轩  阅读(245)  评论(0)    收藏  举报