三次握手:
第一次握手:客户端向服务器发送SYN待确认数据x, 客户端进入SYN_SEND状态
第二次握手:服务器向客户端回传一条ACK应答数据x+1, 同时发送一条SYN待确认数据y,服务器进入SYN_RECV状态
第三次握手:客户端向服务器回传一条ACK应答数据y+1,客户端和服务器同时进入ESTABLISHED状态
四次挥手:
第一次挥手:主动方向被动方发送FIN断开连接请求(FIN x)
第二次挥手:被动方向主动方回传一条ACK应答(ACK x+1)
第三次挥手:被动方向主动方发送FIN断开连接请求(FIN y)
第四次挥手:主动方向被动方回传一条ACK应答(ACK y+1)
注意:当服务器作为主动方断开连接时,在第四次挥手过程中会有一个等待过程(TIME_WAIT),等待时间因操作系统不定
netstat -p | grep “6666”
二、服务器模型
1、循环服务器
int fd = socket();
bind();
listen();
while(1)
{
int connfd = accept();
while(1)
{
//发送或者接收
}
close(connfd);
}
close(sockfd);
2、并发服务器
(1)多线程并发服务器
注意:如果在多线程并发服务器中出现公共数据访问时,需要使用同步或者互斥进行资源保护
pthread_t thread;
int fd = socket();
bind();
listen();
while(1)
{
int connfd = accept();
pthread_create(&thread, NULL, thread_handler, (void *)connfd);
pthread_detach(thread); //将线程设置为分离属性,属于非阻塞操作,当线程结束后,资源自动回收
}
close(sockfd);
void *thread_handler(void *arg)
{
int connfd = (int *)arg;
while(1)
{
//发送或者接收 connfd
send(connfd, buf, 64, 0);
}
close(connfd);
}
(2)多进程并发服务器
注意:使用多进程并发服务器需要注意子进程资源回收的问题
17对应的信号是 SIGCHLD,SIGCHLD 信号是在子进程状态发生改变(如子进程终止、暂停或恢复)时,由操作系统发送给父进程的信号
pid_t pid;
int fd = socket();
bind();
listen();
signal(17, signal_handler);
signal 函数会告知操作系统,当进程接收到 SIGCHLD 信号时,
要调用 signal_handler 函数进行处理
//当进程接收到 SIGCHLD 信号(信号编号为 17)时
,会调用 signal_handler 函数来处理该信号
while(1)
{
int connfd = accept(); //父进程执行
pid = fork()
if(pid == 0) //子进程执行
{
//close(sockfd);
while(1)
{
//发送或者接收
}
close(connfd);
}
}
close(sockfd);
void signal_handler(int num) //信号处理函数 num表示捕获信号的编号
{
wait(NULL);
}
当父进程接收到 SIGCHLD 信号时,会调用 signal_handler 函数,该函数使用 wait(NULL) 来回收子进程的资源,避免子进程成为僵尸进程
三、基于套接字的UDP通信
特点:无连接、是一种不安全可靠,不能保证数据传输准确无误的一种通信方式
通信流程:
客户端:
socket() --> sendto()/recvfrom() --> close()
服务器:
socket() --> bind() --> sendto()/recvfrom() --> close()
1、发送/接收函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
sockfd:数据报套接字
buf:发送内容的首地址
len:请求发送的字节数
flags: 0
dest_addr: 接收方地址结构的首地址
addrlen:接收方地址结构的长度
返回值:
返回成功发送的字节数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数:
sockfd:数据报套接字
buf:存放接收内容的首地址
len:请求接收的字节数
flags: 0
src_addr: 发送方地址结构的首地址
addrlen:发送方地址结构长度的地址
返回值:
返回成功接收的字节数
发送数据实例:
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888);
saddr.sin_addr.s_addr = inet_addr("192.168.6.48");
int s_len = sizeof(saddr);
sendto(sockfd, "hello", 5, 0, (strcut sockaddr *)&saddr, s_len);