了解socket套接字编程

  • 前言
  • TCP可靠传输的建立
  • socket套接字
  • ==socket函数==
  • ==bind函数==
  • ==listen函数==
  • ==connect函数==
  • ==accept函数==
  • ==setsockopt、setsockopt函数==
  • ==总结==
  • TCP的三次握手
  • 利用工具查看
  • 服务器发送和服务器接收
  • 粘包和粘包
  • handler组件会根据分隔符或者长度从缓冲区里面读出数据


前言

博主写的目的一是把自己所学的知识做一个记录方便复习,二是让更多的人看到并指出其中的错误指出,参考内容在文章末尾如有侵权请告知马上删除,不喜勿喷。

通过学习,我们知道基于传输层开发通讯程序,会出现粘包和粘包,所以今天通过Wireshark抓包工具来看看具体的传输内容。

TCP可靠传输的建立

socket套接字

首先来介绍几个相关的API函数

socket函数

sd = socket(protofamily,type,proto);
  • 方法:创建一个套接字,用于传输层和应用层中的应用进程做数据交互
  • 返回值:调用方法返回一个套接字描述符sd
  • 参数1:protofamily为协议族,protofamily = PF_INET(TCP/IP)
  • 参数2:type为类型:SOCK_STREAM,SOCK_DGRAM or SOCK_RAW
    – SOCK_STREAM表示TCP传输,特点:可靠、面向连接、字节流传输、点对点
    – SOCK_DGRAM表示UDP传输,特点:不可靠、非连接、数据报传输
  • 参数3:proto为协议号,默认为0
    示例代码
struct protoent *p; 
	p=getprotobyname("tcp"); 
	SOCKET sd=socket(PF_INET,SOCK_STREAM,p->p_proto);

wireshark 复制 json_socket

bind函数

int bind(sd,localaddr,addrlen);
  • 方法:给socket函数创建的sd描述符绑定一个端点地址(通常客户端是不需要我们自己来绑定)
  • 返回值:调用方法返回一个套接字描述符sd
  • 参数1:套接字sd描述符
  • 参数2:绑定的本地端点地址
    – 端点地址:本地IP地址+端口号
  • 参数3:

listen函数

int listen(sd,queuesize);
  • 方法:设置服务器端的流套接字处于监听状态(仅被服务器端调用,仅用于面向连接的流套接字,不能用于UDP类型的套接字
  • 返回值:0:成功 SOCKET_ERROR :失败
  • 参数1:套接字sd描述符
  • 参数2:queuesize为请求队列大小,用于设置连接请求队列大小的

wireshark 复制 json_wireshark 复制 json_02

connect函数

connect(sd,saddr,saddrlen);
  • 方法:客户端调用connect函数来使客户套接字(sd)与指定服务器端的套接字进行连接(仅用于客户端,可用于TCP客户端和UDP客户端
  • 返回值:0:成功 SOCKET_ERROR :失败
  • 参数1:套接字sd描述符
  • 参数2:saddr 可以理解为指定服务器的特定端口

accept函数

newsock = accept(sd,caddr,caddrlen);
  • 方法:服务器端用accept函数从处于监听状态的流套接字sd的客户端请求队列中取出排在最前的一个客户端连接(该队列是存放已完成三次握手的客户端连接,未完成三次握手的客户端连接存放另外一个队列),并创建一个新的套接字newsock来与客户套接字创建连接通道(仅用于TCP服务端切仅用于服务器
  • 返回值:服务端创建的新的套接字,用来与客户端建立连接进行通讯
  • 参数1:服务端套接字sd描述符
  • 参数1:caddr 可以理解为客户端的指定端口

setsockopt、setsockopt函数

int setsockopt(int sd, int level, int optname, *optval, int optlen);
	int getsockopt(int sd, int level, int optname,  *optval, socklen_t *optlen);
  • setsockopt()函数用来设置套接字sd的选项参数
  • getsockopt()函数用于获取任意类型、任意状态套接口的选项当前值,并把结果存入optval

总结

wireshark 复制 json_客户端_03

TCP的三次握手

三次握手是发生在客户端向服务器发送TCP连接请求,当三次握手成功完成,则把该客户端连接放到握手完成队列中,然后服务端通过accept方法来获取该客户端连接并创建新的socket来与客户端通讯。

一开始主机A和主机B 处于close(关闭)状态,然后B的TCP服务器进程先创建传输控制块(TCP),处于LISTEN状态来监听客户机A的连接,当客户端和服务端处于ESTABLISHED状态,客户端就可以调用send方法向服务器端发送消息,服务端可以通过recv方法获取消息,反之服务端也可以调用send方法给客户端发送数据。

三次握手图

wireshark 复制 json_socket_04


三次握手的大概流程

  • A的TCP客户进程也是首先创建传输控制块TCB,然后打算建立TCP连接时,向B发送请求报文段,这时候首部的同步位SYN=1,同时选择一个初始序号seq=x,TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但是必须消耗掉一个序号,这时候,客户端进入了SYN-SEND(同步已发送)状态
  • B收到连接请求的时候,如果同意建议连接,则会向A发送确认。在确认报文中应把SYN和ACK置为1,确认号是ack=x+1(表示下次B期待收到A的seq为x+1),同时也会为自己选择一个最初的seq=y,也不能携带数据且消耗一个序列号。这时候服务器进入SYN-RCVD状态。
  • 客户端收到B的确认后,还要给服务器发送确认已收到确认。确认报文段的ACK置1,确认号ack=y+1,而自己的序号ack=x+1.TCP的标准规定,ACK报文段可以携带数据,单如果不携带数据则不消化序号,在这种情况,下一个数据报文段的序号仍是seq=x+1,这时,TCP已建立连接。A进入ESTABLISHED(已经建立连接)状态。

利用工具查看

wireshark 复制 json_wireshark 复制 json_05


第5~7 是TCP的三次握手

第8行客户端发送数据

wireshark 复制 json_socket_06

第9行服务器端回复数据

wireshark 复制 json_tcp/ip_07


第10和第11,客户端连续发送2次数据

wireshark 复制 json_wireshark_08


10和11两次连续发到TCP缓冲区中,但是从socket从缓冲区中读到的数据并不一点是上面那种情况

服务器发送和服务器接收

客户端发送内容

String xml = "hi,blueheart,hello world";
        while (true){
            out.write(xml.getBytes());
            out.flush();
        }

服务器端收到的打印内容

wireshark 复制 json_tcp/ip_09


客户端从套接字写入到TCP缓冲区中,然后TCP 中通过的某些机制,发送到服务器中TCP的缓冲区中,最好通过套接字读到服务器程序里,输出为上面的内容。通过上面,我们可以知道客户端发送的数据和服务器接收的数据不是一样的,这就是我们下面要讨论的粘包和粘包的现象。

粘包和粘包

handler组件会根据分隔符或者长度从缓冲区里面读出数据

参考资料
哈尔滨大学李金龙老师的计算机网络 链接: https://www.icourse163.org/course/HIT-154005?from=searchPage.