网络编程实例代码 : 手写
客户端:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("192.168.31.96");
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
char buff[128] = {0};
printf("input : \n");
fgets(buff,128,stdin);
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("buff=%s\n",buff);
close(sockfd);
}
服务器端:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr( "127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
listen(sockfd,5);//5是监听队列的长度,经验值.
while(1)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);//c:链接套接字
if( c < 0 )
{
continue;
}
char buff[128] = {0};
int n = recv(c,buff,127,0);
printf("n = %d ,buff = %s\n",n,buff);
send(c,"ok",2,0);
close(c);
}
exit(0);
}
三次握手可以改为两次吗,可以改为4次吗?哪个阶段可能受到攻击?
TCP面向连接的,所以在通信前,先用“三次握手”来建立连接 。如果connect 返回成功,则三次握手完成。要思考能不能改为两次,首先要知道每次的握手都干了什么?
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;[SYN:同步序列编号(Synchronize Sequence Numbers)]
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
如果采取两次握手,相当于第二次握手结束便建立连接,如果发送SYN的一方不想连接了,也不会有反馈,另一方却一直在等待,浪费了时间,这样就会产生僵尸连接,浪费了网络资源。当然也可以采取4次甚至N次握手,但是没有必要,建立连接的时间太长,双方通信效果也会大打折扣。所以3次只是折中方案,保证了可靠性,又节俭了建立连接的时间。所以,三次握手两次不行。
可能受到的攻击:SYN溢出攻击 / SYN泛洪
在三次握手的时候,容易受到的攻击是syn溢出攻击,也叫syn泛洪,攻击者通过发送TCP 的SYN,而当服务器返回ACK后,该攻击者就不对其进行再确认,那这个TCP连接就处于挂起状态,也就是所谓的半连接状态,服务器收不到再确认的话,还会重复发送ACK给攻击者。这样更加会浪费服务器的资源。攻击者就对服务器发送非常大量的这种TCP连接,由于每一个都没法完成三次握手,所以在服务器上,这些TCP连接会因为挂起状态而消耗CPU和内存,最后服务器可能死机,就无法为正常用户提供服务了
四次挥手可以改为三次吗?
因为断开连接可以是任意一方,所以用A、B来代表通信双方。
(1)A的应用进程先向其TCP发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1(终止等待1)状态,等待B的确认。
(2)B收到连接释放报文段后即发出确认报文段,(ACK=1,确认号ack=u+1,序号seq=v),B进入CLOSE-WAIT(关闭等待)状态,此时的TCP处于半关闭状态,A到B的连接释放。
(3)A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。
(4)B没有要向A发出的数据,B发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),B进入LAST-ACK(最后确认)状态,等待A的确认。
(5)A收到B的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL(最长报文段寿命Maximum Segment Lifetime)后,A才进入CLOSED状态
如果B发出的确认报文段和释放报文段同时发出,就可以看作是三次。
断开连接时,可能受到什么攻击?
FIN 重复发送
time_wait 状态存在的意义
可靠的终止tcp连接。
让迟来的报文被识别,并丢弃
在连接出错如何处理
回复RST报文(复位报文) 序号发送 ,确认号+1
对于异常事件,采用带MSG_OOB标志的recv函数读取带外数据,表示紧急指针,将数据放到最前面进行读取
画出IP的报头和TCP的报头
IPV4报头(图源《Linux高性能服务器编程》2.2)
TCP报头(图源《Linux高性能服务器编程》。3.2)
如何防止connect长久阻塞:
(1)套接字设置超时时间
(2)将套接字设置成非阻塞的模式 fcntl
如何解决粘包问题:
TCP的可靠机制:应答确认,超时重传 ,通过滑动窗口进行流量控制 ,TCP是一个流式服务,没有一个明确的起始和结尾,多次发送的数据可以被对方一次接收,就会产生粘包问题。
1、 send /recv /send : 将每次的数据分开发送,只有收到对方发送的确认信息,才能继续发送。
2、为数据设置起始和结束的标记。比如可以使用’#’作为数据包的结束。