美文网首页
Socket之TCP相关

Socket之TCP相关

作者: 分流替躺欧阳克 | 来源:发表于2019-06-03 18:01 被阅读0次

客户端socket简单示例(以下都是针对TCP协议)

#define BUF_SIZE 100

int socket = socket(AF_INET,SOCK_STREAm,IPPROTO_TCP);
struct sockaddr_in sockAddr;
sockAddr.sn_famil = AF_INET;
sockAddr.sn_addr.s_addr = inet_addr(127.0.0.1);//处理网络字节序,大小端差异
sockAddr.sn_port = htons(1234);

connect(socket,(struct sockAddr *)&sockAddr, sizeof(sockAddr) );
char buffer[BUF_SIZE];
read(socket,buffer,sizeof(BUF_SIZE)-1);

close(socket);

服务端sockt简单示例

AF_INET:代表ipv4地址;SOCK_STREAM:数据传输方式,代表面向连接的数据传输方法;IPPROTO_TCP:指定协议
int socket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
struct sockAddr_in serv_addr;
memset(&serv_addr,0,sizeof(serv_addr));//清理内存
sockAddr.sin_famil = AF_INET;
sockAddr.sin_addr.s_addr = inet_addr(127.0.0.1);
sockAddr.sin_port = htons(1234);

bind(socket,(struct sockAddr_in*)sockAddr,sizeof(sockAddr));//绑定socket描述符,把组合体特定地址分配给socket。
listen(socket,20);//socket是对应描述符,20 是可以排队的最大连接数
sickle_t  clnt_addr_size = sizeof(clnt_addr);
int clinSocket =   accpect(socket, (struct sockaddr*)&serv_addr,clnt_addr_size);//获取客户端的socket
char str[] = "你好,客户端";
write(socket, str,sizeof(str));
close(clinSocket);
close(socket);

客户端流程

  1. 客户端创建socket,先调socket函数创建个socket返回socket描述符,它存在于协议族空间中没有具体地址。
  2. 声明结构体sockAddr_in,赋值地址族或称协议域,IP地址,端口,用来识别主机中的进程。
  3. conect连接服务器(这里开始三次握手连接);
  4. 然后可以使用下面函数进行发送接收数据了
  • read()/write()
  • recv()/send()
  • readv()/writev()
  • recvmsg()/sendmsg()
  • recvfrom()/sendto()
  1. close() 使用后可以关闭数据了(这里开始四次握手断开连接)。

服务端流程

  1. 客户端创建socket,先调socket函数创建个socket返回socket描述符,它存在于协议族空间中没有具体地址。
  2. 声明结构体sockAddr_in,赋值地址族或称协议域,IP地址,端口,用来识别主机中的进程。
  3. 调用bind()函数把一个地址族中的特定地址赋给socket,如:把AF_INET,SOCK_STREAM,IPPROTO_TCP组合赋值给socket。如果没有调用过bind()这个函数,客户端调用connect() 或者服务端调用listen()监听函数,系统会自动分配一个端口号和自身的IP地址组合,用于提供服务,而客户端就不用指定,这也是服务端代码要调bind()函数而客户端不用的原因。(客户端不用bind函数)。
  4. 调用listen(),开始监听上面创造的socket。
  5. 当socket处于listen状态,调用accept()程序就会进入阻塞状态,直到接受到客户端的connect()请求。accept() 会返回一个和客户端有关的socket描述字符,然后可以对这个字符进行各种读写操作,便是向客户端发送数据。
    6.使用完成后便可以close关闭自己创建的socket和accept返回的socket

注:close是将socket从内存中清楚,还有种方式shutdown,用来断开连接,但是不会清除socket,函数原型int shutdown(int sock, int howto),howto参数表示断开方式。在linux下有以下几个方式。

  • SHUT_RD:断开输入流。套接字无法接收数据(即使输入缓冲区收到数据也被抹去),无法调用输入相关函数。其他平台稍有不同。
  • SHUT_WR:断开输出流。套接字无法发送数据,但如果输出缓冲区中还有未传输的数据,则将传递到目标主机。
  • SHUT_RDWR:同时断开 I/O 流。相当于分两次调用 shutdown(),其中一次以
  • SHUT_RD 为参数,另一次以 SHUT_WR 为参数

三次握手建立连接

首先得有个服务端处于监听socket的状态,accept阻塞着线程等待客户端的请求。
下图是网上找的TCP数据报结构,握手时的数据包格式


TCP数据报结构

三次握手中,需要对TCP头部的这几个概念理解

  1. seq表示包的序号。
  2. ack来确认对方发送的某个序号seq收到了,采用ack = seq + 1的方式
  3. URG,ACK,PSH,PST,SYN,FIN这6个bit表示标志,SYN表示是同步连接数据包,FIN是否断连数据包。所以四次断连发送的就是FIN包。

第一次:客户端调用connect()向目标socket发起连接请求,这一步客户端socket会组织一个数据包比如:ACK标志位不设置,设置SYN标志位(Synchronous同步的意思,同步建立连接)标志位 =1,seq = x,进入SYN-SEND(同步已发送)状态,发送给服务端,此时connect()函数会进入阻塞状态,等待服务端的返回。


第一次握手数据包

第二次:服务端接受到客户端的请求和数据包后,组织数据包,SYN置1,ACK标志位(Acknowledge确认的意思)置1, 同时生成一个确认序号ack= x+1 ,同时随机选择seq = y进入SYN-RCVD(同步已发送)状态。


第二次握手数据包

第三次:客户端接受到了服务端的确认连接请求数据包,此时connect会返回,进入连接状,组织数据包:SYN标志位不设置,ACK置1,确认号ack = y+1,seq= x + 1(我第二次发给你包),再次发送给服务端,表示已经知道你已经收到我想连接的请求并且可以连接了。此时服务端收到数据后,进入已建立连接的状态,accept()函数会返回一个socket 描述符,即,我们在服务端进行读写操作的socket,这样连接就完成了。


第三次握手TCP数据包 三次握手连接示意图

注:

seq的作用是标识数据包序号,ack的作用是确认对方发送相应seq序号的数据包是否收到,把客户端发的seq加上1赋值给ack,然后返回给客户端表示已经收到你的连接请求,同时自己根据ack的值,再次组织下一个按序号应该发送的包,给seq赋值,发送给客户端,客户端收到数据后,查看ack是否是自己第一次发送数据包里的seq加上1的值,来确认服务端有没有收到自己的连接请求,然后客户端重新组织数据包,把服务端数据包里的seq加上1 赋值给自己的ack确认号,发送给服务端,表示我已经知道你收到我发送连接的请求了。
这个过程中若是收到服务的数据包ack并不是上次自己发送seq加上1,则会认为服务端没有收到数据,会重新组织数据再次发送,直到接到服务端包含正确的seq数据包或者等待数据包时间超时失效。

四次握手断开连接

  1. 客户端调用close()函数后,向服务端发送FIN数据包(设置了FIN标志位为1),客户端进入第一等待阶段。

  2. 服务器收到数据包后,检测到设置FIN标志位,知道要断开连接了,向客户端发送一个确认包,服务端进入关闭等待阶段。并不是立即关闭,只是先向客户端发送确认包,告诉客户端,我知道要断开了,准备些事情。此时,客户端收到确认包后,等待服务器准备完毕再次给自己发送数据包,这时客户端进入第二等待阶段。

  3. 过一段时间后,服务器准备完成,可以断开连接了,于时再次发送FIN包,告诉客户端,准备好了,可以断开连接了,这时服务端进入一个LAST_ACK的状态(猜测时只能接收一个ACK包的状态)。

  4. 客户端收到服务端的FIN数据包后,再次向服务端发送ACK包,告诉服务端,断开吧,就进入了TIME_WAIT状态(注意,此时客户端并不是立即断开连接,而是会等待一会,因为会存在数据包丢失的情况,若是服务端没有收到自己最后发送的数据包,则服务端还会向客户端发送FIN_2数据包,等待时间,与数据包在网络中的有效时限有关)。此时,服务端接受到数据后会关闭Socket进入Close状态。客户端等了一段时间后仍没有接到FIN_2的数据包,就会认为,服务端已经接到自己最后一次发送的数据包,这时客户端才会进入CLOSE的状态。


    四次断连图

socket缓冲区以及阻塞模式

socket缓冲区

每个socket创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
write()和send() 并不是立即向网络中传输数据,而是先将数据写入缓冲区中,由TCP协议将数据发送到目标机器,一旦数据写入缓冲区,函数就可以成功返回,不管有没有到达目标机器,也不管他们何时被发送到网络,这些都是TCP负责的事情。
这些I/O缓冲区特性如下:

  • I/O缓冲区在每个TCP套接字中单独存在
  • I/O缓冲区在创建套接字时会自动生成
  • 即使关闭套接字也会继续传送输出缓冲区中遗留的数据。
  • 关闭套接字将丢失输入缓冲区中的数据


    四次握手断连示意图.jpg

阻塞模式

对于TCP套接字,当使用write()/send()发送数据时:

  1. 首先会检查缓冲区,若缓冲区的剩余空间小雨要发送的数据时,write()/send()进入阻塞模式,将会等待数据发送到目标机器,腾出足够空间,才会唤醒write()/send()函数继续写入数据。
  2. 如果TCP协议正在向网络发送数据,那么输出缓冲区将会被锁定,不允许写入,write/send()函数写入数据。
  3. 如果要写入的数据大于缓冲区的最大长度,那么将会分批写入。
  4. 直到所有数据被写入缓冲区 write()/send() 才能返回。
    当使用read和recv读取数据时。
  5. 手写会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。
  6. 如果要读取的数据长度小于缓冲区中的数据长度,那么就不能不一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到read()/recv()函数才会返回,否则就一直被阻塞。
  7. 直到读取到数据后 read()/recv()才会返回,否则就一直被阻塞。

所谓阻塞,就是上一步动作没有完成,下一步动作将暂停,直到上一步动作完成后才能继续,以保持同步性。

相关文章

  • Socket之TCP相关

    客户端socket简单示例(以下都是针对TCP协议) 服务端sockt简单示例 客户端流程 客户端创建socket...

  • Socket TCP相关

    TCP 连接3次握手,断开连接4次握手图示 tcp/ip协议listen函数中backlog参数的含义 1、cli...

  • Socket相关&TCP,UDP实战demo

    Socket所在结构模型 Socket相关的面试题 ____这里以实战角度介绍TCP,UDP使用...

  • Python学习23-socket编程

    查看所有Python相关学习笔记 socket编程 应用使用socket编程接口来通过TCP/IP协议进行通信 网...

  • 31.Python之网络编程(socket模块)

    Python之网络编程(socket模块) 什么是socket?Socket是应用层与TCP / IP协议族通信的...

  • Socket

    Socket socket是什么 socket是什么?socket在哪?先了解TCP/IP协议 TCP/IP:传输...

  • TCP socket 编程

    TCP socket 编程 讲一下 socket 编程 步骤 使用 socket 模块 建立 TCP socket...

  • Kotlin网络编程(3)TCP Socket 低层次网络编程

    TCP Socket 通信概念TCP Socket 通信过程Socket 类ServerSocket 类案例:文件...

  • SOCKET,TCP/UDP,HTTP,FTP

    转。。。。。。。。 SOCKET,TCP/UDP,HTTP,FTP (一)TCP/UDP,SOCKET,HTTP,...

  • 22.网络socket

    Socket / TCP / HTTP 联系与区别 Socket / TCP socket)是通信的基石,是支持T...

网友评论

      本文标题:Socket之TCP相关

      本文链接:https://www.haomeiwen.com/subject/ivrcxctx.html