声明:谢绝一切形式的转载
socket套接字
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现, socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭). 说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
TCP/IP
TCP三次握手
tcp的三次握手如下所示。 有很多面试官很特别容易问为什么两次不行?个人感觉,两次也不是不行。只是在有些情况下会产生无效的连接。 比如客户端发起连接,由于网络阻塞,服务器一直没有收到连接请求,客户端没有收到任何回应,启动重传机制,又发起了一次连接,这次很顺利,然后服务端正常返回信息,第二次的连接成功了。可是,万万没想到,那个阻塞的连接经过跋山涉水终于到了服务器端,此时服务器作何处理那? 服务器端认为你咋又发了个请求呀,于是再次进行确认,客户端收到一条服务器端的一个确认,不予理会。可服务器却坚持认为连接已经成功,等待客户端传输数据,于是服务器的许多资源就浪费了。
抓包分析
个人认为,涉及到tcp/ip原理讲解的,空讲理论略显不足,甚至有点"耍流氓"的感觉。"三次握手,四次挥手"这种理论书本中讲述的很清楚了,下面以WireShark工具见证一下这个理论。
三次握手
通过上面可以看出三次握手的流程
客户端向服务器端发送[SYN],seq=0服务端向客户端返回[SYN,ACK] Ack=1 seq=0客户端向服务器端返回[ACK] Ack=1
四次挥手
tcp状态变迁图
基本的socket函数
使用socket编程用到的函数和基本流程如下: 注意:还有两个函数send和recieve。这两个函数比较容易望文生义,认为是send把数据发送出去。其实不是这样子的,只有当send把socket的发送缓冲区填满的时候数据才进行发送,通过TCP/IP协议进行发送。进行数据传输的是协议。
简单聊天系统
前面的理论知识还是很有必要了解的。之后开发程序就简单多了。先看以下效果:
服务器端代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define PORT 6666
#define BACKLOG 10
#define MAXDATASIZE 2048
int main(int argc
, char *argv
[])
{
int listenfd
;
listenfd
= socket(AF_INET
, SOCK_STREAM
, 0);
struct sockaddr_in serveraddr
;
bzero(&serveraddr
, sizeof(serveraddr
));
serveraddr
.sin_family
= AF_INET
;
serveraddr
.sin_port
= htons(PORT
);
serveraddr
.sin_addr
.s_addr
= INADDR_ANY
;
bind(listenfd
, (struct sockaddr
*)&serveraddr
, sizeof(serveraddr
));
listen(listenfd
, BACKLOG
);
printf("======端口绑定成功,等待客户端的连接======\n");
struct sockaddr_in peeraddr
;
socklen_t peer_len
= sizeof(peeraddr
);
int connfd
;
while(1)
{
connfd
= accept(listenfd
, (struct sockaddr
*)&peeraddr
, &peer_len
);
printf("\n=====================客户端链接成功=====================\n");
printf("IP = %s:PORT = %d\n", inet_ntoa(peeraddr
.sin_addr
), ntohs(peeraddr
.sin_port
));
char buf
[MAXDATASIZE
];
while(1)
{
memset(buf
, '\0', MAXDATASIZE
/sizeof (char));
int recv_length
= recv(connfd
, buf
, MAXDATASIZE
/sizeof (char), 0);
if(recv_length
== 0)
{
printf("客户端已经关闭!\n");
break;
}
char vul_buffer
[64] ;
strcpy(vul_buffer
,buf
);
printf("客户端说: ");
fputs(vul_buffer
, stdout);
memset(buf
, '\0', MAXDATASIZE
/sizeof (char));
printf("请输入: ");
fgets(buf
, sizeof(buf
), stdin);
send(connfd
, buf
, recv_length
, 0);
}
close(connfd
);
close(listenfd
);
return 0;
}
}
这里使用了strcpy这个危险函数,后续文章会针对这个漏洞进行攻击。
客户端代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#define PORT 6666
#define MAXDATASIZE 2048
int main(int argc
, char *argv
[])
{
if(argc
!= 2)
{
fprintf(stderr, "请您输入ip地址!\n");
exit(1);
}
int sockfd
;
sockfd
= socket(AF_INET
, SOCK_STREAM
, 0);
const char *server_ip
= argv
[1];
struct sockaddr_in serveraddr
;
bzero(&serveraddr
, sizeof(serveraddr
));
serveraddr
.sin_family
= AF_INET
;
serveraddr
.sin_port
= htons(PORT
);
inet_pton(AF_INET
, server_ip
, &serveraddr
.sin_addr
);
connect(sockfd
, (struct sockaddr
*)&serveraddr
, sizeof(serveraddr
));
printf("=====================服务器链接成功=====================\n");
char buf
[MAXDATASIZE
];
memset(buf
, 0 , sizeof(buf
));
printf("请输入: ");
while(fgets(buf
, sizeof(buf
), stdin) != NULL && (strncmp(buf
, "quit", 4)))
{
send(sockfd
, buf
, sizeof(buf
), 0);
memset(buf
, 0, sizeof(buf
));
recv(sockfd
, buf
, sizeof(buf
), 0);
printf("服务器说: ");
fputs(buf
, stdout);
memset(buf
, 0, sizeof(buf
));
printf("请输入: ");
}
printf("客户端将要被关闭,下次再见\n");
close(sockfd
);
return 0;
}
使用如下命令进行编译
gcc -o client client.c
&& gcc -o server server.c
注意:在判断是否是"quit"的时候,应该使用strncmp而不是strcmp。
公众号
了解更多网络安全内容,欢迎关注我的公众号:无情剑客