Linux系统提供的经典进程间通讯机制(IPC):管道,共享内存,消息队列以及信号量,这些机制允许在同一台计算机上运行的进程可以互相通信,而对于不同计算机(通过网络相连)上的进程间通讯则引入了新的机制:网络进程间通信,进程可以通过套接字网络进程间通信接口互相通信,对于套接字接口可以采用许多不同的网络协议进行通信。
1、五层网络协议栈:
应用层协议:FTP、HTTP、SMTP
传输层协议:TCP协议、UDP协议
网络层协议:IP协议
本章讲述的TCP协议和UDP协议就属于传输层。关于五层结构具体功能参考:https://blog.csdn.net/weixin_37719279/article/details/82846226
2、TCP和UDP协议的区别
TCP向上层提供面向连接的可靠服务,UDP向上层提供无连接的不可靠服务,虽然UDP并没有TCP传输来的准确,但是在实时性要求高的场景下经常被用到。
3、TCP的三次握手和四次挥手
(1)三次握手
第一次握手:建立连接时,客户端发送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连接成功)状态,完成三次握手。
(2)四次挥手
a.客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
b.服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
c.客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
d.服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
e.客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
f.服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
详细参考:https://www.cnblogs.com/bj-mr-li/p/11106390.html
1、套接字描述符
(1)创建套接字:
#include
int socket(int domain, int type,int protocol);
domain:确定通信的特性,包括地址格式
AF_INET:IPv4因特网域
AF_INET6:IPv6因特网域
AF_UNIX:UNIX域
AF_UPSPEC:未指定
type:确定套接字类型
SOCK_DGRAM:固定长度的,无连接的,不可靠的报文传递(UDP)
SOCK_RAW:IP协议的数据报接口
SOCK_SEQPACKET:固定长度的,有序的,可靠的,面向连接的报文传递
SOCK_STREAM:有序的,可靠的,双向的,面向连接的字节流(TCP)
protocol:通常为0,表示为给定的域和套接字类型选择默认协议
(2)关闭套接字:
#include
int shutdown(int sockfd, int how);
how:SHUT_RD(关闭读端),无法从套接字读取数据
SHUT_WR(关闭写端),无法从套接字发送数据
SHUT_RDWR,无法读取和发送数据
2、寻址
(1)字节序
应用程序有时需要在处理器字节序与网络字节序之间转换。对于应用程序,有4个用来在处理器字节序和网络字节序之间转换的函数。
“h”表示“主机”字节序,“n”表示“网络”字节序,“l”表示“长”(即4字节)整数,“s”表示“短”(即2字节)整数
#include
uint32_t htonl(uint32_t hostint32); 返回值:以网络字节序表示的32位整数
uint16_t htons(uint16_t hostint16); 返回值:以网络字节序表示的16位整数
uint32_t ntohl(uint32_t netint32); 返回值:以主机字节序表示的32位整数
uint16_t ntohs(uint16_t netint16); 返回值:以主机字节序表示的16位整数
(2)地址格式
一个地址标识一个特定通信域的套接字端点,地址格式与这个特定的通信域相关,为使不同格式地址能够传入到套接字函数,地址会被强制转换成一个通用的地址结构:
struct sockaddr{
unisgned short as_family;
char sa_data[14];
};
由于系统兼容性问题,用新的地址结构表示:
struct sockaddr_in{
unsigned short sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
(3)inet_aton()
inet_aton是一个改进的方法来将一个字符串IP地址转换为一个32位的网络序列IP地址。
1 输入参数string包含ASCII表示的IP地址。
2 输出参数addr是将要用新的IP地址更新的结构。
如果这个函数成功,函数的返回值非零。如果输入地址不正确则会返回零。使用这个函数并没有错误码存放在errno中,所以他的值会被忽略。
(4)示例
struct sockaddr_in socket_server_addr;
socket_server_addr.sin_family = AF_INET;
/*主机字节序转换为网络字节序*/
socket_server_addr.sin_port = htons(SERVER_PORT);
if (inet_aton(“192.168.1.100”, &socket_server_addr.sin_addr) == 0)
{
printf("invalid server ip\n");
return -1;
}
3,服务器关联套接字与地址
int bind(int sockfd, struct sockaddr *addr, int addrlen);
示例:
struct sockaddr_in socket_server_addr;
duty_socket = socket(AF_INET, SOCK_STREAM, 0);
/* 服务器端填充 sockaddr_in结构 */
socket_server_addr.sin_family = AF_INET;
/*端口号转换为网络字节序*/
socket_server_addr.sin_port = htons(SERVER_PORT);
/*接收本机所有网口的数据*/
socket_server_addr.sin_addr.s_addr = INADDR_ANY;
memset(socket_server_addr.sin_zero, 0, 8);
/* 捆绑sockfd描述符 */
ret = bind(duty_socket, (const struct sockaddr *)&socket_server_addr, sizeof(struct sockaddr));
4、服务器调用listen函数表示接受连接请求
int listen(int sockfd,int backlog);
ret = listen(duty_socket, C_QUEUE);
sockfd是bind后的文件描述符。
backlog设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
listen函数将bind的文件描述符变为监听套接字,返回的情况和bind一样。
5、服务器获得连接请求并建立连接
int accept(int sockfd, struct sockaddr *addr,int *addrlen);
int customer_socket;
customer_socket = accept(duty_socket, (struct sockaddr *)&socket_client_addr, &addr_len);
sockfd是listen后的文件描述符。
addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind,listen和accept是服务器端用的函数。
accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。 accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了,失败时返回-1 。
6、客户端建立连接
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);
ret = connect(client_socket, (const struct sockaddr *)&socket_server_addr, sizeof(struct sockaddr));
可以用connect建立一个连接,在connect中所指定的地址是想与之通信的服务器的地址。
sockfd是socket函数返回的文件描述符。
serv_addr储存了服务器端的连接信息,其中sin_add是服务端的地址。
addrlen是serv_addr的长度
connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符,失败时返回-1。
7、发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd 指定发送端套接字描述符;
buf 指明一个存放应用程序要发送数据的缓冲区;
len 指明实际要发送的数据的字节数;
flags 一般置0。
客户或者服务器应用程序都用send函数来向TCP连接的另一端发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sendto和send相似,区别在于sendto允许在无连接的套接字上指定一个目标地址。
dest_addr 表示目地机的IP地址和端口号信息,
addrlen 常常被赋值为sizeof (struct sockaddr)。
sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。
8、接受数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd 指定接收端套接字描述符;
buf 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
len 指明buf的长度;
flags 一般置0。
客户或者服务器应用程序都用recv函数从TCP连接的另一端接收数据。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
recvfrom通常用于无连接套接字,因为此函数可以获得发送者的地址。
src_addr 是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。
addrlen 常置为sizeof (struct sockaddr)。