网络编程2
TCP编程
scoket
create an endpoint for communicate
socket在内核区创建了一个文件对象,里面有发送缓冲区和接收缓冲区。
scoket(domain,type)各项参数含义:
- domain 领域:AF_INET ipv4协议
- type 通信协议:SOCK_STREAM 有序,可靠,全双工(与AF_INET一起使用,选择的是TCP协议)
bind
bind a name to a socket
服务端是被动的,一定要bind才能被别人发现。客户端可以band,可以不bind。在TCP编程中,不推荐客户端bind,bind后ip和端口绑定,若客户端服务端都bind后,四元组确定,断开连接后,经历一段时间后才能重新建立连接。客户端不bind ,方便同时建立多条链接。
bind(sockfd,*addr,addrlen)
- sockfd 填socket调用的返回值
- addr 填网络地址在内存中的指针(填写的是公共类型)
- addrlen 填网络地址在内存中的大小
注:bind 只能绑定本地ip地址,本地ip地址使用ifconfig查看
bind需要检错,一般情况下可能会出错
ifconfig
ens33 外网网卡 通信的其中一端在本机之外,走外网网卡
lo 本地环回设备 通信的双方都在本机之中,数据就不需要走外网网卡,直接内部通信即可。
端口号
16bit 0~65535
0~1023: 知名协议端口号
- 80 http
- 443 https
- 22 ssh
最好是两万以内的端口号都别用,以免选到知名协议端口
connect
initiate a connection on a socet
connect(scoket,*addr,addrlen)
- sockfd 填scoket的文件描述符
- addr&addrlen 填服务端的 ip 和 port
排查网络故障的一般流程
- netstat -an 重点查看最后一列,查看出问题的网络状态
- 抓包:tcpdump抓包存到一个 *.cap文件:1- 切换到root用户 2-
- 将 *.cap 文件拷贝到 windows(可用winscp), 用wireshark来阅读
listen
listen for connections on a socket
创建socket是一个主动的,只有listen()后才会成为被动的socket。
listen(scokfd,backlog):
- sockfd 填写socket的文件描述符
- backlog 写50就够用了
从通信scoket变成了拥有半连接队列和全连接队列的监听socket。
在listen之后,服务端调用listen,listen本身不会阻塞,也不会收发数据,listen切换了服务端状态。
当服务端调用完listen后,如果客户端connect,客户端会发起第一次握手,服务端进程收到第一次握手,操作系统内核负责将这次连接放入半连接队列,操作系统会回复第二次握手。客户端随后收到第二次握手,回复第三次握手,connect成功后,客户端已经进入ESTABLISHED状态。服务端随后收到第三次握手,操作系统内核负责将连接从半连接队列出队,移入全连接队列,服务端进入ESTABLISHED状态。
listen不处于三次连接的任何一次,时间极短,改变状态。
安全问题
伪造的客户端,只发第一次握手,不发第三次握手(SYN flooding攻击)。被拿到操作系统权限的机器称为肉鸡,可以使用大量肉鸡进行SYN flooding 攻击服务端(被称为DDOS攻击)。
accept
accept a connection on a socket
accept()
- sockfd 监听sockfd的fd
- addr&addrlsn 传入传出 可以都填NULL,只取出连接不获取对面的ip和port,如果想获取对面的ip和port,addrlen指向的值为sizeof(addrlen)。
返回值int ,是新的文件描述符。
检查、监听socket里面的全连接队列
- 若无连接,进程阻塞
- 若有连接,按照先进先出的方式取出连接,并可以获取对面的 ip 和 port
在操作系统的内核视角里面,accept 的本质和read是一样的。类比全连接队列和管道接收缓冲区。
当收到某客户端连接请求,会调用accept时候创建一个通信socket(有一个发送缓冲区和一个接收缓冲区),并分配一个文件描述符。可以类比于使用了两个单工管道实现全双工通信。
若文件描述符超出大小,可使用ulimit -a更改open file(1024)大小到足够大。
send
send(sockfd,*buf,len,flags)
- sockfd :socket文件描述符
- buf : 用户缓冲区数据。
- len : 使用strlen() 得到数据实际长度
- flags : 一般填0就可以
send是一种特殊的write,send直接完成的事情是拷贝,将用户态的 buf 数据拷贝到内核发送缓冲区,数据到内核缓冲区后,由内核协议栈真正的收发数据。发送时,数据超出mtu,内核协议栈进行切分,用户不需要考虑收发数据。使用一次send可能会分几次发送到网络。
发送缓冲区为空的时候,send不会阻塞。
recv
recv(sockfd,*buf,len,flags)
- sockfd : socket文件描述符
- buf : 用户缓冲区,内存可以适当多申请一些,一个栈帧大小大概为10M,根本用不完。
- len : 使用sizeof() 限定缓冲区上限值。
- flags 一般填0
recv是一种特殊的read,recv直接完成的事情是拷贝,将内核接收缓冲区的内容拷贝到用户态的buf中,而接收数据时由内核协议栈完成。
接收缓冲区为空的时候,recv会阻塞。
send和recv对于用户的意义
在应用层,永辉不需要考虑数据如何收发(乱序、丢包等),用户只需要管把数据扔到缓冲区即可。用户的视角中完全可以把建立好的连接看成两根单向管道即可。
可以使用write、read取代send、recv。反过来不行,send、recv只能操作通信socket,不能操作磁盘文件、设备文件、管道文件。