言之者无罪,闻之者足以戒。 - “诗序”
1、三次握手:
看一下三次握手的框图:
(1)、服务器必须准备好接受外来连接
(2)、客户端调用connect来主动打开一个连接,此时客户端TCP将会发送一个SYN分节
(3)、服务器必须确认客户的SYN
(4)、客户必须确认服务器的SYN
下面我们看一下wireshark
对照着这张图我们就可以理解上面的框图中,三次握手的概念了。
下面我们来看一下server的程序:
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_LISTEN_QUE 5
int main(int argc,char *argv[])
{
int listenfd,sockfd,opt=1;
struct sockaddr_in server,client;
char buf[200];
socklen_t len;
int timep;
int ret;
//创建套接字
listenfd = socket(AF_INET,SOCK_STREAM,0);
if(listenfd < 0)
{
perror("Create socket fail");
return -1;
}
//设置套结字关联的选项
if((ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) < 0)
{
perror("Error ,set socket reuse addr failed");
return -1;
}
//清空结构体中的变量值
bzero(&server, sizeof(server));
//初始化结构体的变量
server.sin_family = AF_INET;//IPv4
server.sin_port = htons(8888);//主机转换到网络
server.sin_addr.s_addr = htonl(INADDR_ANY);//同上(使用这个宏套接字可以绑定到所有的
端口)
//获取结构体地址长度
len = sizeof(struct sockaddr);
//绑定服务器ip地址和端口到套接字
if(bind(listenfd, (struct sockaddr *)&server, len)<0)
{
perror("bind error.");
return -1;
}
//设置服务器的最大连接数
listen(listenfd, MAX_LISTEN_QUE);
while(1)
{
//等待客户端请求,如果请求到来,返回一个新的socket
//服务器和客户端利用新的socket来通信
sockfd = accept(listenfd, (struct sockaddr *)&client, &len);
if(sockfd < 0)
{
perror("accept error.");
return -1;
}
}
sleep(10);
//显示系统的当前时间
timep = time(NULL);
//将数据按照一定的格式转换之后复制到buf
//ctime返回一个表示当地时间的字符串
snprintf(buf, sizeof(buf), "%s", ctime(&timep));
//向套接字中写入buf存储的时间
write(sockfd, buf, strlen(buf));
//打印存储的字节数
printf("Bytes:%d\n", strlen(buf));
//打印套接字的文件描述符
printf("sockfd=%d\n", sockfd);
printf("IP:0x%x, Port:%d\n",ntohl(client.sin_addr.s_addr),ntohs(client.sin_port));
sleep(10);
//关闭套接字
//close(sockfd);
return 0;
}
在运行程序进行抓包的时候,sever函数我们是在:Linux系统下先运行,在通过windows系统下的命令提示符中连接sever。
备注:在windows系统下的搜索中输入:cmd就可以看到命令提示符程序
打开之后输入:telnet 192.168.177.133 8888就可以连接sever了(192.168.177.133是我的Linux的服务器地址,8888是我设置的 端口号)。
帮助:如果你输入上述命令之后提示:telnet不是内部或外部命令也不是可运行的程序或批处理,解决办法可以参考下面的文章
https://blog.csdn.net/haijing1995/article/details/66475546
注意:在启动客户端和服务器端的时候,要先启动服务器端之后再启动客户端,然后进行抓包。使用windows下的命令提示符一旦启动程序是不会自己退出的。需要我们强制退出
2、四次挥手:
看一下四次挥手的框图:
这里我们还是先用上面所用到的命令提示符来用wireshark来抓包:
对照着这张图我们就可以理解上面的框图中,四次挥手的概念了。
下面我们在Linux中重复操作一下上面的过程:
先贴出server的程序:
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_LISTEN_QUE 5
int main(int argc,char *argv[])
{
int listenfd,sockfd,opt=1;
struct sockaddr_in server,client;
char buf[200],read_buf[100];
int bytes=0;
socklen_t len;
int timep;
int ret;
//创建套接字
listenfd = socket(AF_INET,SOCK_STREAM,0);
if(listenfd < 0)
{
perror("Create socket fail");
return -1;
}
//设置套结字关联的选项
if((ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) < 0)
{
perror("Error ,set socket reuse addr failed");
return -1;
}
//清空结构体中的变量值
bzero(&server, sizeof(server));
//初始化结构体的变量
server.sin_family = AF_INET;//IPv4
server.sin_port = htons(8888);//主机转换到网络
server.sin_addr.s_addr = htonl(INADDR_ANY);//同上(使用这个宏套接字可以绑定到所有的
端口)
//获取结构体地址长度
len = sizeof(struct sockaddr);
//绑定服务器ip地址和端口到套接字
if(bind(listenfd, (struct sockaddr *)&server, len)<0)
{
perror("bind error.");
return -1;
}
//设置服务器的最大连接数
listen(listenfd, MAX_LISTEN_QUE);
while(1)
{
//等待客户端请求,如果请求到来,返回一个新的socket
//服务器和客户端利用新的socket来通信
sockfd = accept(listenfd, (struct sockaddr *)&client, &len);
if(sockfd < 0)
{
perror("accept error.");
return -1;
}
//显示系统的当前时间
timep = time(NULL);
//将数据按照一定的格式转换之后复制到buf
//ctime返回一个表示当地时间的字符串
snprintf(buf, sizeof(buf), "%s", ctime(&timep));
//向套接字中写入buf存储的时间
write(sockfd, buf, strlen(buf));
//打印存储的字节数
printf("Bytes:%d\n", strlen(buf));
//打印套接字的文件描述符
printf("sockfd=%d\n", sockfd);
printf("IP:0x%x, Port:%d\n",ntohl(client.sin_addr.s_addr),ntohs(client.sin_port));
bytes = read(sockfd,read_buf,100);
if(bytes < 0)
{
printf("read err\n");
return -1;
}
if(bytes == 0)
{
printf("client connection closed\n");
return 0;
}
// sleep(10);
//关闭套接字
close(sockfd);
}
return 0;
}
接下来看一下client的程序:
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
char buf[100];//存储读取的内容
int bytes;//存储读取的字节数
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)//创建套接字
{
printf("socket error\n");
return -1;
}
//结构体中成员变量初始化为0
bzero(&servaddr,sizeof(servaddr));
//初始化成员变量
//初始化成员变量
servaddr.sin_family = AF_INET;//IPv4
servaddr.sin_addr.s_addr = inet_addr("192.168.177.133");//转换为地址格式
servaddr.sin_port = htons(8888);//主机序转换到网络序
if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)//绑定服务器
ip和端口到套结字
{
perror("connect error");
return -2;
}
//存储读取的数据和读取的字节数
bytes == read(sockfd,buf,100);
if(bytes < 0)
{
printf("Error ,read failed\n");
return -3;
}
//如果读取的字节数为0 ,就说明连接已经关闭了
if(0 == bytes)
{
printf("Server close connection\n");
return -4;
}
//打印读取的字节数和读取的内容
printf("read bytes %d\n",bytes);
printf("Time: %s\n",buf);
//关闭套结字
close(sockfd);
return 0;
}
之后看一下程序的运行结果: