三次握手与四次挥手大家都比较清楚,下面是TCP状态转移图
配和下面的图更加容易理解各个状态的变化:
socket的创建,它就是可读、可写、可控制、可关闭的文件描述符。
int socket(int domain,int type,int protocol);
domain参数告诉系统使用哪个底层协议族,type参数指定服务类型,protocol参数是在前两个参数构成的协议集合下,再选择一个具体的协议。
socket系统调用成功时返回一个
socket
文件描述符。
socket的命名,也就是socket地址的绑定,使用bind函数:
int bind(int sockfd,const struct sockaddr*my_addr,socklen_t addrlen);
bind
将
my_addr
所指的
socket
地址分配给未命名的
sockfd
文件描述 符,addrlen
参数指出该
socket地址的长度。在服务器程序中,我们通常要命名socket,因为只有命名后客户端才能知道该如何连接它。客户端则通常不需要命名socket,而是采用匿名方式,即使用操作系统自动分配的socket地址。
顺带提一下sockaddr,它是socket地址的结构体,下面是IPv4的专用结构体
struct sockaddr_in
{
sa_family_t sin_family;/*地址族:
AF_INET*/
u_int16_t sin_port;/*端口号,要用网络字节序表示
*/
struct in_addr sin_addr;/*IPv4地址结构体
*/也就是地址
};
所有专用
socket
地址
类型的变量在实际使用时都需要转化为通用socket
地址类型
sockaddr
(强制转换即可)。
首先,客户端必须进入监听状态,也就是调用listen函数
int listen(int sockfd,int backlog);
sockfd
参数指定被监听的
socket
。
backlog参数提示内核监听队列的最大长度。好了我们监听完成,进入SYN_RCVD状态,等待链接请求。
int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen);
等待连接函数,sockfd
参数是执行过
listen
系统调用的监听socket,addr参数用来获取被接受连接的远端socket地址,该socket地址的长度由addrlen参数指出。accept只是从监听队列中取出连接,而不论连接处于何种状态。被取出后与客户端建立连接,服务端的该套接字进入
ESTABLISHED状态。
int connect(int sockfd,const struct sockaddr*serv_addr,socklen_t addrlen);
客户端主动通过connect函数与服务端建立连接,sockfd参数由socket系统调用返回一个socket。serv_addr参数是服务器监听的socket地址,addrlen参数则指定这个地址的长度。连接成功则进入ESTABLISHED状态。
三次握手完成,客户端程序主动connect进入SYN_SEND状态发送请求,服务端开启监听进入SYN_RECV状态,这两个状态都十分短暂。客户端connect成功后进入连接建立状态,服务端也在收到响应报文后进入连接建立状态。
下面就是四次挥手了,对于socket直接close就是四次挥手,不过,close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1。只有当fd的引用计数为0时,才真正关闭连接。你想马上关闭就用shutdown函数
int shutdown(int sockfd,int howto);
sockfd
参数是待关闭的
socket,
howto
参数决定了
shutdown的行为。shutdown能够分别关闭socket上的读(SHUT_RD)或写(SHUT_WR),或者都关闭(SHUT_RDWR)。而close在关闭连接时只能将socket上的读和写同时关闭。
都说到这了,再说一下这四次挥手吧。客户端主动关闭发出FIN报文进入FIN_WAIT1状态,服务端收到FIN后发出响应报文进入CLOSE_WAIT状态,再向客户端发出FIN报文进入LAST_WAIT。客户端收到第一个响应报文进入FIN_WAIT2状态,再收到服务端的分手消息并发出响应进入TIME_WAIT状态,2个最大传播消息服务端无消息就CLOSE,服务端收到最后的响应也CLOSE。
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#include<iostream>
#include<Ws2Tcpip.h>
using namespace std;
int main() {
//版本使用
WORD wVersion = MAKEWORD(2, 1);
//打开网络ku
WSADATA wSockMsg;
WSAStartup(wVersion, &wSockMsg);
//创建socket
SOCKET socketserver = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//ipv4专用地址
sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(8888);
inet_pton(AF_INET, "127.0.0.1", (void*)&si.sin_addr.S_un.S_addr);
bind(socketserver, (struct sockaddr*)&si, sizeof(si));
listen(socketserver, SOMAXCONN);
sockaddr_in clientMsg;
int nlen = sizeof(clientMsg);
SOCKET socketClient = accept(socketserver, (sockaddr*)&clientMsg, &nlen);
closesocket(socketClient);
closesocket(socketserver);
//清理网络库
WSACleanup();
return 0;
}