了解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);
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为请求队列大小,用于设置连接请求队列大小的
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
总结
TCP的三次握手
三次握手是发生在客户端向服务器发送TCP连接请求,当三次握手成功完成,则把该客户端连接放到握手完成队列中,然后服务端通过accept方法来获取该客户端连接并创建新的socket来与客户端通讯。
一开始主机A和主机B 处于close(关闭)状态,然后B的TCP服务器进程先创建传输控制块(TCP),处于LISTEN状态来监听客户机A的连接,当客户端和服务端处于ESTABLISHED状态,客户端就可以调用send方法向服务器端发送消息,服务端可以通过recv方法获取消息,反之服务端也可以调用send方法给客户端发送数据。
三次握手图
三次握手的大概流程
- 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(已经建立连接)状态。
利用工具查看
第5~7 是TCP的三次握手
第8行客户端发送数据
第9行服务器端回复数据
第10和第11,客户端连续发送2次数据
10和11两次连续发到TCP缓冲区中,但是从socket从缓冲区中读到的数据并不一点是上面那种情况
服务器发送和服务器接收
客户端发送内容
String xml = "hi,blueheart,hello world";
while (true){
out.write(xml.getBytes());
out.flush();
}
服务器端收到的打印内容
客户端从套接字写入到TCP缓冲区中,然后TCP 中通过的某些机制,发送到服务器中TCP的缓冲区中,最好通过套接字读到服务器程序里,输出为上面的内容。通过上面,我们可以知道客户端发送的数据和服务器接收的数据不是一样的,这就是我们下面要讨论的粘包和粘包的现象。
粘包和粘包
handler组件会根据分隔符或者长度从缓冲区里面读出数据
参考资料
哈尔滨大学李金龙老师的计算机网络 链接: https://www.icourse163.org/course/HIT-154005?from=searchPage.