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小写的单词表示序号。
- 字段含义
URG 紧急指针是否有效。为1,表示某一位需要被优先处理
ACK 确认号是否有效,一般置为1。
PSH 提示接收端应用程序立即从TCP缓冲区把数据读走。
RST 对方要求重新建立连接,复位。
SYN 请求建立连接,并在其序列号的字段进行序列号的初始值设定。建立连接,设置为1
FIN 希望断开连接。
三次握手过程理解
第一次握手:建立连接时,客户端发送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
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#include
#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;
}
代码分析
TCP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置要连接的对方的IP地址和端口等属性;
5、连接服务器,用函数connect();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;