tcp协议:
TCP 协议提供的是:面向连接、可靠的、字节流服务。使用 TCP 协议通信的双发必须
先建立连接,然后才能开始数据的读写。双方都必须为该连接分配必要的内核资源,以管理
连接的状态和连接上数据的传输。TCP 连接是全双工的,双方的数据可以通过一个连接进行
读写。完成数据交换之后,通信双方都必须断开连接以释放系统资源。
特点
面向连接 可靠 流试服务 会产生粘包
可靠性:应答确认,超时重传 乱序重排 去重 滑动窗口 流量控制
tcp编程流程:
1.socket()方法是用来创建一个套接字,有了套接字就可以通过网络进行数据的收发。
2.bind()方法是用来指定套接字使用的 IP 地址和端口。
3.listen()方法是用来创建监听队列。
4.accept()处理存放在 listen 创建的已完成三次握手的队列中的连接。
5.send()方法用来向 TCP 连接的对端发送数据。
6.recv()方法用来接收 TCP 连接的对端发送来的数据。
7.close()方法用来关闭 TCP 连接。此时,会进行四次挥手。
8.connect()方法一般由客户端程序执行,需要指定连接的服务器端的 IP 地址和端口。
三次握手四次挥手:
connect()建立连接后进行三次握手,close()进行四次挥手。
三次握手:connect成功则证明三次握手成功。
第一次握手:客户端向服务器端发送连接请求。
第二次握手:服务器向客户端发生应发响应。
第三次握手:当客户端收到连接同意的应答后,还要向服务端发送一个确认报文段,表示:服务端发来的连接同意应答已经成功收到。
三次握手可看做告白的过程。第一次握手是男孩说:“我们在一起吧”,然后女孩听到后开始第二次握手说:“好呀”。这个时候问题来了,男孩可能没有听到女孩的同意,此时开始第三次握手男孩说:“我们终于在一起了”来表示听到女孩的同意。
为什么要进行三次握手?
客户端先进行连接请求,服务器端收到请求回复应答,当客户端接收到应答响应时,就会进入estabished状态,而服务器端只有收到客户端的连接请求后才会进入estabished状态。
此时如果网络拥塞,客户端发送的连接请求迟迟到不了服务端,客户端便超时重发请求,如果服务端正确接收并确认应答,双方便开始通信,通信结束后释放连接。此时,如果那个失效的连接请求抵达了服务端,由于只有两次握手,服务端收到请求就会进入ESTABLISHED状态,等待发送数据或主动发送数据。但此时的客户端早已进入CLOSED状态,服务端将会一直等待下去,这样浪费服务端连接资源。
在创建连接时也易遭到黑客恶意攻击,会一直发送连接请求,发送FIN,但不去管能否成功建立连接,会一直占用listen()的缓存区,使之无法成功建立链接。
四次挥手:第一条是通知关闭,第二条是回复确定。
四次挥手能不能能变成三次?
可以,当第一次close时发送FIN时,同时进行第二次close,可以把第一次close需要回复的ACK和第二次close发送的FIN一起发送。把四次挥手变为三次。
listen():两个,一个放置未完成握手的,一个放置完成握手的,
accept:处理完成握手的(c=accept)
线程实现多客户端链接
服务器端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 #include<assert.h>
6 #include<sys/socket.h>
7 #include<netinet/in.h>
8 #include<arpa/inet.h>
9 #include<pthread.h>
10
11 void* recv_thread(void* argv)
12 {
13 int c=(int)argv;
14 while(1)
15 {
16 char buff[128]={0};
17 int n=recv(c,buff,127,0);
18 if(n<=0)
19 {
20 break;
21 }
22 printf("recv(%d):%s\n",c,buff);
23 send(c,"ok",2,0);
24 }
25 printf("client close\n");
26 }
27
28 int main()
29 {
30 int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字,AF-INET是宏地址,SOCK-stream表示为tcp协议。
31 assert(sockfd!=-1);
32
33 struct sockaddr_in saddr, caddr;//ipv4的套接字地址结构,服务器端,客户端。
34 memset(&saddr,0,sizeof(saddr));//初始化
```35 saddr.sin_family=AF_INET;
36 saddr.sin_port=htons(6000);//端口号:主机服务器短整形:6000
37 saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//ip地址
38
39 int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定
40 assert(res!=-1);
41
42 res=listen(sockfd,5);
43 assert(res!=-1);
44
45 while(1)
46 {
47 int len =sizeof(caddr);
48 int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
49 if(c<0)
50 {
51 continue;
52 }
53
54 printf("accept c=%d\n",c);
55 pthread_t id;
56 pthread_create(&id,NULL,recv_thread,(void*)c);//创建线程
57
58
59 }
60 close(sockfd);
61 exit(0);
62 }
客户端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 #include<assert.h>
6 #include<sys/socket.h>
7 #include<netinet/in.h>
8 #include<arpa/inet.h>
9 int main()
10 {
11 int sockfd=socket(AF_INET,SOCK_STREAM,0);
12 assert(sockfd!=-1);
13
14 struct sockaddr_in saddr;
15 saddr.sin_family=AF_INET;
16 saddr.sin_port=htons(6000);
17 saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
18
19 int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
20 assert(res!=-1);
21 while(1)
22 {
23 char buff[128]={0};
24 printf("input:\n");
25 fgets(buff,128,stdin);
26 if(strncmp(buff,"end",3)==0)
27 {
28 break;
29 }
30 send(sockfd,buff,strlen(buff),0);
31 memset(buff,0,128);
32 recv(sockfd,buff,127,0);
33 printf("buff=%s\n",buff);
34 }
35 close(sockfd);
36
37 exit(0);
38 }
执行结果:
进程实现多客户端链接(fork)
服务器端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 #include<assert.h>
6 #include<sys/socket.h>
7 #include<netinet/in.h>
8 #include<arpa/inet.h>
9 #include<pthread.h>
10 #include<signal.h>
11 #include<sys/wait.h>
12
13 void fun(int sig)
14 {
15 wait(NULL);
16 }
17 int main()
18 {
19 signal(SIGCHLD,fun);//处理僵死进程
20 int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字,AF-INET是宏地址,SOCK-stream表示为tcp协议。
21 assert(sockfd!=-1);
22
23 struct sockaddr_in saddr, caddr;//ipv4的套接字地址结构,服务器端,客户端。
24 memset(&saddr,0,sizeof(saddr));
25 saddr.sin_family=AF_INET;
26 saddr.sin_port=htons(6000);//端口号:主机服务器短整形:6000
27 saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//ip地址
28
29 int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定
30 assert(res!=-1);
31
32 res=listen(sockfd,5);
33 assert(res!=-1);
31
32 res=listen(sockfd,5);
33 assert(res!=-1);
34
35 while(1)
36 {
37 int len =sizeof(caddr);
38 int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
39 if(c<0)
40 {
41 continue;
42 }
43
44 printf("accept c=%d\n",c);
45
46 pid_t pid=fork();//创建子进程
47 if(pid==-1)
48 {
49 printf("fork child err\n");
50 close(c);
51 continue;
52 }
53 if(pid==0)
54 {
55 while(1)
56 {
57 char buff[128]={0};
58 int n=recv(c,buff,127,0);
59 if(n<=0)
60 {
61 break;
62 }
63 printf("child read:%s\n",buff);
64 send(c,"ok",2,0);
65 }
66 printf("client close\n");
67 close(c);//关闭子进程
68
69 exit(0);
70 }
71 close(c);//当产生子进程时,父进程可以关闭,去进行下一个循环
72 }
73 }